2022-05-27 22:05:34 -07:00
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
|
|
use anchor_client::{Client, Cluster, Program};
|
|
|
|
|
|
|
|
use anchor_lang::__private::bytemuck;
|
|
|
|
use anchor_lang::prelude::System;
|
|
|
|
use anchor_lang::{AccountDeserialize, Id};
|
|
|
|
use anchor_spl::associated_token::get_associated_token_address;
|
|
|
|
use anchor_spl::token::{Mint, Token};
|
|
|
|
|
2022-06-18 07:31:28 -07:00
|
|
|
use fixed::types::I80F48;
|
|
|
|
use itertools::Itertools;
|
2022-05-27 22:05:34 -07:00
|
|
|
use mango_v4::instructions::{Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side};
|
2022-06-18 07:31:28 -07:00
|
|
|
use mango_v4::state::{
|
2022-07-13 05:04:20 -07:00
|
|
|
Bank, Group, MangoAccount, MintInfo, PerpMarket, PerpMarketIndex, Serum3Market, TokenIndex,
|
2022-06-18 07:31:28 -07:00
|
|
|
};
|
2022-05-27 22:05:34 -07:00
|
|
|
|
|
|
|
use solana_client::rpc_client::RpcClient;
|
|
|
|
|
2022-05-29 03:25:12 -07:00
|
|
|
use crate::util::MyClone;
|
|
|
|
use anyhow::Context;
|
2022-05-27 22:05:34 -07:00
|
|
|
use solana_client::rpc_filter::{Memcmp, MemcmpEncodedBytes, RpcFilterType};
|
|
|
|
use solana_sdk::instruction::{AccountMeta, Instruction};
|
|
|
|
use solana_sdk::signature::{Keypair, Signature};
|
|
|
|
use solana_sdk::sysvar;
|
|
|
|
use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signer::Signer};
|
|
|
|
|
2022-06-19 11:16:12 -07:00
|
|
|
// todo: might want to integrate geyser, websockets, or simple http polling for keeping data fresh
|
2022-05-27 22:05:34 -07:00
|
|
|
pub struct MangoClient {
|
|
|
|
pub rpc: RpcClient,
|
|
|
|
pub cluster: Cluster,
|
|
|
|
pub commitment: CommitmentConfig,
|
|
|
|
pub payer: Keypair,
|
|
|
|
pub admin: Keypair,
|
|
|
|
pub mango_account_cache: (Pubkey, MangoAccount),
|
|
|
|
pub group: Pubkey,
|
2022-07-13 05:04:20 -07:00
|
|
|
pub group_cache: Group,
|
2022-05-27 22:05:34 -07:00
|
|
|
// TODO: future: this may not scale if there's thousands of mints, probably some function
|
|
|
|
// wrapping getMultipleAccounts is needed (or bettew: we provide this data as a service)
|
2022-06-27 02:27:17 -07:00
|
|
|
pub banks_cache: HashMap<String, Vec<(Pubkey, Bank)>>,
|
|
|
|
pub banks_cache_by_token_index: HashMap<TokenIndex, Vec<(Pubkey, Bank)>>,
|
2022-05-27 22:05:34 -07:00
|
|
|
pub mint_infos_cache: HashMap<Pubkey, (Pubkey, MintInfo, Mint)>,
|
|
|
|
pub mint_infos_cache_by_token_index: HashMap<TokenIndex, (Pubkey, MintInfo, Mint)>,
|
|
|
|
pub serum3_markets_cache: HashMap<String, (Pubkey, Serum3Market)>,
|
|
|
|
pub serum3_external_markets_cache: HashMap<String, (Pubkey, Vec<u8>)>,
|
|
|
|
pub perp_markets_cache: HashMap<String, (Pubkey, PerpMarket)>,
|
2022-06-18 07:31:28 -07:00
|
|
|
pub perp_markets_cache_by_perp_market_index: HashMap<PerpMarketIndex, (Pubkey, PerpMarket)>,
|
2022-05-27 22:05:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: add retry framework for sending tx and rpc calls
|
|
|
|
// 1/ this works right now, but I think mid-term the MangoClient will want to interact with multiple mango accounts
|
|
|
|
// -- then we should probably specify accounts by owner+account_num / or pubkey
|
|
|
|
// 2/ pubkey, can be both owned, but also delegated accouns
|
|
|
|
|
|
|
|
impl MangoClient {
|
|
|
|
pub fn new(
|
|
|
|
cluster: Cluster,
|
|
|
|
commitment: CommitmentConfig,
|
|
|
|
payer: Keypair,
|
|
|
|
admin: Keypair,
|
2022-06-18 07:31:28 -07:00
|
|
|
mango_account_name: &str,
|
2022-05-27 22:05:34 -07:00
|
|
|
) -> anyhow::Result<Self> {
|
|
|
|
let program =
|
|
|
|
Client::new_with_options(cluster.clone(), std::rc::Rc::new(payer.clone()), commitment)
|
|
|
|
.program(mango_v4::ID);
|
|
|
|
|
|
|
|
let rpc = program.rpc();
|
|
|
|
|
|
|
|
let group = Pubkey::find_program_address(
|
2022-05-29 03:25:12 -07:00
|
|
|
&[
|
|
|
|
"Group".as_ref(),
|
|
|
|
admin.pubkey().as_ref(),
|
|
|
|
0u32.to_le_bytes().as_ref(),
|
|
|
|
],
|
2022-05-27 22:05:34 -07:00
|
|
|
&program.id(),
|
|
|
|
)
|
|
|
|
.0;
|
|
|
|
|
2022-07-13 05:04:20 -07:00
|
|
|
let group_data = program.account::<Group>(group)?;
|
|
|
|
|
2022-05-29 03:25:12 -07:00
|
|
|
// Mango Account
|
|
|
|
let mut mango_account_tuples = program.accounts::<MangoAccount>(vec![
|
|
|
|
RpcFilterType::Memcmp(Memcmp {
|
2022-07-06 00:56:14 -07:00
|
|
|
offset: 8,
|
2022-05-29 03:25:12 -07:00
|
|
|
bytes: MemcmpEncodedBytes::Base58(group.to_string()),
|
|
|
|
encoding: None,
|
|
|
|
}),
|
|
|
|
RpcFilterType::Memcmp(Memcmp {
|
2022-07-06 00:56:14 -07:00
|
|
|
offset: 40,
|
2022-05-29 03:25:12 -07:00
|
|
|
bytes: MemcmpEncodedBytes::Base58(payer.pubkey().to_string()),
|
|
|
|
encoding: None,
|
|
|
|
}),
|
|
|
|
])?;
|
|
|
|
let mango_account_opt = mango_account_tuples
|
|
|
|
.iter()
|
|
|
|
.find(|tuple| tuple.1.name() == mango_account_name);
|
|
|
|
if mango_account_opt.is_none() {
|
|
|
|
mango_account_tuples
|
|
|
|
.sort_by(|a, b| a.1.account_num.partial_cmp(&b.1.account_num).unwrap());
|
|
|
|
let account_num = match mango_account_tuples.last() {
|
|
|
|
Some(tuple) => tuple.1.account_num + 1,
|
|
|
|
None => 0u8,
|
|
|
|
};
|
|
|
|
program
|
|
|
|
.request()
|
|
|
|
.instruction(Instruction {
|
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: anchor_lang::ToAccountMetas::to_account_metas(
|
2022-07-06 05:51:15 -07:00
|
|
|
&mango_v4::accounts::AccountCreate {
|
2022-05-29 03:25:12 -07:00
|
|
|
group,
|
|
|
|
owner: payer.pubkey(),
|
|
|
|
account: {
|
|
|
|
Pubkey::find_program_address(
|
|
|
|
&[
|
|
|
|
group.as_ref(),
|
|
|
|
b"MangoAccount".as_ref(),
|
|
|
|
payer.pubkey().as_ref(),
|
|
|
|
&account_num.to_le_bytes(),
|
|
|
|
],
|
|
|
|
&mango_v4::id(),
|
|
|
|
)
|
|
|
|
.0
|
|
|
|
},
|
|
|
|
payer: payer.pubkey(),
|
|
|
|
system_program: System::id(),
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
data: anchor_lang::InstructionData::data(
|
2022-07-06 05:51:15 -07:00
|
|
|
&mango_v4::instruction::AccountCreate {
|
2022-05-29 03:25:12 -07:00
|
|
|
account_num,
|
|
|
|
name: mango_account_name.to_owned(),
|
|
|
|
},
|
|
|
|
),
|
|
|
|
})
|
|
|
|
.send()
|
|
|
|
.context("Failed to create account...")?;
|
|
|
|
}
|
|
|
|
let mango_account_tuples = program.accounts::<MangoAccount>(vec![
|
2022-05-27 22:05:34 -07:00
|
|
|
RpcFilterType::Memcmp(Memcmp {
|
2022-07-06 00:56:14 -07:00
|
|
|
offset: 8,
|
2022-05-27 22:05:34 -07:00
|
|
|
bytes: MemcmpEncodedBytes::Base58(group.to_string()),
|
|
|
|
encoding: None,
|
|
|
|
}),
|
|
|
|
RpcFilterType::Memcmp(Memcmp {
|
2022-07-06 00:56:14 -07:00
|
|
|
offset: 40,
|
2022-05-27 22:05:34 -07:00
|
|
|
bytes: MemcmpEncodedBytes::Base58(payer.pubkey().to_string()),
|
|
|
|
encoding: None,
|
|
|
|
}),
|
|
|
|
])?;
|
2022-05-29 03:25:12 -07:00
|
|
|
let index = mango_account_tuples
|
|
|
|
.iter()
|
2022-06-18 07:31:28 -07:00
|
|
|
.position(|tuple| tuple.1.name() == mango_account_name)
|
2022-05-29 03:25:12 -07:00
|
|
|
.unwrap();
|
|
|
|
let mango_account_cache = mango_account_tuples[index];
|
2022-05-27 22:05:34 -07:00
|
|
|
|
2022-05-29 03:25:12 -07:00
|
|
|
// banks cache
|
2022-05-27 22:05:34 -07:00
|
|
|
let mut banks_cache = HashMap::new();
|
|
|
|
let mut banks_cache_by_token_index = HashMap::new();
|
|
|
|
let bank_tuples = program.accounts::<Bank>(vec![RpcFilterType::Memcmp(Memcmp {
|
2022-07-06 00:56:14 -07:00
|
|
|
offset: 8,
|
2022-05-27 22:05:34 -07:00
|
|
|
bytes: MemcmpEncodedBytes::Base58(group.to_string()),
|
|
|
|
encoding: None,
|
|
|
|
})])?;
|
|
|
|
for (k, v) in bank_tuples {
|
2022-06-27 02:27:17 -07:00
|
|
|
banks_cache
|
|
|
|
.entry(v.name().to_owned())
|
|
|
|
.or_insert_with(|| Vec::new())
|
|
|
|
.push((k, v));
|
|
|
|
banks_cache_by_token_index
|
|
|
|
.entry(v.token_index)
|
|
|
|
.or_insert_with(|| Vec::new())
|
|
|
|
.push((k, v));
|
2022-05-27 22:05:34 -07:00
|
|
|
}
|
|
|
|
|
2022-05-29 03:25:12 -07:00
|
|
|
// mintinfo cache
|
2022-05-27 22:05:34 -07:00
|
|
|
let mut mint_infos_cache = HashMap::new();
|
|
|
|
let mut mint_infos_cache_by_token_index = HashMap::new();
|
|
|
|
let mint_info_tuples =
|
|
|
|
program.accounts::<MintInfo>(vec![RpcFilterType::Memcmp(Memcmp {
|
|
|
|
offset: 8,
|
|
|
|
bytes: MemcmpEncodedBytes::Base58(group.to_string()),
|
|
|
|
encoding: None,
|
|
|
|
})])?;
|
|
|
|
for (k, v) in mint_info_tuples {
|
|
|
|
let data = program
|
|
|
|
.rpc()
|
|
|
|
.get_account_with_commitment(&v.mint, commitment)?
|
|
|
|
.value
|
|
|
|
.unwrap()
|
|
|
|
.data;
|
|
|
|
let mint = Mint::try_deserialize(&mut &data[..])?;
|
|
|
|
|
|
|
|
mint_infos_cache.insert(v.mint, (k, v, mint.clone()));
|
|
|
|
mint_infos_cache_by_token_index.insert(v.token_index, (k, v, mint));
|
|
|
|
}
|
|
|
|
|
2022-05-29 03:25:12 -07:00
|
|
|
// serum3 markets cache
|
2022-05-27 22:05:34 -07:00
|
|
|
let mut serum3_markets_cache = HashMap::new();
|
|
|
|
let mut serum3_external_markets_cache = HashMap::new();
|
|
|
|
let serum3_market_tuples =
|
|
|
|
program.accounts::<Serum3Market>(vec![RpcFilterType::Memcmp(Memcmp {
|
2022-07-06 00:56:14 -07:00
|
|
|
offset: 8,
|
2022-05-27 22:05:34 -07:00
|
|
|
bytes: MemcmpEncodedBytes::Base58(group.to_string()),
|
|
|
|
encoding: None,
|
|
|
|
})])?;
|
|
|
|
for (k, v) in serum3_market_tuples {
|
|
|
|
serum3_markets_cache.insert(v.name().to_owned(), (k, v));
|
|
|
|
|
|
|
|
let market_external_bytes = program
|
|
|
|
.rpc()
|
|
|
|
.get_account_with_commitment(&v.serum_market_external, commitment)?
|
|
|
|
.value
|
|
|
|
.unwrap()
|
|
|
|
.data;
|
|
|
|
serum3_external_markets_cache.insert(
|
|
|
|
v.name().to_owned(),
|
|
|
|
(v.serum_market_external, market_external_bytes),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-05-29 03:25:12 -07:00
|
|
|
// perp markets cache
|
2022-05-27 22:05:34 -07:00
|
|
|
let mut perp_markets_cache = HashMap::new();
|
2022-06-18 07:31:28 -07:00
|
|
|
let mut perp_markets_cache_by_perp_market_index = HashMap::new();
|
2022-05-27 22:05:34 -07:00
|
|
|
let perp_market_tuples =
|
|
|
|
program.accounts::<PerpMarket>(vec![RpcFilterType::Memcmp(Memcmp {
|
2022-07-06 00:56:14 -07:00
|
|
|
offset: 8,
|
2022-05-27 22:05:34 -07:00
|
|
|
bytes: MemcmpEncodedBytes::Base58(group.to_string()),
|
|
|
|
encoding: None,
|
|
|
|
})])?;
|
|
|
|
for (k, v) in perp_market_tuples {
|
|
|
|
perp_markets_cache.insert(v.name().to_owned(), (k, v));
|
2022-06-18 07:31:28 -07:00
|
|
|
perp_markets_cache_by_perp_market_index.insert(v.perp_market_index, (k, v));
|
2022-05-27 22:05:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Self {
|
|
|
|
rpc,
|
|
|
|
cluster,
|
|
|
|
commitment,
|
|
|
|
admin,
|
|
|
|
payer,
|
|
|
|
mango_account_cache,
|
|
|
|
group,
|
2022-07-13 05:04:20 -07:00
|
|
|
group_cache: group_data,
|
2022-05-27 22:05:34 -07:00
|
|
|
banks_cache,
|
|
|
|
banks_cache_by_token_index,
|
|
|
|
mint_infos_cache,
|
|
|
|
mint_infos_cache_by_token_index,
|
|
|
|
serum3_markets_cache,
|
|
|
|
serum3_external_markets_cache,
|
|
|
|
perp_markets_cache,
|
2022-06-18 07:31:28 -07:00
|
|
|
perp_markets_cache_by_perp_market_index,
|
2022-05-27 22:05:34 -07:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-06-27 02:27:17 -07:00
|
|
|
pub fn get_mint_info(&self, token_index: &TokenIndex) -> Pubkey {
|
|
|
|
self.mint_infos_cache_by_token_index
|
|
|
|
.get(token_index)
|
|
|
|
.unwrap()
|
|
|
|
.0
|
|
|
|
}
|
|
|
|
|
2022-05-27 22:05:34 -07:00
|
|
|
pub fn client(&self) -> Client {
|
|
|
|
Client::new_with_options(
|
|
|
|
self.cluster.clone(),
|
|
|
|
std::rc::Rc::new(self.payer.clone()),
|
|
|
|
self.commitment,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn program(&self) -> Program {
|
|
|
|
self.client().program(mango_v4::ID)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn payer(&self) -> Pubkey {
|
|
|
|
self.payer.pubkey()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn group(&self) -> Pubkey {
|
|
|
|
self.group
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_account(&self) -> Result<(Pubkey, MangoAccount), anchor_client::ClientError> {
|
|
|
|
let mango_accounts = self.program().accounts::<MangoAccount>(vec![
|
|
|
|
RpcFilterType::Memcmp(Memcmp {
|
2022-07-06 00:56:14 -07:00
|
|
|
offset: 8,
|
2022-05-27 22:05:34 -07:00
|
|
|
bytes: MemcmpEncodedBytes::Base58(self.group().to_string()),
|
|
|
|
encoding: None,
|
|
|
|
}),
|
|
|
|
RpcFilterType::Memcmp(Memcmp {
|
2022-07-06 00:56:14 -07:00
|
|
|
offset: 40,
|
2022-05-27 22:05:34 -07:00
|
|
|
bytes: MemcmpEncodedBytes::Base58(self.payer().to_string()),
|
|
|
|
encoding: None,
|
|
|
|
}),
|
|
|
|
])?;
|
|
|
|
Ok(mango_accounts[0])
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn derive_health_check_remaining_account_metas(
|
|
|
|
&self,
|
|
|
|
affected_bank: Option<(Pubkey, Bank)>,
|
|
|
|
writable_banks: bool,
|
|
|
|
) -> Result<Vec<AccountMeta>, anchor_client::ClientError> {
|
|
|
|
// figure out all the banks/oracles that need to be passed for the health check
|
|
|
|
let mut banks = vec![];
|
|
|
|
let mut oracles = vec![];
|
|
|
|
let account = self.get_account()?;
|
|
|
|
for position in account.1.tokens.iter_active() {
|
|
|
|
let mint_info = self
|
|
|
|
.mint_infos_cache_by_token_index
|
|
|
|
.get(&position.token_index)
|
|
|
|
.unwrap()
|
|
|
|
.1;
|
|
|
|
// TODO: ALTs are unavailable
|
|
|
|
// let lookup_table = account_loader
|
|
|
|
// .load_bytes(&mint_info.address_lookup_table)
|
|
|
|
// .await
|
|
|
|
// .unwrap();
|
|
|
|
// let addresses = mango_v4::address_lookup_table::addresses(&lookup_table);
|
|
|
|
// banks.push(addresses[mint_info.address_lookup_table_bank_index as usize]);
|
|
|
|
// oracles.push(addresses[mint_info.address_lookup_table_oracle_index as usize]);
|
2022-06-27 02:27:17 -07:00
|
|
|
banks.push(mint_info.first_bank());
|
2022-05-27 22:05:34 -07:00
|
|
|
oracles.push(mint_info.oracle);
|
|
|
|
}
|
|
|
|
if let Some(affected_bank) = affected_bank {
|
|
|
|
if !banks.iter().any(|&v| v == affected_bank.0) {
|
|
|
|
// If there is not yet an active position for the token, we need to pass
|
|
|
|
// the bank/oracle for health check anyway.
|
|
|
|
let new_position = account
|
|
|
|
.1
|
|
|
|
.tokens
|
|
|
|
.values
|
|
|
|
.iter()
|
|
|
|
.position(|p| !p.is_active())
|
|
|
|
.unwrap();
|
|
|
|
banks.insert(new_position, affected_bank.0);
|
|
|
|
oracles.insert(new_position, affected_bank.1.oracle);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let serum_oos = account.1.serum3.iter_active().map(|&s| s.open_orders);
|
2022-06-18 07:31:28 -07:00
|
|
|
let perp_markets = account.1.perps.iter_active_accounts().map(|&pa| {
|
|
|
|
self.perp_markets_cache_by_perp_market_index
|
|
|
|
.get(&pa.market_index)
|
|
|
|
.unwrap()
|
|
|
|
.0
|
|
|
|
});
|
2022-05-27 22:05:34 -07:00
|
|
|
|
|
|
|
Ok(banks
|
|
|
|
.iter()
|
|
|
|
.map(|&pubkey| AccountMeta {
|
|
|
|
pubkey,
|
|
|
|
is_writable: writable_banks,
|
|
|
|
is_signer: false,
|
|
|
|
})
|
|
|
|
.chain(oracles.iter().map(|&pubkey| AccountMeta {
|
|
|
|
pubkey,
|
|
|
|
is_writable: false,
|
|
|
|
is_signer: false,
|
|
|
|
}))
|
|
|
|
.chain(serum_oos.map(|pubkey| AccountMeta {
|
|
|
|
pubkey,
|
|
|
|
is_writable: false,
|
|
|
|
is_signer: false,
|
|
|
|
}))
|
2022-06-18 07:31:28 -07:00
|
|
|
.chain(perp_markets.map(|pubkey| AccountMeta {
|
|
|
|
pubkey,
|
|
|
|
is_writable: false,
|
|
|
|
is_signer: false,
|
|
|
|
}))
|
|
|
|
.collect())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn derive_liquidation_health_check_remaining_account_metas(
|
|
|
|
&self,
|
|
|
|
liqee: &MangoAccount,
|
|
|
|
asset_token_index: TokenIndex,
|
|
|
|
liab_token_index: TokenIndex,
|
|
|
|
) -> Result<Vec<AccountMeta>, anchor_client::ClientError> {
|
|
|
|
// figure out all the banks/oracles that need to be passed for the health check
|
|
|
|
let mut banks = vec![];
|
|
|
|
let mut oracles = vec![];
|
|
|
|
let account = self.get_account()?;
|
|
|
|
|
|
|
|
let token_indexes = liqee
|
|
|
|
.tokens
|
|
|
|
.iter_active()
|
|
|
|
.chain(account.1.tokens.iter_active())
|
|
|
|
.map(|ta| ta.token_index)
|
|
|
|
.unique();
|
|
|
|
|
|
|
|
for token_index in token_indexes {
|
|
|
|
let mint_info = self
|
|
|
|
.mint_infos_cache_by_token_index
|
|
|
|
.get(&token_index)
|
|
|
|
.unwrap()
|
|
|
|
.1;
|
|
|
|
let writable_bank = token_index == asset_token_index || token_index == liab_token_index;
|
|
|
|
// TODO: ALTs are unavailable
|
|
|
|
// let lookup_table = account_loader
|
|
|
|
// .load_bytes(&mint_info.address_lookup_table)
|
|
|
|
// .await
|
|
|
|
// .unwrap();
|
|
|
|
// let addresses = mango_v4::address_lookup_table::addresses(&lookup_table);
|
|
|
|
// banks.push(addresses[mint_info.address_lookup_table_bank_index as usize]);
|
|
|
|
// oracles.push(addresses[mint_info.address_lookup_table_oracle_index as usize]);
|
2022-06-27 02:27:17 -07:00
|
|
|
banks.push((mint_info.first_bank(), writable_bank));
|
2022-06-18 07:31:28 -07:00
|
|
|
oracles.push(mint_info.oracle);
|
|
|
|
}
|
|
|
|
|
|
|
|
let serum_oos = liqee
|
|
|
|
.serum3
|
|
|
|
.iter_active()
|
|
|
|
.chain(account.1.serum3.iter_active())
|
|
|
|
.map(|&s| s.open_orders);
|
|
|
|
let perp_markets = liqee
|
|
|
|
.perps
|
|
|
|
.iter_active_accounts()
|
|
|
|
.chain(account.1.perps.iter_active_accounts())
|
|
|
|
.map(|&pa| {
|
|
|
|
self.perp_markets_cache_by_perp_market_index
|
|
|
|
.get(&pa.market_index)
|
|
|
|
.unwrap()
|
|
|
|
.0
|
|
|
|
});
|
|
|
|
|
|
|
|
let to_account_meta = |pubkey| AccountMeta {
|
|
|
|
pubkey,
|
|
|
|
is_writable: false,
|
|
|
|
is_signer: false,
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(banks
|
|
|
|
.iter()
|
|
|
|
.map(|(pubkey, is_writable)| AccountMeta {
|
|
|
|
pubkey: *pubkey,
|
|
|
|
is_writable: *is_writable,
|
|
|
|
is_signer: false,
|
|
|
|
})
|
|
|
|
.chain(oracles.into_iter().map(to_account_meta))
|
|
|
|
.chain(serum_oos.map(to_account_meta))
|
|
|
|
.chain(perp_markets.map(to_account_meta))
|
2022-05-27 22:05:34 -07:00
|
|
|
.collect())
|
|
|
|
}
|
|
|
|
|
2022-06-09 09:27:31 -07:00
|
|
|
pub fn token_deposit(
|
2022-05-27 22:05:34 -07:00
|
|
|
&self,
|
|
|
|
token_name: &str,
|
|
|
|
amount: u64,
|
|
|
|
) -> Result<Signature, anchor_client::ClientError> {
|
2022-06-27 02:27:17 -07:00
|
|
|
let bank = self.banks_cache.get(token_name).unwrap().get(0).unwrap();
|
2022-05-27 22:05:34 -07:00
|
|
|
let mint_info: MintInfo = self.mint_infos_cache.get(&bank.1.mint).unwrap().1;
|
|
|
|
|
|
|
|
let health_check_metas =
|
|
|
|
self.derive_health_check_remaining_account_metas(Some(*bank), false)?;
|
|
|
|
|
|
|
|
self.program()
|
|
|
|
.request()
|
|
|
|
.instruction(Instruction {
|
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: {
|
|
|
|
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
|
2022-06-09 09:27:31 -07:00
|
|
|
&mango_v4::accounts::TokenDeposit {
|
2022-05-27 22:05:34 -07:00
|
|
|
group: self.group(),
|
|
|
|
account: self.mango_account_cache.0,
|
|
|
|
bank: bank.0,
|
|
|
|
vault: bank.1.vault,
|
|
|
|
token_account: get_associated_token_address(
|
|
|
|
&self.payer(),
|
|
|
|
&mint_info.mint,
|
|
|
|
),
|
|
|
|
token_authority: self.payer(),
|
|
|
|
token_program: Token::id(),
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
ams.extend(health_check_metas.into_iter());
|
|
|
|
ams
|
|
|
|
},
|
2022-06-09 09:27:31 -07:00
|
|
|
data: anchor_lang::InstructionData::data(&mango_v4::instruction::TokenDeposit {
|
2022-05-27 22:05:34 -07:00
|
|
|
amount,
|
|
|
|
}),
|
|
|
|
})
|
|
|
|
.send()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_oracle_price(
|
|
|
|
&self,
|
|
|
|
token_name: &str,
|
|
|
|
) -> Result<pyth_sdk_solana::Price, anyhow::Error> {
|
2022-06-27 02:27:17 -07:00
|
|
|
let bank = self.banks_cache.get(token_name).unwrap().get(0).unwrap().1;
|
2022-05-27 22:05:34 -07:00
|
|
|
|
|
|
|
let data = self
|
|
|
|
.program()
|
|
|
|
.rpc()
|
|
|
|
.get_account_with_commitment(&bank.oracle, self.commitment)?
|
|
|
|
.value
|
|
|
|
.unwrap()
|
|
|
|
.data;
|
|
|
|
|
|
|
|
Ok(pyth_sdk_solana::load_price(&data).unwrap())
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Serum3
|
|
|
|
//
|
|
|
|
|
|
|
|
pub fn serum3_create_open_orders(
|
|
|
|
&self,
|
|
|
|
name: &str,
|
|
|
|
) -> Result<Signature, anchor_client::ClientError> {
|
|
|
|
let (account_pubkey, _) = self.mango_account_cache;
|
|
|
|
|
|
|
|
let serum3_market = self.serum3_markets_cache.get(name).unwrap();
|
|
|
|
|
|
|
|
let open_orders = Pubkey::find_program_address(
|
|
|
|
&[
|
|
|
|
account_pubkey.as_ref(),
|
|
|
|
b"Serum3OO".as_ref(),
|
|
|
|
serum3_market.0.as_ref(),
|
|
|
|
],
|
|
|
|
&self.program().id(),
|
|
|
|
)
|
|
|
|
.0;
|
|
|
|
|
|
|
|
self.program()
|
|
|
|
.request()
|
|
|
|
.instruction(Instruction {
|
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: anchor_lang::ToAccountMetas::to_account_metas(
|
|
|
|
&mango_v4::accounts::Serum3CreateOpenOrders {
|
|
|
|
group: self.group(),
|
|
|
|
account: account_pubkey,
|
|
|
|
|
|
|
|
serum_market: serum3_market.0,
|
|
|
|
serum_program: serum3_market.1.serum_program,
|
|
|
|
serum_market_external: serum3_market.1.serum_market_external,
|
|
|
|
open_orders,
|
|
|
|
owner: self.payer(),
|
|
|
|
payer: self.payer(),
|
|
|
|
system_program: System::id(),
|
|
|
|
rent: sysvar::rent::id(),
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
data: anchor_lang::InstructionData::data(
|
|
|
|
&mango_v4::instruction::Serum3CreateOpenOrders {},
|
|
|
|
),
|
|
|
|
})
|
|
|
|
.send()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
|
|
pub fn serum3_place_order(
|
|
|
|
&self,
|
|
|
|
name: &str,
|
|
|
|
side: Serum3Side,
|
|
|
|
price: f64,
|
|
|
|
size: f64,
|
|
|
|
self_trade_behavior: Serum3SelfTradeBehavior,
|
|
|
|
order_type: Serum3OrderType,
|
|
|
|
client_order_id: u64,
|
|
|
|
limit: u16,
|
|
|
|
) -> Result<Signature, anchor_client::ClientError> {
|
|
|
|
let (_, account) = self.get_account()?;
|
|
|
|
|
|
|
|
let serum3_market = self.serum3_markets_cache.get(name).unwrap();
|
|
|
|
let open_orders = account
|
|
|
|
.serum3
|
|
|
|
.find(serum3_market.1.market_index)
|
|
|
|
.unwrap()
|
|
|
|
.open_orders;
|
|
|
|
let (_, quote_info, quote_mint) = self
|
|
|
|
.mint_infos_cache_by_token_index
|
|
|
|
.get(&serum3_market.1.quote_token_index)
|
|
|
|
.unwrap();
|
|
|
|
let (_, base_info, base_mint) = self
|
|
|
|
.mint_infos_cache_by_token_index
|
|
|
|
.get(&serum3_market.1.base_token_index)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let market_external: &serum_dex::state::MarketState = bytemuck::from_bytes(
|
|
|
|
&(self.serum3_external_markets_cache.get(name).unwrap().1)
|
|
|
|
[5..5 + std::mem::size_of::<serum_dex::state::MarketState>()],
|
|
|
|
);
|
|
|
|
let bids = market_external.bids;
|
|
|
|
let asks = market_external.asks;
|
|
|
|
let event_q = market_external.event_q;
|
|
|
|
let req_q = market_external.req_q;
|
|
|
|
let coin_vault = market_external.coin_vault;
|
|
|
|
let pc_vault = market_external.pc_vault;
|
|
|
|
let vault_signer = serum_dex::state::gen_vault_signer_key(
|
|
|
|
market_external.vault_signer_nonce,
|
|
|
|
&serum3_market.1.serum_market_external,
|
|
|
|
&serum3_market.1.serum_program,
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let health_check_metas = self.derive_health_check_remaining_account_metas(None, false)?;
|
|
|
|
|
|
|
|
// https://github.com/project-serum/serum-ts/blob/master/packages/serum/src/market.ts#L1306
|
|
|
|
let limit_price = {
|
|
|
|
(price
|
|
|
|
* ((10u64.pow(quote_mint.decimals as u32) * market_external.coin_lot_size) as f64))
|
|
|
|
as u64
|
|
|
|
/ (10u64.pow(base_mint.decimals as u32) * market_external.pc_lot_size)
|
|
|
|
};
|
|
|
|
// https://github.com/project-serum/serum-ts/blob/master/packages/serum/src/market.ts#L1333
|
|
|
|
let max_base_qty = {
|
|
|
|
(size * 10u64.pow(base_mint.decimals as u32) as f64) as u64
|
|
|
|
/ market_external.coin_lot_size
|
|
|
|
};
|
|
|
|
let max_native_quote_qty_including_fees = {
|
|
|
|
fn get_fee_tier(msrm_balance: u64, srm_balance: u64) -> u64 {
|
|
|
|
if msrm_balance >= 1 {
|
|
|
|
6
|
|
|
|
} else if srm_balance >= 1_000_000 {
|
|
|
|
5
|
|
|
|
} else if srm_balance >= 100_000 {
|
|
|
|
4
|
|
|
|
} else if srm_balance >= 10_000 {
|
|
|
|
3
|
|
|
|
} else if srm_balance >= 1_000 {
|
|
|
|
2
|
|
|
|
} else if srm_balance >= 100 {
|
|
|
|
1
|
|
|
|
} else {
|
|
|
|
0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_fee_rates(fee_tier: u64) -> (f64, f64) {
|
|
|
|
if fee_tier == 1 {
|
|
|
|
// SRM2
|
|
|
|
return (0.002, -0.0003);
|
|
|
|
} else if fee_tier == 2 {
|
|
|
|
// SRM3
|
|
|
|
return (0.0018, -0.0003);
|
|
|
|
} else if fee_tier == 3 {
|
|
|
|
// SRM4
|
|
|
|
return (0.0016, -0.0003);
|
|
|
|
} else if fee_tier == 4 {
|
|
|
|
// SRM5
|
|
|
|
return (0.0014, -0.0003);
|
|
|
|
} else if fee_tier == 5 {
|
|
|
|
// SRM6
|
|
|
|
return (0.0012, -0.0003);
|
|
|
|
} else if fee_tier == 6 {
|
|
|
|
// MSRM
|
|
|
|
return (0.001, -0.0005);
|
|
|
|
}
|
|
|
|
// Base
|
|
|
|
(0.0022, -0.0003)
|
|
|
|
}
|
|
|
|
|
|
|
|
let fee_tier = get_fee_tier(0, 0);
|
|
|
|
let rates = get_fee_rates(fee_tier);
|
|
|
|
(market_external.pc_lot_size as f64 * (1f64 + rates.0)) as u64
|
|
|
|
* (limit_price * max_base_qty)
|
|
|
|
};
|
|
|
|
|
|
|
|
self.program()
|
|
|
|
.request()
|
|
|
|
.instruction(Instruction {
|
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: {
|
|
|
|
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
|
|
|
|
&mango_v4::accounts::Serum3PlaceOrder {
|
|
|
|
group: self.group(),
|
|
|
|
account: self.mango_account_cache.0,
|
|
|
|
open_orders,
|
2022-06-27 02:27:17 -07:00
|
|
|
quote_bank: quote_info.first_bank(),
|
|
|
|
quote_vault: quote_info.first_vault(),
|
|
|
|
base_bank: base_info.first_bank(),
|
|
|
|
base_vault: base_info.first_vault(),
|
2022-05-27 22:05:34 -07:00
|
|
|
serum_market: serum3_market.0,
|
|
|
|
serum_program: serum3_market.1.serum_program,
|
|
|
|
serum_market_external: serum3_market.1.serum_market_external,
|
|
|
|
market_bids: from_serum_style_pubkey(&bids),
|
|
|
|
market_asks: from_serum_style_pubkey(&asks),
|
|
|
|
market_event_queue: from_serum_style_pubkey(&event_q),
|
|
|
|
market_request_queue: from_serum_style_pubkey(&req_q),
|
|
|
|
market_base_vault: from_serum_style_pubkey(&coin_vault),
|
|
|
|
market_quote_vault: from_serum_style_pubkey(&pc_vault),
|
|
|
|
market_vault_signer: vault_signer,
|
|
|
|
owner: self.payer(),
|
|
|
|
token_program: Token::id(),
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
ams.extend(health_check_metas.into_iter());
|
|
|
|
ams
|
|
|
|
},
|
|
|
|
data: anchor_lang::InstructionData::data(
|
|
|
|
&mango_v4::instruction::Serum3PlaceOrder {
|
|
|
|
side,
|
|
|
|
limit_price,
|
|
|
|
max_base_qty,
|
|
|
|
max_native_quote_qty_including_fees,
|
|
|
|
self_trade_behavior,
|
|
|
|
order_type,
|
|
|
|
client_order_id,
|
|
|
|
limit,
|
|
|
|
},
|
|
|
|
),
|
|
|
|
})
|
|
|
|
.send()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn serum3_settle_funds(&self, name: &str) -> Result<Signature, anchor_client::ClientError> {
|
|
|
|
let (_, account) = self.get_account()?;
|
|
|
|
|
|
|
|
let serum3_market = self.serum3_markets_cache.get(name).unwrap();
|
|
|
|
let open_orders = account
|
|
|
|
.serum3
|
|
|
|
.find(serum3_market.1.market_index)
|
|
|
|
.unwrap()
|
|
|
|
.open_orders;
|
|
|
|
let (_, quote_info, _) = self
|
|
|
|
.mint_infos_cache_by_token_index
|
|
|
|
.get(&serum3_market.1.quote_token_index)
|
|
|
|
.unwrap();
|
|
|
|
let (_, base_info, _) = self
|
|
|
|
.mint_infos_cache_by_token_index
|
|
|
|
.get(&serum3_market.1.base_token_index)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let market_external: &serum_dex::state::MarketState = bytemuck::from_bytes(
|
|
|
|
&(self.serum3_external_markets_cache.get(name).unwrap().1)
|
|
|
|
[5..5 + std::mem::size_of::<serum_dex::state::MarketState>()],
|
|
|
|
);
|
|
|
|
let coin_vault = market_external.coin_vault;
|
|
|
|
let pc_vault = market_external.pc_vault;
|
|
|
|
let vault_signer = serum_dex::state::gen_vault_signer_key(
|
|
|
|
market_external.vault_signer_nonce,
|
|
|
|
&serum3_market.1.serum_market_external,
|
|
|
|
&serum3_market.1.serum_program,
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
self.program()
|
|
|
|
.request()
|
|
|
|
.instruction(Instruction {
|
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: anchor_lang::ToAccountMetas::to_account_metas(
|
|
|
|
&mango_v4::accounts::Serum3SettleFunds {
|
|
|
|
group: self.group(),
|
|
|
|
account: self.mango_account_cache.0,
|
|
|
|
open_orders,
|
2022-06-27 02:27:17 -07:00
|
|
|
quote_bank: quote_info.first_bank(),
|
|
|
|
quote_vault: quote_info.first_vault(),
|
|
|
|
base_bank: base_info.first_bank(),
|
|
|
|
base_vault: base_info.first_vault(),
|
2022-05-27 22:05:34 -07:00
|
|
|
serum_market: serum3_market.0,
|
|
|
|
serum_program: serum3_market.1.serum_program,
|
|
|
|
serum_market_external: serum3_market.1.serum_market_external,
|
|
|
|
market_base_vault: from_serum_style_pubkey(&coin_vault),
|
|
|
|
market_quote_vault: from_serum_style_pubkey(&pc_vault),
|
|
|
|
market_vault_signer: vault_signer,
|
|
|
|
owner: self.payer(),
|
|
|
|
token_program: Token::id(),
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
data: anchor_lang::InstructionData::data(
|
|
|
|
&mango_v4::instruction::Serum3SettleFunds {},
|
|
|
|
),
|
|
|
|
})
|
|
|
|
.send()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn serum3_cancel_all_orders(&self, market_name: &str) -> Result<Vec<u128>, anyhow::Error> {
|
|
|
|
let serum3_market = self.serum3_markets_cache.get(market_name).unwrap();
|
|
|
|
|
|
|
|
let open_orders = Pubkey::find_program_address(
|
|
|
|
&[
|
|
|
|
self.mango_account_cache.0.as_ref(),
|
|
|
|
b"Serum3OO".as_ref(),
|
|
|
|
serum3_market.0.as_ref(),
|
|
|
|
],
|
|
|
|
&self.program().id(),
|
|
|
|
)
|
|
|
|
.0;
|
|
|
|
|
|
|
|
let open_orders_bytes = self
|
|
|
|
.program()
|
|
|
|
.rpc()
|
|
|
|
.get_account_with_commitment(&open_orders, self.commitment)?
|
|
|
|
.value
|
|
|
|
.unwrap()
|
|
|
|
.data;
|
|
|
|
let open_orders_data: &serum_dex::state::OpenOrders = bytemuck::from_bytes(
|
|
|
|
&open_orders_bytes[5..5 + std::mem::size_of::<serum_dex::state::OpenOrders>()],
|
|
|
|
);
|
|
|
|
|
|
|
|
let mut orders = vec![];
|
|
|
|
for order_id in open_orders_data.orders {
|
|
|
|
if order_id != 0 {
|
|
|
|
// TODO: find side for order_id, and only cancel the relevant order
|
|
|
|
self.serum3_cancel_order(market_name, Serum3Side::Bid, order_id)
|
|
|
|
.ok();
|
|
|
|
self.serum3_cancel_order(market_name, Serum3Side::Ask, order_id)
|
|
|
|
.ok();
|
|
|
|
|
|
|
|
orders.push(order_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(orders)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn serum3_cancel_order(
|
|
|
|
&self,
|
|
|
|
market_name: &str,
|
|
|
|
side: Serum3Side,
|
|
|
|
order_id: u128,
|
|
|
|
) -> Result<(), anyhow::Error> {
|
|
|
|
let (account_pubkey, _account) = self.get_account()?;
|
|
|
|
|
|
|
|
let serum3_market = self.serum3_markets_cache.get(market_name).unwrap();
|
|
|
|
|
|
|
|
let open_orders = Pubkey::find_program_address(
|
|
|
|
&[
|
|
|
|
account_pubkey.as_ref(),
|
|
|
|
b"Serum3OO".as_ref(),
|
|
|
|
serum3_market.0.as_ref(),
|
|
|
|
],
|
|
|
|
&self.program().id(),
|
|
|
|
)
|
|
|
|
.0;
|
|
|
|
|
|
|
|
let market_external: &serum_dex::state::MarketState = bytemuck::from_bytes(
|
|
|
|
&(self
|
|
|
|
.serum3_external_markets_cache
|
|
|
|
.get(market_name)
|
|
|
|
.unwrap()
|
|
|
|
.1)[5..5 + std::mem::size_of::<serum_dex::state::MarketState>()],
|
|
|
|
);
|
|
|
|
let bids = market_external.bids;
|
|
|
|
let asks = market_external.asks;
|
|
|
|
let event_q = market_external.event_q;
|
|
|
|
|
|
|
|
self.program()
|
|
|
|
.request()
|
|
|
|
.instruction(Instruction {
|
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: {
|
|
|
|
anchor_lang::ToAccountMetas::to_account_metas(
|
|
|
|
&mango_v4::accounts::Serum3CancelOrder {
|
|
|
|
group: self.group(),
|
|
|
|
account: account_pubkey,
|
|
|
|
serum_market: serum3_market.0,
|
|
|
|
serum_program: serum3_market.1.serum_program,
|
|
|
|
serum_market_external: serum3_market.1.serum_market_external,
|
|
|
|
open_orders,
|
|
|
|
market_bids: from_serum_style_pubkey(&bids),
|
|
|
|
market_asks: from_serum_style_pubkey(&asks),
|
|
|
|
market_event_queue: from_serum_style_pubkey(&event_q),
|
|
|
|
owner: self.payer(),
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
)
|
|
|
|
},
|
|
|
|
data: anchor_lang::InstructionData::data(
|
|
|
|
&mango_v4::instruction::Serum3CancelOrder { side, order_id },
|
|
|
|
),
|
|
|
|
})
|
|
|
|
.send()?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Perps
|
|
|
|
//
|
|
|
|
|
|
|
|
//
|
2022-06-18 07:31:28 -07:00
|
|
|
// Liquidation
|
2022-05-27 22:05:34 -07:00
|
|
|
//
|
2022-06-18 07:31:28 -07:00
|
|
|
|
|
|
|
pub fn liq_token_with_token(
|
|
|
|
&self,
|
|
|
|
liqee: (&Pubkey, &MangoAccount),
|
|
|
|
asset_token_index: TokenIndex,
|
|
|
|
liab_token_index: TokenIndex,
|
|
|
|
max_liab_transfer: I80F48,
|
|
|
|
) -> Result<Signature, anchor_client::ClientError> {
|
|
|
|
let health_remaining_ams = self
|
|
|
|
.derive_liquidation_health_check_remaining_account_metas(
|
|
|
|
liqee.1,
|
|
|
|
asset_token_index,
|
|
|
|
liab_token_index,
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
self.program()
|
|
|
|
.request()
|
|
|
|
.instruction(Instruction {
|
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: {
|
|
|
|
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
|
|
|
|
&mango_v4::accounts::LiqTokenWithToken {
|
|
|
|
group: self.group(),
|
|
|
|
liqee: *liqee.0,
|
|
|
|
liqor: self.mango_account_cache.0,
|
|
|
|
liqor_owner: self.payer.pubkey(),
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
ams.extend(health_remaining_ams);
|
|
|
|
ams
|
|
|
|
},
|
|
|
|
data: anchor_lang::InstructionData::data(
|
|
|
|
&mango_v4::instruction::LiqTokenWithToken {
|
|
|
|
asset_token_index,
|
|
|
|
liab_token_index,
|
|
|
|
max_liab_transfer,
|
|
|
|
},
|
|
|
|
),
|
|
|
|
})
|
|
|
|
.send()
|
|
|
|
}
|
2022-07-13 05:04:20 -07:00
|
|
|
|
|
|
|
pub fn liq_token_bankruptcy(
|
|
|
|
&self,
|
|
|
|
liqee: (&Pubkey, &MangoAccount),
|
|
|
|
liab_token_index: TokenIndex,
|
|
|
|
max_liab_transfer: I80F48,
|
|
|
|
) -> Result<Signature, anchor_client::ClientError> {
|
|
|
|
let quote_token_index = 0;
|
|
|
|
|
|
|
|
let (_, quote_mint_info, _) = self
|
|
|
|
.mint_infos_cache_by_token_index
|
|
|
|
.get("e_token_index)
|
|
|
|
.unwrap();
|
|
|
|
let (liab_mint_info_key, liab_mint_info, _) = self
|
|
|
|
.mint_infos_cache_by_token_index
|
|
|
|
.get(&liab_token_index)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let bank_remaining_ams = liab_mint_info
|
|
|
|
.banks()
|
|
|
|
.iter()
|
|
|
|
.map(|bank_pubkey| AccountMeta {
|
|
|
|
pubkey: *bank_pubkey,
|
|
|
|
is_signer: false,
|
|
|
|
is_writable: true,
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
let health_remaining_ams = self
|
|
|
|
.derive_liquidation_health_check_remaining_account_metas(
|
|
|
|
liqee.1,
|
|
|
|
quote_token_index,
|
|
|
|
liab_token_index,
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
self.program()
|
|
|
|
.request()
|
|
|
|
.instruction(Instruction {
|
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: {
|
|
|
|
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
|
|
|
|
&mango_v4::accounts::LiqTokenBankruptcy {
|
|
|
|
group: self.group(),
|
|
|
|
liqee: *liqee.0,
|
|
|
|
liqor: self.mango_account_cache.0,
|
|
|
|
liqor_owner: self.payer.pubkey(),
|
|
|
|
liab_mint_info: *liab_mint_info_key,
|
|
|
|
quote_vault: quote_mint_info.first_vault(),
|
|
|
|
insurance_vault: self.group_cache.insurance_vault,
|
|
|
|
token_program: Token::id(),
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
ams.extend(bank_remaining_ams);
|
|
|
|
ams.extend(health_remaining_ams);
|
|
|
|
ams
|
|
|
|
},
|
|
|
|
data: anchor_lang::InstructionData::data(
|
|
|
|
&mango_v4::instruction::LiqTokenBankruptcy {
|
|
|
|
liab_token_index,
|
|
|
|
max_liab_transfer,
|
|
|
|
},
|
|
|
|
),
|
|
|
|
})
|
|
|
|
.send()
|
|
|
|
}
|
2022-05-27 22:05:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn from_serum_style_pubkey(d: &[u64; 4]) -> Pubkey {
|
|
|
|
Pubkey::new(bytemuck::cast_slice(d as &[_]))
|
|
|
|
}
|