Merge pull request #210 from blockworks-foundation/ckamm/liq-force-close
Liquidate serum open orders
This commit is contained in:
commit
14913c70d2
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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}`,
|
||||
);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue