bins: Fix restarting on new or changed listings (#802)
- Don't just restart on new listings, but also on significant changes to
old listings such as oracle changes.
- Cover the liquidator and settler in addition to the keeper.
(cherry picked from commit ce16d79b13
)
This commit is contained in:
parent
5cfbb8386d
commit
9ba0004760
|
@ -31,14 +31,9 @@ pub async fn save_snapshot(
|
|||
let oracles_and_vaults = group_context
|
||||
.tokens
|
||||
.values()
|
||||
.map(|value| value.mint_info.oracle)
|
||||
.chain(group_context.perp_markets.values().map(|p| p.market.oracle))
|
||||
.chain(
|
||||
group_context
|
||||
.tokens
|
||||
.values()
|
||||
.flat_map(|value| value.mint_info.vaults),
|
||||
)
|
||||
.map(|value| value.oracle)
|
||||
.chain(group_context.perp_markets.values().map(|p| p.oracle))
|
||||
.chain(group_context.tokens.values().flat_map(|value| value.vaults))
|
||||
.unique()
|
||||
.filter(|pk| *pk != Pubkey::default())
|
||||
.collect::<Vec<Pubkey>>();
|
||||
|
@ -46,7 +41,7 @@ pub async fn save_snapshot(
|
|||
let serum_programs = group_context
|
||||
.serum3_markets
|
||||
.values()
|
||||
.map(|s3| s3.market.serum_program)
|
||||
.map(|s3| s3.serum_program)
|
||||
.unique()
|
||||
.collect_vec();
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use itertools::Itertools;
|
||||
use mango_v4::accounts_zerocopy::KeyedAccount;
|
||||
use mango_v4_client::{Client, MangoGroupContext};
|
||||
|
@ -11,11 +13,23 @@ pub async fn run(client: &Client, group: Pubkey) -> anyhow::Result<()> {
|
|||
let oracles = context
|
||||
.tokens
|
||||
.values()
|
||||
.map(|t| t.mint_info.oracle)
|
||||
.chain(context.perp_markets.values().map(|p| p.market.oracle))
|
||||
.map(|t| t.oracle)
|
||||
.chain(context.perp_markets.values().map(|p| p.oracle))
|
||||
.unique()
|
||||
.collect_vec();
|
||||
|
||||
let banks: HashMap<_, _> = mango_v4_client::gpa::fetch_banks(&rpc_async, mango_v4::id(), group)
|
||||
.await?
|
||||
.iter()
|
||||
.map(|(_, b)| (b.oracle, *b))
|
||||
.collect();
|
||||
let perp_markets: HashMap<_, _> =
|
||||
mango_v4_client::gpa::fetch_perp_markets(&rpc_async, mango_v4::id(), group)
|
||||
.await?
|
||||
.iter()
|
||||
.map(|(_, p)| (p.oracle, *p))
|
||||
.collect();
|
||||
|
||||
let mut interval = tokio::time::interval(std::time::Duration::from_secs(5));
|
||||
loop {
|
||||
interval.tick().await;
|
||||
|
@ -41,25 +55,19 @@ pub async fn run(client: &Client, group: Pubkey) -> anyhow::Result<()> {
|
|||
account: account_opt.unwrap(),
|
||||
};
|
||||
|
||||
let tc_opt = context
|
||||
.tokens
|
||||
.values()
|
||||
.find(|t| t.mint_info.oracle == *pubkey);
|
||||
let pc_opt = context
|
||||
.perp_markets
|
||||
.values()
|
||||
.find(|p| p.market.oracle == *pubkey);
|
||||
let bank_opt = banks.get(pubkey);
|
||||
let perp_opt = perp_markets.get(pubkey);
|
||||
let mut price = None;
|
||||
if let Some(tc) = tc_opt {
|
||||
match tc.bank.oracle_price(&keyed_account, Some(slot)) {
|
||||
if let Some(bank) = bank_opt {
|
||||
match bank.oracle_price(&keyed_account, Some(slot)) {
|
||||
Ok(p) => price = Some(p),
|
||||
Err(e) => {
|
||||
error!("could not read bank oracle {}: {e:?}", keyed_account.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(pc) = pc_opt {
|
||||
match pc.market.oracle_price(&keyed_account, Some(slot)) {
|
||||
if let Some(perp) = perp_opt {
|
||||
match perp.oracle_price(&keyed_account, Some(slot)) {
|
||||
Ok(p) => price = Some(p),
|
||||
Err(e) => {
|
||||
error!("could not read perp oracle {}: {e:?}", keyed_account.key);
|
||||
|
|
|
@ -5,7 +5,8 @@ use itertools::Itertools;
|
|||
|
||||
use anchor_lang::{__private::bytemuck::cast_ref, solana_program};
|
||||
use futures::Future;
|
||||
use mango_v4::state::{EventQueue, EventType, FillEvent, OutEvent, PerpMarket, TokenIndex};
|
||||
use mango_v4::state::{EventQueue, EventType, FillEvent, OutEvent, TokenIndex};
|
||||
use mango_v4_client::PerpMarketContext;
|
||||
use prometheus::{register_histogram, Encoder, Histogram, IntCounter, Registry};
|
||||
use solana_sdk::{
|
||||
instruction::{AccountMeta, Instruction},
|
||||
|
@ -79,7 +80,7 @@ pub async fn runner(
|
|||
interval_update_banks: u64,
|
||||
interval_consume_events: u64,
|
||||
interval_update_funding: u64,
|
||||
interval_check_new_listings_and_abort: u64,
|
||||
interval_check_for_changes_and_abort: u64,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let handles1 = mango_client
|
||||
.context
|
||||
|
@ -106,12 +107,12 @@ pub async fn runner(
|
|||
.values()
|
||||
.filter(|perp|
|
||||
// MNGO-PERP-OLD
|
||||
perp.market.perp_market_index != 1)
|
||||
perp.perp_market_index != 1)
|
||||
.map(|perp| {
|
||||
loop_consume_events(
|
||||
mango_client.clone(),
|
||||
perp.address,
|
||||
perp.market,
|
||||
perp,
|
||||
interval_consume_events,
|
||||
)
|
||||
})
|
||||
|
@ -123,12 +124,12 @@ pub async fn runner(
|
|||
.values()
|
||||
.filter(|perp|
|
||||
// MNGO-PERP-OLD
|
||||
perp.market.perp_market_index != 1)
|
||||
perp.perp_market_index != 1)
|
||||
.map(|perp| {
|
||||
loop_update_funding(
|
||||
mango_client.clone(),
|
||||
perp.address,
|
||||
perp.market,
|
||||
perp,
|
||||
interval_update_funding,
|
||||
)
|
||||
})
|
||||
|
@ -138,9 +139,9 @@ pub async fn runner(
|
|||
futures::future::join_all(handles1),
|
||||
futures::future::join_all(handles2),
|
||||
futures::future::join_all(handles3),
|
||||
loop_check_new_listings_and_abort(
|
||||
MangoClient::loop_check_for_context_changes_and_abort(
|
||||
mango_client.clone(),
|
||||
interval_check_new_listings_and_abort
|
||||
Duration::from_secs(interval_check_for_changes_and_abort),
|
||||
),
|
||||
serve_metrics(),
|
||||
debugging_handle,
|
||||
|
@ -149,27 +150,6 @@ pub async fn runner(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn loop_check_new_listings_and_abort(mango_client: Arc<MangoClient>, interval: u64) {
|
||||
let mut interval = time::interval(Duration::from_secs(interval));
|
||||
loop {
|
||||
if mango_client
|
||||
.context
|
||||
.new_tokens_listed(&mango_client.client.rpc_async())
|
||||
.await
|
||||
.unwrap()
|
||||
|| mango_client
|
||||
.context
|
||||
.new_perp_markets_listed(&mango_client.client.rpc_async())
|
||||
.await
|
||||
.unwrap()
|
||||
{
|
||||
std::process::abort();
|
||||
}
|
||||
|
||||
interval.tick().await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn loop_update_index_and_rate(
|
||||
mango_client: Arc<MangoClient>,
|
||||
token_indices: Vec<TokenIndex>,
|
||||
|
@ -191,14 +171,14 @@ pub async fn loop_update_index_and_rate(
|
|||
let mut instructions = vec![];
|
||||
for token_index in token_indices_clone.iter() {
|
||||
let token = client.context.token(*token_index);
|
||||
let banks_for_a_token = token.mint_info.banks();
|
||||
let oracle = token.mint_info.oracle;
|
||||
let banks_for_a_token = token.banks();
|
||||
let oracle = token.oracle;
|
||||
|
||||
let mut ix = Instruction {
|
||||
program_id: mango_v4::id(),
|
||||
accounts: anchor_lang::ToAccountMetas::to_account_metas(
|
||||
&mango_v4::accounts::TokenUpdateIndexAndRate {
|
||||
group: token.mint_info.group,
|
||||
group: token.group,
|
||||
mint_info: token.mint_info_address,
|
||||
oracle,
|
||||
instructions: solana_program::sysvar::instructions::id(),
|
||||
|
@ -264,7 +244,7 @@ pub async fn loop_update_index_and_rate(
|
|||
pub async fn loop_consume_events(
|
||||
mango_client: Arc<MangoClient>,
|
||||
pk: Pubkey,
|
||||
perp_market: PerpMarket,
|
||||
perp_market: &PerpMarketContext,
|
||||
interval: u64,
|
||||
) {
|
||||
let mut interval = time::interval(Duration::from_secs(interval));
|
||||
|
@ -362,7 +342,7 @@ pub async fn loop_consume_events(
|
|||
METRIC_CONSUME_EVENTS_FAILURE.inc();
|
||||
info!(
|
||||
"metricName=ConsumeEventsV4Failure market={} durationMs={} consumed={} error={}",
|
||||
perp_market.name(),
|
||||
perp_market.name,
|
||||
confirmation_time,
|
||||
num_of_events,
|
||||
e.to_string()
|
||||
|
@ -372,9 +352,7 @@ pub async fn loop_consume_events(
|
|||
METRIC_CONSUME_EVENTS_SUCCESS.inc();
|
||||
info!(
|
||||
"metricName=ConsumeEventsV4Success market={} durationMs={} consumed={}",
|
||||
perp_market.name(),
|
||||
confirmation_time,
|
||||
num_of_events,
|
||||
perp_market.name, confirmation_time, num_of_events,
|
||||
);
|
||||
info!("{:?}", sig_result);
|
||||
}
|
||||
|
@ -384,7 +362,7 @@ pub async fn loop_consume_events(
|
|||
pub async fn loop_update_funding(
|
||||
mango_client: Arc<MangoClient>,
|
||||
pk: Pubkey,
|
||||
perp_market: PerpMarket,
|
||||
perp_market: &PerpMarketContext,
|
||||
interval: u64,
|
||||
) {
|
||||
let mut interval = time::interval(Duration::from_secs(interval));
|
||||
|
@ -417,7 +395,7 @@ pub async fn loop_update_funding(
|
|||
METRIC_UPDATE_FUNDING_FAILURE.inc();
|
||||
error!(
|
||||
"metricName=UpdateFundingV4Error market={} durationMs={} error={}",
|
||||
perp_market.name(),
|
||||
perp_market.name,
|
||||
confirmation_time,
|
||||
e.to_string()
|
||||
);
|
||||
|
@ -426,8 +404,7 @@ pub async fn loop_update_funding(
|
|||
METRIC_UPDATE_FUNDING_SUCCESS.inc();
|
||||
info!(
|
||||
"metricName=UpdateFundingV4Success market={} durationMs={}",
|
||||
perp_market.name(),
|
||||
confirmation_time,
|
||||
perp_market.name, confirmation_time,
|
||||
);
|
||||
info!("{:?}", sig_result);
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ pub async fn runner(
|
|||
|
||||
let mut price_arcs = HashMap::new();
|
||||
for s3_market in mango_client.context.serum3_markets.values() {
|
||||
let base_token_index = s3_market.market.base_token_index;
|
||||
let base_token_index = s3_market.base_token_index;
|
||||
let price = mango_client
|
||||
.bank_oracle_price(base_token_index)
|
||||
.await
|
||||
|
@ -47,11 +47,8 @@ pub async fn runner(
|
|||
.map(|s3_market| {
|
||||
loop_blocking_orders(
|
||||
mango_client.clone(),
|
||||
s3_market.market.name().to_string(),
|
||||
price_arcs
|
||||
.get(&s3_market.market.base_token_index)
|
||||
.unwrap()
|
||||
.clone(),
|
||||
s3_market.name.clone(),
|
||||
price_arcs.get(&s3_market.base_token_index).unwrap().clone(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
@ -70,7 +67,7 @@ async fn ensure_oo(mango_client: &Arc<MangoClient>) -> Result<(), anyhow::Error>
|
|||
for (market_index, serum3_market) in mango_client.context.serum3_markets.iter() {
|
||||
if account.serum3_orders(*market_index).is_err() {
|
||||
mango_client
|
||||
.serum3_create_open_orders(serum3_market.market.name())
|
||||
.serum3_create_open_orders(&serum3_market.name)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -178,7 +178,6 @@ impl<'a> LiquidateHelper<'a> {
|
|||
let max_perp_unsettled_leverage = I80F48::from_num(0.95);
|
||||
let perp_unsettled_cost = I80F48::ONE
|
||||
- perp
|
||||
.market
|
||||
.init_overall_asset_weight
|
||||
.min(max_perp_unsettled_leverage);
|
||||
let max_pnl_transfer = allowed_usdc_borrow / perp_unsettled_cost;
|
||||
|
@ -334,7 +333,7 @@ impl<'a> LiquidateHelper<'a> {
|
|||
asset_usdc_equivalent.is_positive()
|
||||
&& self
|
||||
.allowed_asset_tokens
|
||||
.contains(&self.client.context.token(*asset_token_index).mint_info.mint)
|
||||
.contains(&self.client.context.token(*asset_token_index).mint)
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
|
@ -350,7 +349,7 @@ impl<'a> LiquidateHelper<'a> {
|
|||
liab_usdc_equivalent.is_negative()
|
||||
&& self
|
||||
.allowed_liab_tokens
|
||||
.contains(&self.client.context.token(*liab_token_index).mint_info.mint)
|
||||
.contains(&self.client.context.token(*liab_token_index).mint)
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
|
@ -414,7 +413,7 @@ impl<'a> LiquidateHelper<'a> {
|
|||
liab_usdc_equivalent.is_negative()
|
||||
&& self
|
||||
.allowed_liab_tokens
|
||||
.contains(&self.client.context.token(*liab_token_index).mint_info.mint)
|
||||
.contains(&self.client.context.token(*liab_token_index).mint)
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
|
@ -569,13 +568,7 @@ pub async fn maybe_liquidate_account(
|
|||
|
||||
let maint_health = health_cache.health(HealthType::Maint);
|
||||
|
||||
let all_token_mints = HashSet::from_iter(
|
||||
mango_client
|
||||
.context
|
||||
.tokens
|
||||
.values()
|
||||
.map(|c| c.mint_info.mint),
|
||||
);
|
||||
let all_token_mints = HashSet::from_iter(mango_client.context.tokens.values().map(|c| c.mint));
|
||||
|
||||
// try liquidating
|
||||
let maybe_txsig = LiquidateHelper {
|
||||
|
|
|
@ -198,15 +198,15 @@ async fn main() -> anyhow::Result<()> {
|
|||
let mango_oracles = group_context
|
||||
.tokens
|
||||
.values()
|
||||
.map(|value| value.mint_info.oracle)
|
||||
.chain(group_context.perp_markets.values().map(|p| p.market.oracle))
|
||||
.map(|value| value.oracle)
|
||||
.chain(group_context.perp_markets.values().map(|p| p.oracle))
|
||||
.unique()
|
||||
.collect::<Vec<Pubkey>>();
|
||||
|
||||
let serum_programs = group_context
|
||||
.serum3_markets
|
||||
.values()
|
||||
.map(|s3| s3.market.serum_program)
|
||||
.map(|s3| s3.serum_program)
|
||||
.unique()
|
||||
.collect_vec();
|
||||
|
||||
|
@ -533,6 +533,12 @@ async fn main() -> anyhow::Result<()> {
|
|||
}
|
||||
});
|
||||
|
||||
let check_changes_for_abort_job =
|
||||
tokio::spawn(MangoClient::loop_check_for_context_changes_and_abort(
|
||||
mango_client.clone(),
|
||||
Duration::from_secs(300),
|
||||
));
|
||||
|
||||
if cli.telemetry == BoolArg::True {
|
||||
tokio::spawn(telemetry::report_regularly(
|
||||
mango_client,
|
||||
|
@ -546,6 +552,7 @@ async fn main() -> anyhow::Result<()> {
|
|||
rebalance_job,
|
||||
liquidation_job,
|
||||
token_swap_info_job,
|
||||
check_changes_for_abort_job,
|
||||
]
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use itertools::Itertools;
|
||||
use mango_v4::accounts_zerocopy::KeyedAccountSharedData;
|
||||
use mango_v4::state::{
|
||||
Bank, BookSide, MangoAccountValue, PerpPosition, PlaceOrderType, Side, TokenIndex,
|
||||
Bank, BookSide, MangoAccountValue, PerpMarket, PerpPosition, PlaceOrderType, Side, TokenIndex,
|
||||
QUOTE_TOKEN_INDEX,
|
||||
};
|
||||
use mango_v4_client::{
|
||||
|
@ -34,7 +34,7 @@ fn token_bank(
|
|||
token: &TokenContext,
|
||||
account_fetcher: &chain_data::AccountFetcher,
|
||||
) -> anyhow::Result<Bank> {
|
||||
account_fetcher.fetch::<Bank>(&token.mint_info.first_bank())
|
||||
account_fetcher.fetch::<Bank>(&token.first_bank())
|
||||
}
|
||||
|
||||
pub struct Rebalancer {
|
||||
|
@ -121,8 +121,8 @@ impl Rebalancer {
|
|||
.get("SOL") // TODO: better use mint
|
||||
.unwrap(),
|
||||
);
|
||||
let quote_mint = quote_token.mint_info.mint;
|
||||
let sol_mint = sol_token.mint_info.mint;
|
||||
let quote_mint = quote_token.mint;
|
||||
let sol_mint = sol_token.mint;
|
||||
let jupiter_version = self.config.jupiter_version;
|
||||
|
||||
let full_route_job = self.jupiter_quote(
|
||||
|
@ -143,7 +143,7 @@ impl Rebalancer {
|
|||
// For the SOL -> output route we need to adjust the in amount by the SOL price
|
||||
let sol_price = self
|
||||
.account_fetcher
|
||||
.fetch_bank_price(&sol_token.mint_info.first_bank())?;
|
||||
.fetch_bank_price(&sol_token.first_bank())?;
|
||||
let in_amount_sol = (I80F48::from(in_amount_quote) / sol_price)
|
||||
.ceil()
|
||||
.to_num::<u64>();
|
||||
|
@ -199,8 +199,8 @@ impl Rebalancer {
|
|||
.get("SOL") // TODO: better use mint
|
||||
.unwrap(),
|
||||
);
|
||||
let quote_mint = quote_token.mint_info.mint;
|
||||
let sol_mint = sol_token.mint_info.mint;
|
||||
let quote_mint = quote_token.mint;
|
||||
let sol_mint = sol_token.mint;
|
||||
let jupiter_version = self.config.jupiter_version;
|
||||
|
||||
let full_route_job =
|
||||
|
@ -305,10 +305,8 @@ impl Rebalancer {
|
|||
{
|
||||
continue;
|
||||
}
|
||||
let token_mint = token.mint_info.mint;
|
||||
let token_price = self
|
||||
.account_fetcher
|
||||
.fetch_bank_price(&token.mint_info.first_bank())?;
|
||||
let token_mint = token.mint;
|
||||
let token_price = self.account_fetcher.fetch_bank_price(&token.first_bank())?;
|
||||
|
||||
// It's not always possible to bring the native balance to 0 through swaps:
|
||||
// Consider a price <1. You need to sell a bunch of tokens to get 1 USDC native and
|
||||
|
@ -419,7 +417,7 @@ impl Rebalancer {
|
|||
#[instrument(
|
||||
skip_all,
|
||||
fields(
|
||||
perp_market_name = perp.market.name(),
|
||||
perp_market_name = perp.name,
|
||||
base_lots = perp_position.base_position_lots(),
|
||||
effective_lots = perp_position.effective_base_position_lots(),
|
||||
quote_native = %perp_position.quote_position_native()
|
||||
|
@ -438,28 +436,28 @@ impl Rebalancer {
|
|||
let base_lots = perp_position.base_position_lots();
|
||||
let effective_lots = perp_position.effective_base_position_lots();
|
||||
let quote_native = perp_position.quote_position_native();
|
||||
let perp_market: PerpMarket = self.account_fetcher.fetch(&perp.address)?;
|
||||
|
||||
if effective_lots != 0 {
|
||||
// send an ioc order to reduce the base position
|
||||
let oracle_account_data = self.account_fetcher.fetch_raw(&perp.market.oracle)?;
|
||||
let oracle_account =
|
||||
KeyedAccountSharedData::new(perp.market.oracle, oracle_account_data);
|
||||
let oracle_price = perp.market.oracle_price(&oracle_account, None)?;
|
||||
let oracle_price_lots = perp.market.native_price_to_lot(oracle_price);
|
||||
let oracle_account_data = self.account_fetcher.fetch_raw(&perp.oracle)?;
|
||||
let oracle_account = KeyedAccountSharedData::new(perp.oracle, oracle_account_data);
|
||||
let oracle_price = perp_market.oracle_price(&oracle_account, None)?;
|
||||
let oracle_price_lots = perp_market.native_price_to_lot(oracle_price);
|
||||
let (side, order_price, oo_lots) = if effective_lots > 0 {
|
||||
(
|
||||
Side::Ask,
|
||||
oracle_price * (I80F48::ONE - perp.market.base_liquidation_fee),
|
||||
oracle_price * (I80F48::ONE - perp_market.base_liquidation_fee),
|
||||
perp_position.asks_base_lots,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
Side::Bid,
|
||||
oracle_price * (I80F48::ONE + perp.market.base_liquidation_fee),
|
||||
oracle_price * (I80F48::ONE + perp_market.base_liquidation_fee),
|
||||
perp_position.bids_base_lots,
|
||||
)
|
||||
};
|
||||
let price_lots = perp.market.native_price_to_lot(order_price);
|
||||
let price_lots = perp_market.native_price_to_lot(order_price);
|
||||
let max_base_lots = effective_lots.abs() - oo_lots;
|
||||
if max_base_lots <= 0 {
|
||||
warn!(?side, oo_lots, "cannot place reduce-only order",);
|
||||
|
@ -470,8 +468,8 @@ impl Rebalancer {
|
|||
// even match anything. That way we don't need to pay the tx fee and
|
||||
// ioc penalty fee unnecessarily.
|
||||
let opposite_side_key = match side.invert_side() {
|
||||
Side::Bid => perp.market.bids,
|
||||
Side::Ask => perp.market.asks,
|
||||
Side::Bid => perp.bids,
|
||||
Side::Ask => perp.asks,
|
||||
};
|
||||
let bookside = Box::new(self.account_fetcher.fetch::<BookSide>(&opposite_side_key)?);
|
||||
if bookside.quantity_at_price(price_lots, now_ts, oracle_price_lots) <= 0 {
|
||||
|
|
|
@ -94,8 +94,8 @@ impl TokenSwapInfoUpdater {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
let token_mint = self.mango_client.context.mint_info(token_index).mint;
|
||||
let quote_mint = self.mango_client.context.mint_info(quote_index).mint;
|
||||
let token_mint = self.mango_client.context.token(token_index).mint;
|
||||
let quote_mint = self.mango_client.context.token(quote_index).mint;
|
||||
|
||||
// these prices are in USD, which doesn't exist on chain
|
||||
let token_price = self
|
||||
|
|
|
@ -364,7 +364,7 @@ impl Context {
|
|||
&self,
|
||||
token_index: TokenIndex,
|
||||
) -> anyhow::Result<(Bank, I80F48, Pubkey)> {
|
||||
let info = self.mango_client.context.mint_info(token_index);
|
||||
let info = self.mango_client.context.token(token_index);
|
||||
let (bank, price) = self
|
||||
.account_fetcher
|
||||
.fetch_bank_and_price(&info.first_bank())?;
|
||||
|
|
|
@ -60,10 +60,8 @@ pub fn max_swap_source_with_limits(
|
|||
mango_v4_client::health_cache::new_sync(&client.context, account_fetcher, &account)
|
||||
.expect("always ok");
|
||||
|
||||
let source_bank: Bank =
|
||||
account_fetcher.fetch(&client.context.mint_info(source).first_bank())?;
|
||||
let target_bank: Bank =
|
||||
account_fetcher.fetch(&client.context.mint_info(target).first_bank())?;
|
||||
let source_bank: Bank = account_fetcher.fetch(&client.context.token(source).first_bank())?;
|
||||
let target_bank: Bank = account_fetcher.fetch(&client.context.token(target).first_bank())?;
|
||||
|
||||
let source_price = health_cache.token_info(source).unwrap().prices.oracle;
|
||||
|
||||
|
@ -104,10 +102,8 @@ pub fn max_swap_source_ignoring_limits(
|
|||
mango_v4_client::health_cache::new_sync(&client.context, account_fetcher, &account)
|
||||
.expect("always ok");
|
||||
|
||||
let source_bank: Bank =
|
||||
account_fetcher.fetch(&client.context.mint_info(source).first_bank())?;
|
||||
let target_bank: Bank =
|
||||
account_fetcher.fetch(&client.context.mint_info(target).first_bank())?;
|
||||
let source_bank: Bank = account_fetcher.fetch(&client.context.token(source).first_bank())?;
|
||||
let target_bank: Bank = account_fetcher.fetch(&client.context.token(target).first_bank())?;
|
||||
|
||||
let source_price = health_cache.token_info(source).unwrap().prices.oracle;
|
||||
|
||||
|
|
|
@ -83,14 +83,14 @@ async fn main() -> anyhow::Result<()> {
|
|||
let perp_queue_pks: Vec<_> = group_context
|
||||
.perp_markets
|
||||
.values()
|
||||
.map(|context| (context.address, context.market.event_queue))
|
||||
.map(|context| (context.address, context.event_queue))
|
||||
.collect();
|
||||
|
||||
// fetch all serum/openbook markets to find their event queues
|
||||
let serum_market_pks: Vec<_> = group_context
|
||||
.serum3_markets
|
||||
.values()
|
||||
.map(|context| context.market.serum_market_external)
|
||||
.map(|context| context.serum_market_external)
|
||||
.collect();
|
||||
|
||||
let serum_market_ais = client
|
||||
|
|
|
@ -384,23 +384,22 @@ async fn main() -> anyhow::Result<()> {
|
|||
.perp_markets
|
||||
.values()
|
||||
.map(|context| {
|
||||
let quote_decimals = match group_context.tokens.get(&context.market.settle_token_index)
|
||||
{
|
||||
let quote_decimals = match group_context.tokens.get(&context.settle_token_index) {
|
||||
Some(token) => token.decimals,
|
||||
None => panic!("token not found for market"), // todo: default to 6 for usdc?
|
||||
};
|
||||
(
|
||||
context.address,
|
||||
MarketConfig {
|
||||
name: context.market.name().to_owned(),
|
||||
bids: context.market.bids,
|
||||
asks: context.market.asks,
|
||||
event_queue: context.market.event_queue,
|
||||
oracle: context.market.oracle,
|
||||
base_decimals: context.market.base_decimals,
|
||||
name: context.name.clone(),
|
||||
bids: context.bids,
|
||||
asks: context.asks,
|
||||
event_queue: context.event_queue,
|
||||
oracle: context.oracle,
|
||||
base_decimals: context.base_decimals,
|
||||
quote_decimals,
|
||||
base_lot_size: context.market.base_lot_size,
|
||||
quote_lot_size: context.market.quote_lot_size,
|
||||
base_lot_size: context.base_lot_size,
|
||||
quote_lot_size: context.quote_lot_size,
|
||||
},
|
||||
)
|
||||
})
|
||||
|
@ -410,18 +409,18 @@ async fn main() -> anyhow::Result<()> {
|
|||
.serum3_markets
|
||||
.values()
|
||||
.map(|context| {
|
||||
let base_decimals = match group_context.tokens.get(&context.market.base_token_index) {
|
||||
let base_decimals = match group_context.tokens.get(&context.base_token_index) {
|
||||
Some(token) => token.decimals,
|
||||
None => panic!("token not found for market"), // todo: default?
|
||||
};
|
||||
let quote_decimals = match group_context.tokens.get(&context.market.quote_token_index) {
|
||||
let quote_decimals = match group_context.tokens.get(&context.quote_token_index) {
|
||||
Some(token) => token.decimals,
|
||||
None => panic!("token not found for market"), // todo: default to 6 for usdc?
|
||||
};
|
||||
(
|
||||
context.market.serum_market_external,
|
||||
context.serum_market_external,
|
||||
MarketConfig {
|
||||
name: context.market.name().to_owned(),
|
||||
name: context.name.clone(),
|
||||
bids: context.bids,
|
||||
asks: context.asks,
|
||||
event_queue: context.event_q,
|
||||
|
@ -438,7 +437,7 @@ async fn main() -> anyhow::Result<()> {
|
|||
let perp_queue_pks: Vec<(Pubkey, Pubkey)> = group_context
|
||||
.perp_markets
|
||||
.values()
|
||||
.map(|context| (context.address, context.market.event_queue))
|
||||
.map(|context| (context.address, context.event_queue))
|
||||
.collect();
|
||||
|
||||
let _a: Vec<(String, String)> = group_context
|
||||
|
@ -446,20 +445,15 @@ async fn main() -> anyhow::Result<()> {
|
|||
.values()
|
||||
.map(|context| {
|
||||
(
|
||||
context.market.serum_market_external.to_string(),
|
||||
context.market.name().to_owned(),
|
||||
context.serum_market_external.to_string(),
|
||||
context.name.clone(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let b: Vec<(String, String)> = group_context
|
||||
.perp_markets
|
||||
.values()
|
||||
.map(|context| {
|
||||
(
|
||||
context.address.to_string(),
|
||||
context.market.name().to_owned(),
|
||||
)
|
||||
})
|
||||
.map(|context| (context.address.to_string(), context.name.clone()))
|
||||
.collect();
|
||||
let market_pubkey_strings: HashMap<String, String> = [b].concat().into_iter().collect();
|
||||
|
||||
|
|
|
@ -368,23 +368,22 @@ async fn main() -> anyhow::Result<()> {
|
|||
.perp_markets
|
||||
.values()
|
||||
.map(|context| {
|
||||
let quote_decimals = match group_context.tokens.get(&context.market.settle_token_index)
|
||||
{
|
||||
let quote_decimals = match group_context.tokens.get(&context.settle_token_index) {
|
||||
Some(token) => token.decimals,
|
||||
None => panic!("token not found for market"), // todo: default to 6 for usdc?
|
||||
};
|
||||
(
|
||||
context.address,
|
||||
MarketConfig {
|
||||
name: context.market.name().to_owned(),
|
||||
bids: context.market.bids,
|
||||
asks: context.market.asks,
|
||||
event_queue: context.market.event_queue,
|
||||
oracle: context.market.oracle,
|
||||
base_decimals: context.market.base_decimals,
|
||||
name: context.name.clone(),
|
||||
bids: context.bids,
|
||||
asks: context.asks,
|
||||
event_queue: context.event_queue,
|
||||
oracle: context.oracle,
|
||||
base_decimals: context.base_decimals,
|
||||
quote_decimals,
|
||||
base_lot_size: context.market.base_lot_size,
|
||||
quote_lot_size: context.market.quote_lot_size,
|
||||
base_lot_size: context.base_lot_size,
|
||||
quote_lot_size: context.quote_lot_size,
|
||||
},
|
||||
)
|
||||
})
|
||||
|
@ -394,18 +393,18 @@ async fn main() -> anyhow::Result<()> {
|
|||
.serum3_markets
|
||||
.values()
|
||||
.map(|context| {
|
||||
let base_decimals = match group_context.tokens.get(&context.market.base_token_index) {
|
||||
let base_decimals = match group_context.tokens.get(&context.base_token_index) {
|
||||
Some(token) => token.decimals,
|
||||
None => panic!("token not found for market"), // todo: default?
|
||||
};
|
||||
let quote_decimals = match group_context.tokens.get(&context.market.quote_token_index) {
|
||||
let quote_decimals = match group_context.tokens.get(&context.quote_token_index) {
|
||||
Some(token) => token.decimals,
|
||||
None => panic!("token not found for market"), // todo: default to 6 for usdc?
|
||||
};
|
||||
(
|
||||
context.market.serum_market_external,
|
||||
context.serum_market_external,
|
||||
MarketConfig {
|
||||
name: context.market.name().to_owned(),
|
||||
name: context.name.clone(),
|
||||
bids: context.bids,
|
||||
asks: context.asks,
|
||||
event_queue: context.event_q,
|
||||
|
|
|
@ -65,7 +65,6 @@ async fn compute_pnl(
|
|||
.perp_markets
|
||||
.get(&pp.market_index)
|
||||
.unwrap()
|
||||
.market
|
||||
.settle_token_index;
|
||||
let perp_settle_health = health_cache.perp_max_settle(settle_token_index).unwrap();
|
||||
let settleable_pnl = if pnl > 0 {
|
||||
|
|
|
@ -125,15 +125,15 @@ async fn main() -> anyhow::Result<()> {
|
|||
let mango_oracles = group_context
|
||||
.tokens
|
||||
.values()
|
||||
.map(|value| value.mint_info.oracle)
|
||||
.chain(group_context.perp_markets.values().map(|p| p.market.oracle))
|
||||
.map(|value| value.oracle)
|
||||
.chain(group_context.perp_markets.values().map(|p| p.oracle))
|
||||
.unique()
|
||||
.collect::<Vec<Pubkey>>();
|
||||
|
||||
let serum_programs = group_context
|
||||
.serum3_markets
|
||||
.values()
|
||||
.map(|s3| s3.market.serum_program)
|
||||
.map(|s3| s3.serum_program)
|
||||
.unique()
|
||||
.collect_vec();
|
||||
|
||||
|
@ -337,8 +337,19 @@ async fn main() -> anyhow::Result<()> {
|
|||
}
|
||||
});
|
||||
|
||||
let check_changes_for_abort_job =
|
||||
tokio::spawn(MangoClient::loop_check_for_context_changes_and_abort(
|
||||
mango_client.clone(),
|
||||
Duration::from_secs(300),
|
||||
));
|
||||
|
||||
use futures::StreamExt;
|
||||
let mut jobs: futures::stream::FuturesUnordered<_> = vec![data_job, settle_job, tcs_start_job]
|
||||
let mut jobs: futures::stream::FuturesUnordered<_> = vec![
|
||||
data_job,
|
||||
settle_job,
|
||||
tcs_start_job,
|
||||
check_changes_for_abort_job,
|
||||
]
|
||||
.into_iter()
|
||||
.collect();
|
||||
jobs.next().await;
|
||||
|
|
|
@ -42,7 +42,7 @@ fn perp_markets_and_prices(
|
|||
|
||||
let settle_token = mango_client.context.token(perp_market.settle_token_index);
|
||||
let settle_token_price =
|
||||
account_fetcher.fetch_bank_price(&settle_token.mint_info.first_bank())?;
|
||||
account_fetcher.fetch_bank_price(&settle_token.first_bank())?;
|
||||
|
||||
Ok((
|
||||
*market_index,
|
||||
|
|
|
@ -112,7 +112,7 @@ impl State {
|
|||
|
||||
// Clear newly created token positions, so the liqor account is mostly empty
|
||||
for token_index in startable_chunk.iter().map(|(_, _, ti)| *ti).unique() {
|
||||
let mint = mango_client.context.token(token_index).mint_info.mint;
|
||||
let mint = mango_client.context.token(token_index).mint;
|
||||
instructions.append(mango_client.token_withdraw_instructions(
|
||||
&liqor_account,
|
||||
mint,
|
||||
|
@ -166,12 +166,7 @@ impl State {
|
|||
}
|
||||
|
||||
fn oracle_for_token(&self, token_index: TokenIndex) -> anyhow::Result<I80F48> {
|
||||
let bank_pk = self
|
||||
.mango_client
|
||||
.context
|
||||
.token(token_index)
|
||||
.mint_info
|
||||
.first_bank();
|
||||
let bank_pk = self.mango_client.context.token(token_index).first_bank();
|
||||
self.account_fetcher.fetch_bank_price(&bank_pk)
|
||||
}
|
||||
|
||||
|
@ -201,7 +196,6 @@ impl State {
|
|||
.mango_client
|
||||
.context
|
||||
.token(tcs.sell_token_index)
|
||||
.mint_info
|
||||
.first_bank();
|
||||
let mut sell_bank: Bank = self.account_fetcher.fetch(&sell_bank_pk)?;
|
||||
let sell_pos = account.token_position(tcs.sell_token_index)?;
|
||||
|
|
|
@ -18,7 +18,7 @@ use itertools::Itertools;
|
|||
use mango_v4::accounts_ix::{Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side};
|
||||
use mango_v4::accounts_zerocopy::KeyedAccountSharedData;
|
||||
use mango_v4::state::{
|
||||
Bank, Group, MangoAccountValue, PerpMarketIndex, PlaceOrderType, SelfTradeBehavior,
|
||||
Bank, Group, MangoAccountValue, PerpMarket, PerpMarketIndex, PlaceOrderType, SelfTradeBehavior,
|
||||
Serum3MarketIndex, Side, TokenIndex, INSURANCE_TOKEN_INDEX,
|
||||
};
|
||||
|
||||
|
@ -295,7 +295,7 @@ impl MangoClient {
|
|||
}
|
||||
|
||||
pub async fn first_bank(&self, token_index: TokenIndex) -> anyhow::Result<Bank> {
|
||||
let bank_address = self.context.mint_info(token_index).first_bank();
|
||||
let bank_address = self.context.token(token_index).first_bank();
|
||||
account_fetcher_fetch_anchor_account(&*self.account_fetcher, &bank_address).await
|
||||
}
|
||||
|
||||
|
@ -338,7 +338,6 @@ impl MangoClient {
|
|||
) -> anyhow::Result<Signature> {
|
||||
let token = self.context.token_by_mint(&mint)?;
|
||||
let token_index = token.token_index;
|
||||
let mint_info = token.mint_info;
|
||||
|
||||
let (health_check_metas, health_cu) = self
|
||||
.derive_health_check_remaining_account_metas(vec![token_index], vec![], vec![])
|
||||
|
@ -353,13 +352,10 @@ impl MangoClient {
|
|||
group: self.group(),
|
||||
account: self.mango_account_address,
|
||||
owner: self.owner(),
|
||||
bank: mint_info.first_bank(),
|
||||
vault: mint_info.first_vault(),
|
||||
oracle: mint_info.oracle,
|
||||
token_account: get_associated_token_address(
|
||||
&self.owner(),
|
||||
&mint_info.mint,
|
||||
),
|
||||
bank: token.first_bank(),
|
||||
vault: token.first_vault(),
|
||||
oracle: token.oracle,
|
||||
token_account: get_associated_token_address(&self.owner(), &token.mint),
|
||||
token_authority: self.owner(),
|
||||
token_program: Token::id(),
|
||||
},
|
||||
|
@ -390,7 +386,6 @@ impl MangoClient {
|
|||
) -> anyhow::Result<PreparedInstructions> {
|
||||
let token = self.context.token_by_mint(&mint)?;
|
||||
let token_index = token.token_index;
|
||||
let mint_info = token.mint_info;
|
||||
|
||||
let (health_check_metas, health_cu) =
|
||||
self.context.derive_health_check_remaining_account_metas(
|
||||
|
@ -416,12 +411,12 @@ impl MangoClient {
|
|||
group: self.group(),
|
||||
account: self.mango_account_address,
|
||||
owner: self.owner(),
|
||||
bank: mint_info.first_bank(),
|
||||
vault: mint_info.first_vault(),
|
||||
oracle: mint_info.oracle,
|
||||
bank: token.first_bank(),
|
||||
vault: token.first_vault(),
|
||||
oracle: token.oracle,
|
||||
token_account: get_associated_token_address(
|
||||
&self.owner(),
|
||||
&mint_info.mint,
|
||||
&token.mint,
|
||||
),
|
||||
token_program: Token::id(),
|
||||
},
|
||||
|
@ -454,7 +449,7 @@ impl MangoClient {
|
|||
|
||||
pub async fn bank_oracle_price(&self, token_index: TokenIndex) -> anyhow::Result<I80F48> {
|
||||
let bank = self.first_bank(token_index).await?;
|
||||
let mint_info = self.context.mint_info(token_index);
|
||||
let mint_info = self.context.token(token_index);
|
||||
let oracle = self
|
||||
.account_fetcher
|
||||
.fetch_raw_account(&mint_info.oracle)
|
||||
|
@ -471,12 +466,11 @@ impl MangoClient {
|
|||
perp_market_index: PerpMarketIndex,
|
||||
) -> anyhow::Result<I80F48> {
|
||||
let perp = self.context.perp(perp_market_index);
|
||||
let oracle = self
|
||||
.account_fetcher
|
||||
.fetch_raw_account(&perp.market.oracle)
|
||||
.await?;
|
||||
let price = perp.market.oracle_price(
|
||||
&KeyedAccountSharedData::new(perp.market.oracle, oracle.into()),
|
||||
let perp_market: PerpMarket =
|
||||
account_fetcher_fetch_anchor_account(&*self.account_fetcher, &perp.address).await?;
|
||||
let oracle = self.account_fetcher.fetch_raw_account(&perp.oracle).await?;
|
||||
let price = perp_market.oracle_price(
|
||||
&KeyedAccountSharedData::new(perp.oracle, oracle.into()),
|
||||
None,
|
||||
)?;
|
||||
Ok(price)
|
||||
|
@ -510,8 +504,8 @@ impl MangoClient {
|
|||
group: self.group(),
|
||||
account: account_pubkey,
|
||||
serum_market: s3.address,
|
||||
serum_program: s3.market.serum_program,
|
||||
serum_market_external: s3.market.serum_market_external,
|
||||
serum_program: s3.serum_program,
|
||||
serum_market_external: s3.serum_market_external,
|
||||
open_orders,
|
||||
owner: self.owner(),
|
||||
payer: self.owner(),
|
||||
|
@ -558,9 +552,9 @@ impl MangoClient {
|
|||
.context
|
||||
.derive_health_check_remaining_account_metas(account, vec![], vec![], vec![])?;
|
||||
|
||||
let payer_mint_info = match side {
|
||||
Serum3Side::Bid => quote.mint_info,
|
||||
Serum3Side::Ask => base.mint_info,
|
||||
let payer_token = match side {
|
||||
Serum3Side::Bid => "e,
|
||||
Serum3Side::Ask => &base,
|
||||
};
|
||||
|
||||
let ixs = PreparedInstructions::from_single(
|
||||
|
@ -572,12 +566,12 @@ impl MangoClient {
|
|||
group: self.group(),
|
||||
account: self.mango_account_address,
|
||||
open_orders,
|
||||
payer_bank: payer_mint_info.first_bank(),
|
||||
payer_vault: payer_mint_info.first_vault(),
|
||||
payer_oracle: payer_mint_info.oracle,
|
||||
payer_bank: payer_token.first_bank(),
|
||||
payer_vault: payer_token.first_vault(),
|
||||
payer_oracle: payer_token.oracle,
|
||||
serum_market: s3.address,
|
||||
serum_program: s3.market.serum_program,
|
||||
serum_market_external: s3.market.serum_market_external,
|
||||
serum_program: s3.serum_program,
|
||||
serum_market_external: s3.serum_market_external,
|
||||
market_bids: s3.bids,
|
||||
market_asks: s3.asks,
|
||||
market_event_queue: s3.event_q,
|
||||
|
@ -660,13 +654,13 @@ impl MangoClient {
|
|||
group: self.group(),
|
||||
account: self.mango_account_address,
|
||||
open_orders,
|
||||
quote_bank: quote.mint_info.first_bank(),
|
||||
quote_vault: quote.mint_info.first_vault(),
|
||||
base_bank: base.mint_info.first_bank(),
|
||||
base_vault: base.mint_info.first_vault(),
|
||||
quote_bank: quote.first_bank(),
|
||||
quote_vault: quote.first_vault(),
|
||||
base_bank: base.first_bank(),
|
||||
base_vault: base.first_vault(),
|
||||
serum_market: s3.address,
|
||||
serum_program: s3.market.serum_program,
|
||||
serum_market_external: s3.market.serum_market_external,
|
||||
serum_program: s3.serum_program,
|
||||
serum_market_external: s3.serum_market_external,
|
||||
market_base_vault: s3.coin_vault,
|
||||
market_quote_vault: s3.pc_vault,
|
||||
market_vault_signer: s3.vault_signer,
|
||||
|
@ -674,8 +668,8 @@ impl MangoClient {
|
|||
token_program: Token::id(),
|
||||
},
|
||||
v2: mango_v4::accounts::Serum3SettleFundsV2Extra {
|
||||
quote_oracle: quote.mint_info.oracle,
|
||||
base_oracle: base.mint_info.oracle,
|
||||
quote_oracle: quote.oracle,
|
||||
base_oracle: base.oracle,
|
||||
},
|
||||
},
|
||||
None,
|
||||
|
@ -708,8 +702,8 @@ impl MangoClient {
|
|||
market_asks: s3.asks,
|
||||
market_event_queue: s3.event_q,
|
||||
serum_market: s3.address,
|
||||
serum_program: s3.market.serum_program,
|
||||
serum_market_external: s3.market.serum_market_external,
|
||||
serum_program: s3.serum_program,
|
||||
serum_market_external: s3.serum_market_external,
|
||||
owner: self.owner(),
|
||||
},
|
||||
None,
|
||||
|
@ -782,18 +776,18 @@ impl MangoClient {
|
|||
account: *liqee.0,
|
||||
open_orders: *open_orders,
|
||||
serum_market: s3.address,
|
||||
serum_program: s3.market.serum_program,
|
||||
serum_market_external: s3.market.serum_market_external,
|
||||
serum_program: s3.serum_program,
|
||||
serum_market_external: s3.serum_market_external,
|
||||
market_bids: s3.bids,
|
||||
market_asks: s3.asks,
|
||||
market_event_queue: s3.event_q,
|
||||
market_base_vault: s3.coin_vault,
|
||||
market_quote_vault: s3.pc_vault,
|
||||
market_vault_signer: s3.vault_signer,
|
||||
quote_bank: quote.mint_info.first_bank(),
|
||||
quote_vault: quote.mint_info.first_vault(),
|
||||
base_bank: base.mint_info.first_bank(),
|
||||
base_vault: base.mint_info.first_vault(),
|
||||
quote_bank: quote.first_bank(),
|
||||
quote_vault: quote.first_vault(),
|
||||
base_bank: base.first_bank(),
|
||||
base_vault: base.first_vault(),
|
||||
token_program: Token::id(),
|
||||
},
|
||||
None,
|
||||
|
@ -832,8 +826,8 @@ impl MangoClient {
|
|||
group: self.group(),
|
||||
account: self.mango_account_address,
|
||||
serum_market: s3.address,
|
||||
serum_program: s3.market.serum_program,
|
||||
serum_market_external: s3.market.serum_market_external,
|
||||
serum_program: s3.serum_program,
|
||||
serum_market_external: s3.serum_market_external,
|
||||
open_orders,
|
||||
market_bids: s3.bids,
|
||||
market_asks: s3.asks,
|
||||
|
@ -890,10 +884,10 @@ impl MangoClient {
|
|||
account: self.mango_account_address,
|
||||
owner: self.owner(),
|
||||
perp_market: perp.address,
|
||||
bids: perp.market.bids,
|
||||
asks: perp.market.asks,
|
||||
event_queue: perp.market.event_queue,
|
||||
oracle: perp.market.oracle,
|
||||
bids: perp.bids,
|
||||
asks: perp.asks,
|
||||
event_queue: perp.event_queue,
|
||||
oracle: perp.oracle,
|
||||
},
|
||||
None,
|
||||
);
|
||||
|
@ -972,8 +966,8 @@ impl MangoClient {
|
|||
account: self.mango_account_address,
|
||||
owner: self.owner(),
|
||||
perp_market: perp.address,
|
||||
bids: perp.market.bids,
|
||||
asks: perp.market.asks,
|
||||
bids: perp.bids,
|
||||
asks: perp.asks,
|
||||
},
|
||||
None,
|
||||
)
|
||||
|
@ -1030,7 +1024,7 @@ impl MangoClient {
|
|||
account_b: (&Pubkey, &MangoAccountValue),
|
||||
) -> anyhow::Result<PreparedInstructions> {
|
||||
let perp = self.context.perp(market_index);
|
||||
let settlement_token = self.context.token(perp.market.settle_token_index);
|
||||
let settlement_token = self.context.token(perp.settle_token_index);
|
||||
|
||||
let (health_remaining_ams, health_cu) = self
|
||||
.context
|
||||
|
@ -1054,9 +1048,9 @@ impl MangoClient {
|
|||
perp_market: perp.address,
|
||||
account_a: *account_a.0,
|
||||
account_b: *account_b.0,
|
||||
oracle: perp.market.oracle,
|
||||
settle_bank: settlement_token.mint_info.first_bank(),
|
||||
settle_oracle: settlement_token.mint_info.oracle,
|
||||
oracle: perp.oracle,
|
||||
settle_bank: settlement_token.first_bank(),
|
||||
settle_oracle: settlement_token.oracle,
|
||||
},
|
||||
None,
|
||||
);
|
||||
|
@ -1103,8 +1097,8 @@ impl MangoClient {
|
|||
group: self.group(),
|
||||
account: *liqee.0,
|
||||
perp_market: perp.address,
|
||||
bids: perp.market.bids,
|
||||
asks: perp.market.asks,
|
||||
bids: perp.bids,
|
||||
asks: perp.asks,
|
||||
},
|
||||
None,
|
||||
);
|
||||
|
@ -1130,7 +1124,7 @@ impl MangoClient {
|
|||
max_pnl_transfer: u64,
|
||||
) -> anyhow::Result<PreparedInstructions> {
|
||||
let perp = self.context.perp(market_index);
|
||||
let settle_token_info = self.context.token(perp.market.settle_token_index);
|
||||
let settle_token_info = self.context.token(perp.settle_token_index);
|
||||
|
||||
let (health_remaining_ams, health_cu) = self
|
||||
.derive_liquidation_health_check_remaining_account_metas(liqee.1, &[], &[])
|
||||
|
@ -1144,13 +1138,13 @@ impl MangoClient {
|
|||
&mango_v4::accounts::PerpLiqBaseOrPositivePnl {
|
||||
group: self.group(),
|
||||
perp_market: perp.address,
|
||||
oracle: perp.market.oracle,
|
||||
oracle: perp.oracle,
|
||||
liqor: self.mango_account_address,
|
||||
liqor_owner: self.owner(),
|
||||
liqee: *liqee.0,
|
||||
settle_bank: settle_token_info.mint_info.first_bank(),
|
||||
settle_vault: settle_token_info.mint_info.first_vault(),
|
||||
settle_oracle: settle_token_info.mint_info.oracle,
|
||||
settle_bank: settle_token_info.first_bank(),
|
||||
settle_vault: settle_token_info.first_vault(),
|
||||
settle_oracle: settle_token_info.oracle,
|
||||
},
|
||||
None,
|
||||
);
|
||||
|
@ -1183,7 +1177,7 @@ impl MangoClient {
|
|||
.await?;
|
||||
|
||||
let perp = self.context.perp(market_index);
|
||||
let settle_token_info = self.context.token(perp.market.settle_token_index);
|
||||
let settle_token_info = self.context.token(perp.settle_token_index);
|
||||
let insurance_token_info = self.context.token(INSURANCE_TOKEN_INDEX);
|
||||
|
||||
let (health_remaining_ams, health_cu) = self
|
||||
|
@ -1202,17 +1196,17 @@ impl MangoClient {
|
|||
&mango_v4::accounts::PerpLiqNegativePnlOrBankruptcyV2 {
|
||||
group: self.group(),
|
||||
perp_market: perp.address,
|
||||
oracle: perp.market.oracle,
|
||||
oracle: perp.oracle,
|
||||
liqor: self.mango_account_address,
|
||||
liqor_owner: self.owner(),
|
||||
liqee: *liqee.0,
|
||||
settle_bank: settle_token_info.mint_info.first_bank(),
|
||||
settle_vault: settle_token_info.mint_info.first_vault(),
|
||||
settle_oracle: settle_token_info.mint_info.oracle,
|
||||
settle_bank: settle_token_info.first_bank(),
|
||||
settle_vault: settle_token_info.first_vault(),
|
||||
settle_oracle: settle_token_info.oracle,
|
||||
insurance_vault: group.insurance_vault,
|
||||
insurance_bank: insurance_token_info.mint_info.first_bank(),
|
||||
insurance_bank_vault: insurance_token_info.mint_info.first_vault(),
|
||||
insurance_oracle: insurance_token_info.mint_info.oracle,
|
||||
insurance_bank: insurance_token_info.first_bank(),
|
||||
insurance_bank_vault: insurance_token_info.first_vault(),
|
||||
insurance_oracle: insurance_token_info.oracle,
|
||||
token_program: Token::id(),
|
||||
},
|
||||
None,
|
||||
|
@ -1289,7 +1283,6 @@ impl MangoClient {
|
|||
let liab_info = self.context.token(liab_token_index);
|
||||
|
||||
let bank_remaining_ams = liab_info
|
||||
.mint_info
|
||||
.banks()
|
||||
.iter()
|
||||
.map(|bank_pubkey| util::to_writable_account_meta(*bank_pubkey))
|
||||
|
@ -1320,7 +1313,7 @@ impl MangoClient {
|
|||
liqor: self.mango_account_address,
|
||||
liqor_owner: self.owner(),
|
||||
liab_mint_info: liab_info.mint_info_address,
|
||||
quote_vault: quote_info.mint_info.first_vault(),
|
||||
quote_vault: quote_info.first_vault(),
|
||||
insurance_vault: group.insurance_vault,
|
||||
token_program: Token::id(),
|
||||
},
|
||||
|
@ -1664,6 +1657,30 @@ impl MangoClient {
|
|||
.simulate(&self.client)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn loop_check_for_context_changes_and_abort(
|
||||
mango_client: Arc<MangoClient>,
|
||||
interval: Duration,
|
||||
) {
|
||||
let mut delay = tokio::time::interval(interval);
|
||||
let rpc_async = mango_client.client.rpc_async();
|
||||
loop {
|
||||
delay.tick().await;
|
||||
|
||||
let new_context =
|
||||
match MangoGroupContext::new_from_rpc(&rpc_async, mango_client.group()).await {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
tracing::warn!("could not fetch context to check for changes: {e:?}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if mango_client.context.changed_significantly(&new_context) {
|
||||
std::process::abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
|
|
|
@ -2,11 +2,10 @@ use std::collections::HashMap;
|
|||
|
||||
use anchor_client::ClientError;
|
||||
|
||||
use anchor_lang::__private::bytemuck::{self, Zeroable};
|
||||
use anchor_lang::__private::bytemuck;
|
||||
|
||||
use mango_v4::state::{
|
||||
Bank, Group, MangoAccountValue, MintInfo, PerpMarket, PerpMarketIndex, Serum3Market,
|
||||
Serum3MarketIndex, TokenIndex,
|
||||
Group, MangoAccountValue, PerpMarketIndex, Serum3MarketIndex, TokenIndex, MAX_BANKS,
|
||||
};
|
||||
|
||||
use fixed::types::I80F48;
|
||||
|
@ -20,26 +19,51 @@ use solana_sdk::account::Account;
|
|||
use solana_sdk::instruction::AccountMeta;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct TokenContext {
|
||||
pub group: Pubkey,
|
||||
pub token_index: TokenIndex,
|
||||
pub name: String,
|
||||
pub mint_info: MintInfo,
|
||||
pub mint: Pubkey,
|
||||
pub oracle: Pubkey,
|
||||
pub banks: [Pubkey; MAX_BANKS],
|
||||
pub vaults: [Pubkey; MAX_BANKS],
|
||||
pub fallback_oracle: Pubkey,
|
||||
pub mint_info_address: Pubkey,
|
||||
pub decimals: u8,
|
||||
/// Bank snapshot is never updated, only use static parts!
|
||||
pub bank: Bank,
|
||||
}
|
||||
|
||||
impl TokenContext {
|
||||
pub fn native_to_ui(&self, native: I80F48) -> f64 {
|
||||
(native / I80F48::from(10u64.pow(self.decimals.into()))).to_num()
|
||||
}
|
||||
|
||||
pub fn first_bank(&self) -> Pubkey {
|
||||
self.banks[0]
|
||||
}
|
||||
|
||||
pub fn first_vault(&self) -> Pubkey {
|
||||
self.vaults[0]
|
||||
}
|
||||
|
||||
pub fn banks(&self) -> &[Pubkey] {
|
||||
let n_banks = self
|
||||
.banks
|
||||
.iter()
|
||||
.position(|&b| b == Pubkey::default())
|
||||
.unwrap_or(MAX_BANKS);
|
||||
&self.banks[..n_banks]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct Serum3MarketContext {
|
||||
pub address: Pubkey,
|
||||
pub market: Serum3Market,
|
||||
pub name: String,
|
||||
pub serum_program: Pubkey,
|
||||
pub serum_market_external: Pubkey,
|
||||
pub base_token_index: TokenIndex,
|
||||
pub quote_token_index: TokenIndex,
|
||||
pub bids: Pubkey,
|
||||
pub asks: Pubkey,
|
||||
pub event_q: Pubkey,
|
||||
|
@ -51,10 +75,21 @@ pub struct Serum3MarketContext {
|
|||
pub pc_lot_size: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct PerpMarketContext {
|
||||
pub group: Pubkey,
|
||||
pub perp_market_index: PerpMarketIndex,
|
||||
pub settle_token_index: TokenIndex,
|
||||
pub address: Pubkey,
|
||||
/// PerpMarket snapshot is never updated, only use static parts!
|
||||
pub market: PerpMarket,
|
||||
pub name: String,
|
||||
pub bids: Pubkey,
|
||||
pub asks: Pubkey,
|
||||
pub event_queue: Pubkey,
|
||||
pub oracle: Pubkey,
|
||||
pub base_lot_size: i64,
|
||||
pub quote_lot_size: i64,
|
||||
pub base_decimals: u8,
|
||||
pub init_overall_asset_weight: I80F48,
|
||||
}
|
||||
|
||||
pub struct ComputeEstimates {
|
||||
|
@ -128,10 +163,6 @@ impl MangoGroupContext {
|
|||
self.token(token_index).mint_info_address
|
||||
}
|
||||
|
||||
pub fn mint_info(&self, token_index: TokenIndex) -> MintInfo {
|
||||
self.token(token_index).mint_info
|
||||
}
|
||||
|
||||
pub fn perp(&self, perp_market_index: PerpMarketIndex) -> &PerpMarketContext {
|
||||
self.perp_markets.get(&perp_market_index).unwrap()
|
||||
}
|
||||
|
@ -149,11 +180,11 @@ impl MangoGroupContext {
|
|||
}
|
||||
|
||||
pub fn serum3_base_token(&self, market_index: Serum3MarketIndex) -> &TokenContext {
|
||||
self.token(self.serum3(market_index).market.base_token_index)
|
||||
self.token(self.serum3(market_index).base_token_index)
|
||||
}
|
||||
|
||||
pub fn serum3_quote_token(&self, market_index: Serum3MarketIndex) -> &TokenContext {
|
||||
self.token(self.serum3(market_index).market.quote_token_index)
|
||||
self.token(self.serum3(market_index).quote_token_index)
|
||||
}
|
||||
|
||||
pub fn token(&self, token_index: TokenIndex) -> &TokenContext {
|
||||
|
@ -163,7 +194,7 @@ impl MangoGroupContext {
|
|||
pub fn token_by_mint(&self, mint: &Pubkey) -> anyhow::Result<&TokenContext> {
|
||||
self.tokens
|
||||
.values()
|
||||
.find(|tc| tc.mint_info.mint == *mint)
|
||||
.find(|tc| tc.mint == *mint)
|
||||
.ok_or_else(|| anyhow::anyhow!("no token for mint {}", mint))
|
||||
}
|
||||
|
||||
|
@ -192,10 +223,14 @@ impl MangoGroupContext {
|
|||
TokenContext {
|
||||
token_index: mi.token_index,
|
||||
name: String::new(),
|
||||
mint_info: *mi,
|
||||
mint_info_address: *pk,
|
||||
decimals: u8::MAX,
|
||||
bank: Bank::zeroed(),
|
||||
banks: mi.banks,
|
||||
vaults: mi.vaults,
|
||||
fallback_oracle: Pubkey::default(), // coming in v0.22
|
||||
oracle: mi.oracle,
|
||||
group: mi.group,
|
||||
mint: mi.mint,
|
||||
},
|
||||
)
|
||||
})
|
||||
|
@ -209,7 +244,6 @@ impl MangoGroupContext {
|
|||
let token = tokens.get_mut(&bank.token_index).unwrap();
|
||||
token.name = bank.name().into();
|
||||
token.decimals = bank.mint_decimals;
|
||||
token.bank = bank.clone();
|
||||
}
|
||||
assert!(tokens.values().all(|t| t.decimals != u8::MAX));
|
||||
|
||||
|
@ -237,7 +271,11 @@ impl MangoGroupContext {
|
|||
s.market_index,
|
||||
Serum3MarketContext {
|
||||
address: *pk,
|
||||
market: *s,
|
||||
base_token_index: s.base_token_index,
|
||||
quote_token_index: s.quote_token_index,
|
||||
name: s.name().to_string(),
|
||||
serum_program: s.serum_program,
|
||||
serum_market_external: s.serum_market_external,
|
||||
bids: from_serum_style_pubkey(market_external.bids),
|
||||
asks: from_serum_style_pubkey(market_external.asks),
|
||||
event_q: from_serum_style_pubkey(market_external.event_q),
|
||||
|
@ -261,7 +299,18 @@ impl MangoGroupContext {
|
|||
pm.perp_market_index,
|
||||
PerpMarketContext {
|
||||
address: *pk,
|
||||
market: *pm,
|
||||
group: pm.group,
|
||||
oracle: pm.oracle,
|
||||
perp_market_index: pm.perp_market_index,
|
||||
settle_token_index: pm.settle_token_index,
|
||||
asks: pm.asks,
|
||||
bids: pm.bids,
|
||||
event_queue: pm.event_queue,
|
||||
base_decimals: pm.base_decimals,
|
||||
base_lot_size: pm.base_lot_size,
|
||||
quote_lot_size: pm.quote_lot_size,
|
||||
init_overall_asset_weight: pm.init_overall_asset_weight,
|
||||
name: pm.name().to_string(),
|
||||
},
|
||||
)
|
||||
})
|
||||
|
@ -274,11 +323,11 @@ impl MangoGroupContext {
|
|||
.collect::<HashMap<_, _>>();
|
||||
let serum3_market_indexes_by_name = serum3_markets
|
||||
.iter()
|
||||
.map(|(i, s)| (s.market.name().to_string(), *i))
|
||||
.map(|(i, s)| (s.name.clone(), *i))
|
||||
.collect::<HashMap<_, _>>();
|
||||
let perp_market_indexes_by_name = perp_markets
|
||||
.iter()
|
||||
.map(|(i, p)| (p.market.name().to_string(), *i))
|
||||
.map(|(i, p)| (p.name.clone(), *i))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let group_data = fetch_anchor_account::<Group>(rpc, &group).await?;
|
||||
|
@ -314,10 +363,7 @@ impl MangoGroupContext {
|
|||
account.ensure_token_position(*affected_token_index)?;
|
||||
}
|
||||
for affected_perp_market_index in affected_perp_markets {
|
||||
let settle_token_index = self
|
||||
.perp(affected_perp_market_index)
|
||||
.market
|
||||
.settle_token_index;
|
||||
let settle_token_index = self.perp(affected_perp_market_index).settle_token_index;
|
||||
account.ensure_perp_position(affected_perp_market_index, settle_token_index)?;
|
||||
}
|
||||
|
||||
|
@ -325,12 +371,12 @@ impl MangoGroupContext {
|
|||
let mut banks = vec![];
|
||||
let mut oracles = vec![];
|
||||
for position in account.active_token_positions() {
|
||||
let mint_info = self.mint_info(position.token_index);
|
||||
let token = self.token(position.token_index);
|
||||
banks.push((
|
||||
mint_info.first_bank(),
|
||||
token.first_bank(),
|
||||
writable_banks.iter().any(|&ti| ti == position.token_index),
|
||||
));
|
||||
oracles.push(mint_info.oracle);
|
||||
oracles.push(token.oracle);
|
||||
}
|
||||
|
||||
let serum_oos = account.active_serum3_orders().map(|&s| s.open_orders);
|
||||
|
@ -339,7 +385,7 @@ impl MangoGroupContext {
|
|||
.map(|&pa| self.perp_market_address(pa.market_index));
|
||||
let perp_oracles = account
|
||||
.active_perp_positions()
|
||||
.map(|&pa| self.perp(pa.market_index).market.oracle);
|
||||
.map(|&pa| self.perp(pa.market_index).oracle);
|
||||
|
||||
let to_account_meta = |pubkey| AccountMeta {
|
||||
pubkey,
|
||||
|
@ -384,10 +430,10 @@ impl MangoGroupContext {
|
|||
.unique();
|
||||
|
||||
for token_index in token_indexes {
|
||||
let mint_info = self.mint_info(token_index);
|
||||
let token = self.token(token_index);
|
||||
let writable_bank = writable_banks.iter().contains(&token_index);
|
||||
banks.push((mint_info.first_bank(), writable_bank));
|
||||
oracles.push(mint_info.oracle);
|
||||
banks.push((token.first_bank(), writable_bank));
|
||||
oracles.push(token.oracle);
|
||||
}
|
||||
|
||||
let serum_oos = account2
|
||||
|
@ -405,7 +451,7 @@ impl MangoGroupContext {
|
|||
.map(|&index| self.perp_market_address(index));
|
||||
let perp_oracles = perp_market_indexes
|
||||
.iter()
|
||||
.map(|&index| self.perp(index).market.oracle);
|
||||
.map(|&index| self.perp(index).oracle);
|
||||
|
||||
let to_account_meta = |pubkey| AccountMeta {
|
||||
pubkey,
|
||||
|
@ -453,6 +499,47 @@ impl MangoGroupContext {
|
|||
Ok((accounts, cu))
|
||||
}
|
||||
|
||||
/// Returns true if the on-chain context changed significantly, this currently means:
|
||||
/// - new listings (token, serum, perp)
|
||||
/// - oracle pubkey or config changes
|
||||
/// - other config changes visible through the context
|
||||
/// This is done because those would affect the pubkeys the websocket streams need to listen to,
|
||||
/// or change limits, oracle staleness or other relevant configuration.
|
||||
pub fn changed_significantly(&self, other: &Self) -> bool {
|
||||
if other.tokens.len() != self.tokens.len() {
|
||||
return true;
|
||||
}
|
||||
for (&ti, old) in self.tokens.iter() {
|
||||
if old != other.token(ti) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if other.serum3_markets.len() != self.serum3_markets.len() {
|
||||
return true;
|
||||
}
|
||||
for (&mi, old) in self.serum3_markets.iter() {
|
||||
if old != other.serum3(mi) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if other.perp_markets.len() != self.perp_markets.len() {
|
||||
return true;
|
||||
}
|
||||
for (&pi, old) in self.perp_markets.iter() {
|
||||
if old != other.perp(pi) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if other.address_lookup_tables != self.address_lookup_tables {
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub async fn new_tokens_listed(&self, rpc: &RpcClientAsync) -> anyhow::Result<bool> {
|
||||
let mint_infos = fetch_mint_infos(rpc, mango_v4::id(), self.group).await?;
|
||||
Ok(mint_infos.len() > self.tokens.len())
|
||||
|
|
|
@ -211,25 +211,19 @@ impl<'a> JupiterV4<'a> {
|
|||
.position(|ix| !is_setup_ix(ix.program_id))
|
||||
.unwrap();
|
||||
|
||||
let bank_ams = [
|
||||
source_token.mint_info.first_bank(),
|
||||
target_token.mint_info.first_bank(),
|
||||
]
|
||||
let bank_ams = [source_token.first_bank(), target_token.first_bank()]
|
||||
.into_iter()
|
||||
.map(util::to_writable_account_meta)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let vault_ams = [
|
||||
source_token.mint_info.first_vault(),
|
||||
target_token.mint_info.first_vault(),
|
||||
]
|
||||
let vault_ams = [source_token.first_vault(), target_token.first_vault()]
|
||||
.into_iter()
|
||||
.map(util::to_writable_account_meta)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let owner = self.mango_client.owner();
|
||||
|
||||
let token_ams = [source_token.mint_info.mint, target_token.mint_info.mint]
|
||||
let token_ams = [source_token.mint, target_token.mint]
|
||||
.into_iter()
|
||||
.map(|mint| {
|
||||
util::to_writable_account_meta(
|
||||
|
@ -270,7 +264,7 @@ impl<'a> JupiterV4<'a> {
|
|||
spl_associated_token_account::instruction::create_associated_token_account_idempotent(
|
||||
&owner,
|
||||
&owner,
|
||||
&source_token.mint_info.mint,
|
||||
&source_token.mint,
|
||||
&Token::id(),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -220,25 +220,19 @@ impl<'a> JupiterV6<'a> {
|
|||
let source_token = self.mango_client.context.token_by_mint(&input_mint)?;
|
||||
let target_token = self.mango_client.context.token_by_mint(&output_mint)?;
|
||||
|
||||
let bank_ams = [
|
||||
source_token.mint_info.first_bank(),
|
||||
target_token.mint_info.first_bank(),
|
||||
]
|
||||
let bank_ams = [source_token.first_bank(), target_token.first_bank()]
|
||||
.into_iter()
|
||||
.map(util::to_writable_account_meta)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let vault_ams = [
|
||||
source_token.mint_info.first_vault(),
|
||||
target_token.mint_info.first_vault(),
|
||||
]
|
||||
let vault_ams = [source_token.first_vault(), target_token.first_vault()]
|
||||
.into_iter()
|
||||
.map(util::to_writable_account_meta)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let owner = self.mango_client.owner();
|
||||
|
||||
let token_ams = [source_token.mint_info.mint, target_token.mint_info.mint]
|
||||
let token_ams = [source_token.mint, target_token.mint]
|
||||
.into_iter()
|
||||
.map(|mint| {
|
||||
util::to_writable_account_meta(
|
||||
|
@ -303,7 +297,7 @@ impl<'a> JupiterV6<'a> {
|
|||
spl_associated_token_account::instruction::create_associated_token_account_idempotent(
|
||||
&owner,
|
||||
&owner,
|
||||
&source_token.mint_info.mint,
|
||||
&source_token.mint,
|
||||
&Token::id(),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -10,7 +10,7 @@ mod chain_data_fetcher;
|
|||
mod client;
|
||||
mod context;
|
||||
pub mod error_tracking;
|
||||
mod gpa;
|
||||
pub mod gpa;
|
||||
pub mod health_cache;
|
||||
pub mod jupiter;
|
||||
pub mod perp_pnl;
|
||||
|
|
Loading…
Reference in New Issue