liquidator: force-cancel perp orders, liq perp base positions

This commit is contained in:
Christian Kamm 2022-09-15 09:57:48 +02:00
parent a97b40a521
commit 9cbc352197
9 changed files with 512 additions and 102 deletions

View File

@ -15,14 +15,16 @@ use bincode::Options;
use fixed::types::I80F48;
use itertools::Itertools;
use mango_v4::instructions::{Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side};
use mango_v4::state::{Bank, Group, MangoAccountValue, Serum3MarketIndex, TokenIndex};
use mango_v4::state::{
Bank, Group, MangoAccountValue, PerpMarketIndex, Serum3MarketIndex, TokenIndex,
};
use solana_client::nonblocking::rpc_client::RpcClient as RpcClientAsync;
use solana_client::rpc_client::RpcClient;
use solana_sdk::signer::keypair;
use crate::account_fetcher::*;
use crate::context::{MangoGroupContext, Serum3MarketContext, TokenContext};
use crate::context::{MangoGroupContext, PerpMarketContext, Serum3MarketContext, TokenContext};
use crate::gpa::fetch_mango_accounts;
use crate::jupiter;
use crate::util::MyClone;
@ -836,12 +838,98 @@ impl MangoClient {
//
// Perps
//
fn perp_data_by_market_index(
&self,
market_index: PerpMarketIndex,
) -> Result<&PerpMarketContext, ClientError> {
Ok(self.context.perp_markets.get(&market_index).unwrap())
}
pub fn perp_liq_force_cancel_orders(
&self,
liqee: (&Pubkey, &MangoAccountValue),
market_index: PerpMarketIndex,
) -> anyhow::Result<Signature> {
let perp = self.perp_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::PerpLiqForceCancelOrders {
group: self.group(),
account: *liqee.0,
perp_market: perp.address,
asks: perp.market.asks,
bids: perp.market.bids,
oracle: perp.market.oracle,
},
None,
);
ams.extend(health_remaining_ams.into_iter());
ams
},
data: anchor_lang::InstructionData::data(
&mango_v4::instruction::PerpLiqForceCancelOrders { limit: 5 },
),
})
.send()
.map_err(prettify_client_error)
}
pub fn perp_liq_base_position(
&self,
liqee: (&Pubkey, &MangoAccountValue),
market_index: PerpMarketIndex,
max_base_transfer: i64,
) -> anyhow::Result<Signature> {
let perp = self.perp_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::PerpLiqBasePosition {
group: self.group(),
perp_market: perp.address,
oracle: perp.market.oracle,
liqor: self.mango_account_address,
liqor_owner: self.owner(),
liqee: *liqee.0,
},
None,
);
ams.extend(health_remaining_ams.into_iter());
ams
},
data: anchor_lang::InstructionData::data(
&mango_v4::instruction::PerpLiqBasePosition { max_base_transfer },
),
})
.signer(&self.owner)
.send()
.map_err(prettify_client_error)
}
//
// Liquidation
//
pub fn liq_token_with_token(
pub fn token_liq_with_token(
&self,
liqee: (&Pubkey, &MangoAccountValue),
asset_token_index: TokenIndex,
@ -886,7 +974,7 @@ impl MangoClient {
.map_err(prettify_client_error)
}
pub fn liq_token_bankruptcy(
pub fn token_liq_bankruptcy(
&self,
liqee: (&Pubkey, &MangoAccountValue),
liab_token_index: TokenIndex,

View File

@ -5,7 +5,7 @@ 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,
Serum3Orders, TokenIndex, QUOTE_TOKEN_INDEX,
PerpMarketIndex, Serum3Orders, Side, TokenIndex, QUOTE_TOKEN_INDEX,
};
use itertools::Itertools;
@ -177,6 +177,36 @@ pub fn maybe_liquidate_account(
.filter_map_ok(|v| v)
.collect::<anyhow::Result<Vec<Serum3Orders>>>()?;
// look for any perp open orders and base positions
let perp_force_cancels = account
.active_perp_positions()
.filter_map(|pp| pp.has_open_orders().then(|| pp.market_index))
.collect::<Vec<PerpMarketIndex>>();
let mut perp_base_positions = account
.active_perp_positions()
.map(|pp| {
let base_lots = pp.base_position_lots();
if base_lots == 0 {
return Ok(None);
}
let perp = mango_client.context.perp(pp.market_index);
let oracle = account_fetcher.fetch_raw_account(perp.market.oracle)?;
let price = perp.market.oracle_price(&KeyedAccountSharedData::new(
perp.market.oracle,
oracle.into(),
))?;
Ok(Some((
pp.market_index,
base_lots,
price,
I80F48::from(base_lots.abs()) * price,
)))
})
.filter_map_ok(|v| v)
.collect::<anyhow::Result<Vec<(PerpMarketIndex, i64, I80F48, I80F48)>>>()?;
// sort by base_position_value, ascending
perp_base_positions.sort_by(|a, b| a.3.cmp(&b.3));
let get_max_liab_transfer = |source, target| -> anyhow::Result<I80F48> {
let mut liqor = account_fetcher
.fetch_fresh_mango_account(&mango_client.mango_account_address)
@ -203,7 +233,7 @@ pub fn maybe_liquidate_account(
// try liquidating
let txsig = if !serum_force_cancels.is_empty() {
// pick a random market to force-cancel orders on
// Cancel all orders on a random serum market
let serum_orders = serum_force_cancels.choose(&mut rand::thread_rng()).unwrap();
let sig = mango_client.serum3_liq_force_cancel_orders(
(pubkey, &account),
@ -211,13 +241,68 @@ pub fn maybe_liquidate_account(
&serum_orders.open_orders,
)?;
log::info!(
"Force cancelled serum market on account {}, market index {}, maint_health was {}, tx sig {:?}",
"Force cancelled serum orders on account {}, market index {}, maint_health was {}, tx sig {:?}",
pubkey,
serum_orders.market_index,
maint_health,
sig
);
sig
} else if !perp_force_cancels.is_empty() {
// Cancel all orders on a random perp market
let perp_market_index = *perp_force_cancels.choose(&mut rand::thread_rng()).unwrap();
let sig =
mango_client.perp_liq_force_cancel_orders((pubkey, &account), perp_market_index)?;
log::info!(
"Force cancelled perp orders on account {}, market index {}, maint_health was {}, tx sig {:?}",
pubkey,
perp_market_index,
maint_health,
sig
);
sig
} else if !perp_base_positions.is_empty() {
// Liquidate the highest-value perp base position
let (perp_market_index, base_lots, price, _) = perp_base_positions.last().unwrap();
let perp = mango_client.context.perp(*perp_market_index);
let (side, side_signum) = if *base_lots > 0 {
(Side::Bid, 1)
} else {
(Side::Ask, -1)
};
// Compute the max number of base_lots the liqor is willing to take
let max_base_transfer_abs = {
let mut liqor = account_fetcher
.fetch_fresh_mango_account(&mango_client.mango_account_address)
.context("getting liquidator account")?;
liqor.ensure_perp_position(*perp_market_index, QUOTE_TOKEN_INDEX)?;
let health_cache = new_health_cache_(&mango_client.context, account_fetcher, &liqor)
.expect("always ok");
health_cache.max_perp_for_health_ratio(
*perp_market_index,
*price,
perp.market.base_lot_size,
side,
min_health_ratio,
)?
};
log::info!("computed max_base_transfer to be {max_base_transfer_abs}");
let sig = mango_client.perp_liq_base_position(
(pubkey, &account),
*perp_market_index,
side_signum * max_base_transfer_abs,
)?;
log::info!(
"Liquidated base position for perp market on account {}, market index {}, maint_health was {}, tx sig {:?}",
pubkey,
perp_market_index,
maint_health,
sig
);
sig
} else if is_spot_bankrupt {
if tokens.is_empty() {
anyhow::bail!("mango account {}, is bankrupt has no active tokens", pubkey);
@ -240,7 +325,7 @@ pub fn maybe_liquidate_account(
let max_liab_transfer = get_max_liab_transfer(liab_token_index, quote_token_index)?;
let sig = mango_client
.liq_token_bankruptcy((pubkey, &account), liab_token_index, max_liab_transfer)
.token_liq_bankruptcy((pubkey, &account), liab_token_index, max_liab_transfer)
.context("sending liq_token_bankruptcy")?;
log::info!(
"Liquidated bankruptcy for {}, maint_health was {}, tx sig {:?}",
@ -288,7 +373,7 @@ pub fn maybe_liquidate_account(
// TODO: log liquee's liab_needed, need to refactor program code to be able to be accessed from client side
//
let sig = mango_client
.liq_token_with_token(
.token_liq_with_token(
(pubkey, &account),
asset_token_index,
liab_token_index,

View File

@ -8,6 +8,7 @@ use client::{chain_data, keypair_from_cli, Client, MangoClient, MangoGroupContex
use log::*;
use mango_v4::state::{PerpMarketIndex, TokenIndex};
use itertools::Itertools;
use solana_sdk::commitment_config::CommitmentConfig;
use solana_sdk::pubkey::Pubkey;
use std::collections::HashSet;
@ -126,6 +127,8 @@ async fn main() -> anyhow::Result<()> {
.tokens
.values()
.map(|value| value.mint_info.oracle)
.chain(group_context.perp_markets.values().map(|p| p.market.oracle))
.unique()
.collect::<Vec<Pubkey>>();
//

View File

@ -34,7 +34,14 @@ import {
PerpPosition,
} from './accounts/mangoAccount';
import { StubOracle } from './accounts/oracle';
import { PerpMarket, PerpOrderSide, PerpOrderType } from './accounts/perp';
import {
PerpEventQueue,
PerpMarket,
PerpOrderType,
PerpOrderSide,
FillEvent,
OutEvent,
} from './accounts/perp';
import {
generateSerum3MarketExternalVaultSignerAddress,
Serum3Market,
@ -541,9 +548,20 @@ export class MangoClient {
group: Group,
accountNumber?: number,
name?: string,
tokenCount?: number,
serum3Count?: number,
perpCount?: number,
perpOoCount?: number,
): Promise<TransactionSignature> {
const transaction = await this.program.methods
.accountCreate(accountNumber ?? 0, 8, 8, 0, 0, name ?? '')
.accountCreate(
accountNumber ?? 0,
tokenCount ?? 8,
serum3Count ?? 8,
perpCount ?? 0,
perpOoCount ?? 0,
name ?? '',
)
.accounts({
group: group.publicKey,
owner: (this.program.provider as AnchorProvider).wallet.publicKey,
@ -560,6 +578,32 @@ export class MangoClient {
);
}
public async createAndFetchMangoAccount(
group: Group,
accountNumber?: number,
name?: string,
tokenCount?: number,
serum3Count?: number,
perpCount?: number,
perpOoCount?: number,
): Promise<MangoAccount | undefined> {
const accNum = accountNumber ?? 0;
await this.createMangoAccount(
group,
accNum,
name,
tokenCount,
serum3Count,
perpCount,
perpOoCount,
);
return await this.getMangoAccountForOwner(
group,
(this.program.provider as AnchorProvider).wallet.publicKey,
accNum,
);
}
public async expandMangoAccount(
group: Group,
account: MangoAccount,
@ -1511,6 +1555,37 @@ export class MangoClient {
);
}
async perpDeactivatePosition(
group: Group,
mangoAccount: MangoAccount,
perpMarketName: string,
): Promise<TransactionSignature> {
const perpMarket = group.perpMarketsMap.get(perpMarketName)!;
const healthRemainingAccounts: PublicKey[] =
this.buildHealthRemainingAccounts(
AccountRetriever.Fixed,
group,
[mangoAccount],
[],
[],
);
return await this.program.methods
.perpDeactivatePosition()
.accounts({
group: group.publicKey,
account: mangoAccount.publicKey,
perpMarket: perpMarket.publicKey,
owner: (this.program.provider as AnchorProvider).wallet.publicKey,
})
.remainingAccounts(
healthRemainingAccounts.map(
(pk) =>
({ pubkey: pk, isWritable: false, isSigner: false } as AccountMeta),
),
)
.rpc();
}
async perpPlaceOrder(
group: Group,
mangoAccount: MangoAccount,
@ -1586,6 +1661,60 @@ export class MangoClient {
.rpc();
}
async perpConsumeEvents(
group: Group,
perpMarketName: string,
accounts: PublicKey[],
limit: number,
): Promise<TransactionSignature> {
const perpMarket = group.perpMarketsMap.get(perpMarketName)!;
return await this.program.methods
.perpConsumeEvents(new BN(limit))
.accounts({
group: group.publicKey,
perpMarket: perpMarket.publicKey,
eventQueue: perpMarket.eventQueue,
})
.remainingAccounts(
accounts.map(
(pk) =>
({ pubkey: pk, isWritable: true, isSigner: false } as AccountMeta),
),
)
.rpc();
}
async perpConsumeAllEvents(
group: Group,
perpMarketName: string,
): Promise<void> {
const limit = 8;
const perpMarket = group.perpMarketsMap.get(perpMarketName)!;
const eventQueue = await perpMarket.loadEventQueue(this);
let unconsumedEvents = eventQueue.getUnconsumedEvents();
while (unconsumedEvents.length > 0) {
const events = unconsumedEvents.splice(0, limit);
const accounts = events
.map((ev) => {
switch (ev.eventType) {
case PerpEventQueue.FILL_EVENT_TYPE:
const fill = <FillEvent>ev;
return [fill.maker, fill.taker];
case PerpEventQueue.OUT_EVENT_TYPE:
const out = <OutEvent>ev;
return [out.owner];
case PerpEventQueue.LIQUIDATE_EVENT_TYPE:
return [];
default:
throw new Error(`Unknown event with eventType ${ev.eventType}`);
}
})
.flat();
await this.perpConsumeEvents(group, perpMarketName, accounts, limit);
}
}
public async marginTrade({
group,
mangoAccount,

View File

@ -45,15 +45,6 @@ async function main() {
let sig;
// close stub oracles
const stubOracles = await client.getStubOracle(group);
for (const stubOracle of stubOracles) {
sig = await client.stubOracleClose(group, stubOracle.publicKey);
console.log(
`Closed stub oracle ${stubOracle.publicKey}, sig https://explorer.solana.com/tx/${sig}`,
);
}
// close all banks
for (const banks of group.banksMapByMint.values()) {
sig = await client.tokenDeregister(group, banks[0].mint);
@ -81,6 +72,15 @@ async function main() {
);
}
// close stub oracles
const stubOracles = await client.getStubOracle(group);
for (const stubOracle of stubOracles) {
sig = await client.stubOracleClose(group, stubOracle.publicKey);
console.log(
`Closed stub oracle ${stubOracle.publicKey}, sig https://explorer.solana.com/tx/${sig}`,
);
}
// finally, close the group
sig = await client.groupClose(group);
console.log(`Closed group, sig https://explorer.solana.com/tx/${sig}`);

View File

@ -16,12 +16,14 @@ const MAINNET_MINTS = new Map([
['USDC', 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'],
['BTC', '9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E'],
['SOL', 'So11111111111111111111111111111111111111112'],
['MNGO', 'MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac'],
]);
const STUB_PRICES = new Map([
['USDC', 1.0],
['BTC', 20000.0], // btc and usdc both have 6 decimals
['SOL', 0.04], // sol has 9 decimals, equivalent to $40 per SOL
['MNGO', 0.04], // same price/decimals as SOL for convenience
]);
// External markets are matched with those in https://github.com/blockworks-foundation/mango-client-v3/blob/main/src/ids.json
@ -179,14 +181,48 @@ async function main() {
}
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',
);
try {
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',
);
} catch (error) {
console.log(error);
}
console.log('Registering MNGO-PERP market...');
const mngoMainnetOracle = oracles.get('MNGO');
try {
await client.perpCreateMarket(
group,
mngoMainnetOracle,
0,
'MNGO-PERP',
0.1,
9,
0,
10,
100000, // base lots
0.9,
0.8,
1.1,
1.2,
0.05,
-0.001,
0.002,
-0.1,
0.1,
10,
false,
false,
);
} catch (error) {
console.log(error);
}
process.exit();
}

View File

@ -39,11 +39,14 @@ async function main() {
// create + fetch account
console.log(`Creating mangoaccount...`);
const mangoAccount = (await client.getOrCreateMangoAccount(
const mangoAccount = (await client.createAndFetchMangoAccount(
group,
admin.publicKey,
ACCOUNT_NUM,
'LIQTEST, FUNDING',
8,
4,
4,
4,
))!;
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
console.log(mangoAccount.toString());
@ -54,16 +57,16 @@ async function main() {
// deposit
try {
console.log(`...depositing 10 USDC`);
await client.tokenDeposit(group, mangoAccount, usdcMint, 10);
console.log(`...depositing 5 USDC`);
await client.tokenDeposit(group, mangoAccount, usdcMint, 5);
await mangoAccount.reload(client, group);
console.log(`...depositing 0.0004 BTC`);
await client.tokenDeposit(group, mangoAccount, btcMint, 0.0004);
console.log(`...depositing 0.0002 BTC`);
await client.tokenDeposit(group, mangoAccount, btcMint, 0.0002);
await mangoAccount.reload(client, group);
console.log(`...depositing 0.25 SOL`);
await client.tokenDeposit(group, mangoAccount, solMint, 0.25);
console.log(`...depositing 0.15 SOL`);
await client.tokenDeposit(group, mangoAccount, solMint, 0.15);
await mangoAccount.reload(client, group);
} catch (error) {
console.log(error);

View File

@ -6,6 +6,8 @@ import {
Serum3SelfTradeBehavior,
Serum3Side,
} from '../accounts/serum3';
import { Side, PerpOrderType } from '../accounts/perp';
import { MangoAccount } from '../accounts/mangoAccount';
import { MangoClient } from '../client';
import { MANGO_V4_ID } from '../constants';
@ -26,6 +28,7 @@ const MAINNET_MINTS = new Map([
['USDC', 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'],
['BTC', '9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E'],
['SOL', 'So11111111111111111111111111111111111111112'],
['MNGO', 'MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac'],
]);
const TOKEN_SCENARIOS: [string, string, number, string, number][] = [
@ -66,19 +69,30 @@ async function main() {
admin.publicKey,
);
let maxAccountNum = Math.max(0, ...accounts.map((a) => a.accountNum));
const fundingAccount = accounts.find(
(account) => account.name == 'LIQTEST, FUNDING',
);
if (!fundingAccount) {
throw new Error('could not find funding account');
}
async function createMangoAccount(name: string): Promise<MangoAccount> {
const accountNum = maxAccountNum + 1;
maxAccountNum = maxAccountNum + 1;
await client.createMangoAccount(group, accountNum, name, 4, 4, 4, 4);
return (await client.getMangoAccountForOwner(
group,
admin.publicKey,
accountNum,
))!;
}
for (const scenario of TOKEN_SCENARIOS) {
const [name, assetName, assetAmount, liabName, liabAmount] = scenario;
// create account
console.log(`Creating mangoaccount...`);
let mangoAccount = (await client.getOrCreateMangoAccount(
group,
admin.publicKey,
maxAccountNum + 1,
name,
))!;
maxAccountNum = maxAccountNum + 1;
let mangoAccount = await createMangoAccount(name);
console.log(
`...created mangoAccount ${mangoAccount.publicKey} for ${name}`,
);
@ -119,13 +133,7 @@ async function main() {
const name = 'LIQTEST, serum orders';
console.log(`Creating mangoaccount...`);
let mangoAccount = (await client.getOrCreateMangoAccount(
group,
admin.publicKey,
maxAccountNum + 1,
name,
))!;
maxAccountNum = maxAccountNum + 1;
let mangoAccount = await createMangoAccount(name);
console.log(
`...created mangoAccount ${mangoAccount.publicKey} for ${name}`,
);
@ -188,6 +196,108 @@ async function main() {
}
}
// Perp orders bring health <0, liquidator force closes
{
const name = 'LIQTEST, perp orders';
console.log(`Creating mangoaccount...`);
let mangoAccount = await createMangoAccount(name);
console.log(
`...created mangoAccount ${mangoAccount.publicKey} for ${name}`,
);
const baseMint = new PublicKey(MAINNET_MINTS.get('MNGO')!);
const collateralMint = new PublicKey(MAINNET_MINTS.get('SOL')!);
const collateralOracle = group.banksMapByName.get('SOL')![0].oracle;
await client.tokenDepositNative(
group,
mangoAccount,
collateralMint,
100000,
); // valued as $0.004 maint collateral
await mangoAccount.reload(client, group);
await client.stubOracleSet(group, collateralOracle, PRICES['SOL'] * 4);
try {
await client.perpPlaceOrder(
group,
mangoAccount,
'MNGO-PERP',
Side.bid,
1, // ui price that won't get hit
0.0011, // ui base quantity, 11 base lots, $0.044
0.044, // ui quote quantity
4200,
PerpOrderType.limit,
0,
5,
);
} finally {
await client.stubOracleSet(group, collateralOracle, PRICES['SOL']);
}
}
// Perp base pos brings health<0, liquidator takes most of it
{
const name = 'LIQTEST, perp base pos';
console.log(`Creating mangoaccount...`);
let mangoAccount = await createMangoAccount(name);
console.log(
`...created mangoAccount ${mangoAccount.publicKey} for ${name}`,
);
const baseMint = new PublicKey(MAINNET_MINTS.get('MNGO')!);
const collateralMint = new PublicKey(MAINNET_MINTS.get('SOL')!);
const collateralOracle = group.banksMapByName.get('SOL')![0].oracle;
await client.tokenDepositNative(
group,
mangoAccount,
collateralMint,
100000,
); // valued as $0.004 maint collateral
await mangoAccount.reload(client, group);
await client.stubOracleSet(group, collateralOracle, PRICES['SOL'] * 5);
try {
await client.perpPlaceOrder(
group,
fundingAccount,
'MNGO-PERP',
Side.ask,
40,
0.0011, // ui base quantity, 11 base lots, $0.044
0.044, // ui quote quantity
4200,
PerpOrderType.limit,
0,
5,
);
await client.perpPlaceOrder(
group,
mangoAccount,
'MNGO-PERP',
Side.bid,
40,
0.0011, // ui base quantity, 11 base lots, $0.044
0.044, // ui quote quantity
4200,
PerpOrderType.market,
0,
5,
);
await client.perpConsumeAllEvents(group, 'MNGO-PERP');
} finally {
await client.stubOracleSet(group, collateralOracle, PRICES['SOL']);
}
}
process.exit();
}

View File

@ -1,7 +1,8 @@
import { AnchorProvider, Wallet } from '@project-serum/anchor';
import { BN, AnchorProvider, Wallet } from '@project-serum/anchor';
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
import fs from 'fs';
import { MangoClient } from '../client';
import { Side, PerpOrderType } from '../accounts/perp';
import { MANGO_V4_ID } from '../constants';
//
@ -57,63 +58,18 @@ async function main() {
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.balance(bank).toNumber();
if (amount < 0) {
try {
await client.tokenDepositNative(
group,
account,
bank.mint,
Math.ceil(-amount),
);
await account.reload(client, group);
} catch (error) {
console.log(
`failed to deposit ${bank.name} into ${account.publicKey}: ${error}`,
);
process.exit();
}
}
for (let perpPosition of account.perpActive()) {
const perpMarket = group.findPerpMarket(perpPosition.marketIndex)!;
console.log(
`closing perp orders on: ${account} for market ${perpMarket.name}`,
);
await client.perpCancelAllOrders(group, account, perpMarket.name, 10);
}
}
accounts = await client.getMangoAccountsForOwner(group, admin.publicKey);
for (let account of accounts) {
console.log(`withdrawing deposits of account: ${account}`);
// withdraw all funds
for (let token of account.tokensActive()) {
const bank = group.getFirstBankByTokenIndex(token.tokenIndex);
const amount = token.balance(bank).toNumber();
if (amount > 0) {
try {
const allowBorrow = false;
await client.tokenWithdrawNative(
group,
account,
bank.mint,
amount,
allowBorrow,
);
await account.reload(client, group);
} catch (error) {
console.log(
`failed to withdraw ${bank.name} from ${account.publicKey}: ${error}`,
);
process.exit();
}
}
}
// close account
try {
console.log(`closing account: ${account}`);