Rust client: auto close spot account if needed to place a new order (#877)
rust client: - add serum3 place order to command - add serum3 create open orders command - add serum3 close open orders command - auto create serum3 open orders if needed when placing a new order - auto close serum3 slot if needed when placing a new order & also close unused token if needed to place a serum3 order
This commit is contained in:
parent
34d86ef0f4
commit
5d29eb2f0b
|
@ -1,5 +1,6 @@
|
|||
use clap::clap_derive::ArgEnum;
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
use mango_v4::accounts_ix::{Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side};
|
||||
use mango_v4::state::{PlaceOrderType, SelfTradeBehavior, Side};
|
||||
use mango_v4_client::{
|
||||
keypair_from_cli, pubkey_from_cli, Client, MangoClient, TransactionBuilderConfig,
|
||||
|
@ -126,6 +127,63 @@ struct PerpPlaceOrder {
|
|||
rpc: Rpc,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
struct Serum3CreateOpenOrders {
|
||||
#[clap(long)]
|
||||
account: String,
|
||||
|
||||
/// also pays for everything
|
||||
#[clap(short, long)]
|
||||
owner: String,
|
||||
|
||||
#[clap(long)]
|
||||
market_name: String,
|
||||
|
||||
#[clap(flatten)]
|
||||
rpc: Rpc,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
struct Serum3CloseOpenOrders {
|
||||
#[clap(long)]
|
||||
account: String,
|
||||
|
||||
/// also pays for everything
|
||||
#[clap(short, long)]
|
||||
owner: String,
|
||||
|
||||
#[clap(long)]
|
||||
market_name: String,
|
||||
|
||||
#[clap(flatten)]
|
||||
rpc: Rpc,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
struct Serum3PlaceOrder {
|
||||
#[clap(long)]
|
||||
account: String,
|
||||
|
||||
/// also pays for everything
|
||||
#[clap(short, long)]
|
||||
owner: String,
|
||||
|
||||
#[clap(long)]
|
||||
market_name: String,
|
||||
|
||||
#[clap(long, value_enum)]
|
||||
side: CliSide,
|
||||
|
||||
#[clap(short, long)]
|
||||
price: f64,
|
||||
|
||||
#[clap(long)]
|
||||
quantity: f64,
|
||||
|
||||
#[clap(flatten)]
|
||||
rpc: Rpc,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug, Clone)]
|
||||
enum Command {
|
||||
CreateAccount(CreateAccount),
|
||||
|
@ -167,6 +225,9 @@ enum Command {
|
|||
output: String,
|
||||
},
|
||||
PerpPlaceOrder(PerpPlaceOrder),
|
||||
Serum3CloseOpenOrders(Serum3CloseOpenOrders),
|
||||
Serum3CreateOpenOrders(Serum3CreateOpenOrders),
|
||||
Serum3PlaceOrder(Serum3PlaceOrder),
|
||||
}
|
||||
|
||||
impl Rpc {
|
||||
|
@ -326,6 +387,65 @@ async fn main() -> Result<(), anyhow::Error> {
|
|||
.await?;
|
||||
println!("{}", txsig);
|
||||
}
|
||||
Command::Serum3CreateOpenOrders(cmd) => {
|
||||
let client = cmd.rpc.client(Some(&cmd.owner))?;
|
||||
let account = pubkey_from_cli(&cmd.account);
|
||||
let owner = Arc::new(keypair_from_cli(&cmd.owner));
|
||||
let client = MangoClient::new_for_existing_account(client, account, owner).await?;
|
||||
|
||||
let txsig = client.serum3_create_open_orders(&cmd.market_name).await?;
|
||||
println!("{}", txsig);
|
||||
}
|
||||
Command::Serum3CloseOpenOrders(cmd) => {
|
||||
let client = cmd.rpc.client(Some(&cmd.owner))?;
|
||||
let account = pubkey_from_cli(&cmd.account);
|
||||
let owner = Arc::new(keypair_from_cli(&cmd.owner));
|
||||
let client = MangoClient::new_for_existing_account(client, account, owner).await?;
|
||||
|
||||
let txsig = client.serum3_close_open_orders(&cmd.market_name).await?;
|
||||
println!("{}", txsig);
|
||||
}
|
||||
Command::Serum3PlaceOrder(cmd) => {
|
||||
let client = cmd.rpc.client(Some(&cmd.owner))?;
|
||||
let account = pubkey_from_cli(&cmd.account);
|
||||
let owner = Arc::new(keypair_from_cli(&cmd.owner));
|
||||
let client = MangoClient::new_for_existing_account(client, account, owner).await?;
|
||||
let market_index = client.context.serum3_market_index(&cmd.market_name);
|
||||
let market = client.context.serum3(market_index);
|
||||
let base_token = client.context.token(market.base_token_index);
|
||||
let quote_token = client.context.token(market.quote_token_index);
|
||||
|
||||
fn native(x: f64, b: u32) -> u64 {
|
||||
(x * (10_i64.pow(b)) as f64) as u64
|
||||
}
|
||||
|
||||
// coin_lot_size = base lot size ?
|
||||
// cf priceNumberToLots
|
||||
let price_lots = native(cmd.price, quote_token.decimals as u32) * market.coin_lot_size
|
||||
/ (native(1.0, base_token.decimals as u32) * market.pc_lot_size);
|
||||
|
||||
// cf baseSizeNumberToLots
|
||||
let max_base_lots =
|
||||
native(cmd.quantity, base_token.decimals as u32) / market.coin_lot_size;
|
||||
|
||||
let txsig = client
|
||||
.serum3_place_order(
|
||||
&cmd.market_name,
|
||||
match cmd.side {
|
||||
CliSide::Bid => Serum3Side::Bid,
|
||||
CliSide::Ask => Serum3Side::Ask,
|
||||
},
|
||||
price_lots,
|
||||
max_base_lots as u64,
|
||||
((price_lots * max_base_lots) as f64 * 1.01) as u64,
|
||||
Serum3SelfTradeBehavior::AbortTransaction,
|
||||
Serum3OrderType::Limit,
|
||||
SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(),
|
||||
10,
|
||||
)
|
||||
.await?;
|
||||
println!("{}", txsig);
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use anchor_client::ClientError::AnchorError;
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
@ -126,6 +127,7 @@ impl ClientBuilder {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Client {
|
||||
config: ClientConfig,
|
||||
rpc_async: RpcClientAsync,
|
||||
|
@ -577,40 +579,40 @@ impl MangoClient {
|
|||
|
||||
let ixs = PreparedInstructions::from_vec(
|
||||
vec![
|
||||
spl_associated_token_account::instruction::create_associated_token_account_idempotent(
|
||||
&self.owner(),
|
||||
&self.owner(),
|
||||
&mint,
|
||||
&Token::id(),
|
||||
),
|
||||
Instruction {
|
||||
program_id: mango_v4::id(),
|
||||
accounts: {
|
||||
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
|
||||
&mango_v4::accounts::TokenWithdraw {
|
||||
group: self.group(),
|
||||
account: self.mango_account_address,
|
||||
owner: self.owner(),
|
||||
bank: token.first_bank(),
|
||||
vault: token.first_vault(),
|
||||
oracle: token.oracle,
|
||||
token_account: get_associated_token_address(
|
||||
&self.owner(),
|
||||
&token.mint,
|
||||
),
|
||||
token_program: Token::id(),
|
||||
},
|
||||
None,
|
||||
);
|
||||
ams.extend(health_check_metas.into_iter());
|
||||
ams
|
||||
spl_associated_token_account::instruction::create_associated_token_account_idempotent(
|
||||
&self.owner(),
|
||||
&self.owner(),
|
||||
&mint,
|
||||
&Token::id(),
|
||||
),
|
||||
Instruction {
|
||||
program_id: mango_v4::id(),
|
||||
accounts: {
|
||||
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
|
||||
&mango_v4::accounts::TokenWithdraw {
|
||||
group: self.group(),
|
||||
account: self.mango_account_address,
|
||||
owner: self.owner(),
|
||||
bank: token.first_bank(),
|
||||
vault: token.first_vault(),
|
||||
oracle: token.oracle,
|
||||
token_account: get_associated_token_address(
|
||||
&self.owner(),
|
||||
&token.mint,
|
||||
),
|
||||
token_program: Token::id(),
|
||||
},
|
||||
None,
|
||||
);
|
||||
ams.extend(health_check_metas.into_iter());
|
||||
ams
|
||||
},
|
||||
data: anchor_lang::InstructionData::data(&mango_v4::instruction::TokenWithdraw {
|
||||
amount,
|
||||
allow_borrow,
|
||||
}),
|
||||
},
|
||||
data: anchor_lang::InstructionData::data(&mango_v4::instruction::TokenWithdraw {
|
||||
amount,
|
||||
allow_borrow,
|
||||
}),
|
||||
},
|
||||
],
|
||||
],
|
||||
self.instruction_cu(health_cu),
|
||||
);
|
||||
Ok(ixs)
|
||||
|
@ -658,6 +660,45 @@ impl MangoClient {
|
|||
// Serum3
|
||||
//
|
||||
|
||||
pub fn serum3_close_open_orders_instruction(
|
||||
&self,
|
||||
market_index: Serum3MarketIndex,
|
||||
) -> PreparedInstructions {
|
||||
let account_pubkey = self.mango_account_address;
|
||||
let s3 = self.context.serum3(market_index);
|
||||
|
||||
let open_orders = self.serum3_create_open_orders_address(market_index);
|
||||
|
||||
PreparedInstructions::from_single(
|
||||
Instruction {
|
||||
program_id: mango_v4::id(),
|
||||
accounts: anchor_lang::ToAccountMetas::to_account_metas(
|
||||
&mango_v4::accounts::Serum3CloseOpenOrders {
|
||||
group: self.group(),
|
||||
account: account_pubkey,
|
||||
serum_market: s3.address,
|
||||
serum_program: s3.serum_program,
|
||||
serum_market_external: s3.serum_market_external,
|
||||
open_orders,
|
||||
owner: self.owner(),
|
||||
sol_destination: self.owner(),
|
||||
},
|
||||
None,
|
||||
),
|
||||
data: anchor_lang::InstructionData::data(
|
||||
&mango_v4::instruction::Serum3CloseOpenOrders {},
|
||||
),
|
||||
},
|
||||
self.context.compute_estimates.cu_per_mango_instruction,
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn serum3_close_open_orders(&self, name: &str) -> anyhow::Result<Signature> {
|
||||
let market_index = self.context.serum3_market_index(name);
|
||||
let ix = self.serum3_close_open_orders_instruction(market_index);
|
||||
self.send_and_confirm_owner_tx(ix.to_instructions()).await
|
||||
}
|
||||
|
||||
pub fn serum3_create_open_orders_instruction(
|
||||
&self,
|
||||
market_index: Serum3MarketIndex,
|
||||
|
@ -665,15 +706,7 @@ impl MangoClient {
|
|||
let account_pubkey = self.mango_account_address;
|
||||
let s3 = self.context.serum3(market_index);
|
||||
|
||||
let open_orders = Pubkey::find_program_address(
|
||||
&[
|
||||
b"Serum3OO".as_ref(),
|
||||
account_pubkey.as_ref(),
|
||||
s3.address.as_ref(),
|
||||
],
|
||||
&mango_v4::ID,
|
||||
)
|
||||
.0;
|
||||
let open_orders = self.serum3_create_open_orders_address(market_index);
|
||||
|
||||
Instruction {
|
||||
program_id: mango_v4::id(),
|
||||
|
@ -698,6 +731,23 @@ impl MangoClient {
|
|||
}
|
||||
}
|
||||
|
||||
fn serum3_create_open_orders_address(&self, market_index: Serum3MarketIndex) -> Pubkey {
|
||||
let account_pubkey = self.mango_account_address;
|
||||
let s3 = self.context.serum3(market_index);
|
||||
|
||||
let open_orders = Pubkey::find_program_address(
|
||||
&[
|
||||
b"Serum3OO".as_ref(),
|
||||
account_pubkey.as_ref(),
|
||||
s3.address.as_ref(),
|
||||
],
|
||||
&mango_v4::ID,
|
||||
)
|
||||
.0;
|
||||
|
||||
open_orders
|
||||
}
|
||||
|
||||
pub async fn serum3_create_open_orders(&self, name: &str) -> anyhow::Result<Signature> {
|
||||
let market_index = self.context.serum3_market_index(name);
|
||||
let ix = self.serum3_create_open_orders_instruction(market_index);
|
||||
|
@ -721,20 +771,22 @@ impl MangoClient {
|
|||
let s3 = self.context.serum3(market_index);
|
||||
let base = self.context.serum3_base_token(market_index);
|
||||
let quote = self.context.serum3_quote_token(market_index);
|
||||
let open_orders = account
|
||||
.serum3_orders(market_index)
|
||||
.expect("oo is created")
|
||||
.open_orders;
|
||||
let (payer_token, receiver_token) = match side {
|
||||
Serum3Side::Bid => ("e, &base),
|
||||
Serum3Side::Ask => (&base, "e),
|
||||
};
|
||||
|
||||
let open_orders = account.serum3_orders(market_index).map(|x| x.open_orders)?;
|
||||
|
||||
let (health_check_metas, health_cu) = self
|
||||
.derive_health_check_remaining_account_metas(account, vec![], vec![], vec![])
|
||||
.derive_health_check_remaining_account_metas(
|
||||
&account,
|
||||
vec![],
|
||||
vec![receiver_token.token_index],
|
||||
vec![],
|
||||
)
|
||||
.await?;
|
||||
|
||||
let payer_token = match side {
|
||||
Serum3Side::Bid => "e,
|
||||
Serum3Side::Ask => &base,
|
||||
};
|
||||
|
||||
let ixs = PreparedInstructions::from_single(
|
||||
Instruction {
|
||||
program_id: mango_v4::id(),
|
||||
|
@ -766,7 +818,7 @@ impl MangoClient {
|
|||
ams
|
||||
},
|
||||
data: anchor_lang::InstructionData::data(
|
||||
&mango_v4::instruction::Serum3PlaceOrder {
|
||||
&mango_v4::instruction::Serum3PlaceOrderV2 {
|
||||
side,
|
||||
limit_price,
|
||||
max_base_qty,
|
||||
|
@ -785,6 +837,205 @@ impl MangoClient {
|
|||
Ok(ixs)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn serum3_create_or_replace_account_instruction(
|
||||
&self,
|
||||
mut account: &mut MangoAccountValue,
|
||||
market_index: Serum3MarketIndex,
|
||||
side: Serum3Side,
|
||||
) -> anyhow::Result<PreparedInstructions> {
|
||||
let mut ixs = PreparedInstructions::new();
|
||||
|
||||
let base = self.context.serum3_base_token(market_index);
|
||||
let quote = self.context.serum3_quote_token(market_index);
|
||||
let (payer_token, receiver_token) = match side {
|
||||
Serum3Side::Bid => ("e, &base),
|
||||
Serum3Side::Ask => (&base, "e),
|
||||
};
|
||||
|
||||
let open_orders_opt = account
|
||||
.serum3_orders(market_index)
|
||||
.map(|x| x.open_orders)
|
||||
.ok();
|
||||
|
||||
let mut missing_tokens = false;
|
||||
|
||||
let token_replace_ixs = self
|
||||
.find_existing_or_try_to_replace_token_positions(
|
||||
&mut account,
|
||||
&[payer_token.token_index, receiver_token.token_index],
|
||||
)
|
||||
.await;
|
||||
match token_replace_ixs {
|
||||
Ok(res) => {
|
||||
ixs.append(res);
|
||||
}
|
||||
Err(_) => missing_tokens = true,
|
||||
}
|
||||
|
||||
if open_orders_opt.is_none() {
|
||||
let has_available_slot = account.all_serum3_orders().any(|p| !p.is_active());
|
||||
let should_close_one_open_orders_account = !has_available_slot || missing_tokens;
|
||||
|
||||
if should_close_one_open_orders_account {
|
||||
ixs.append(
|
||||
self.deactivate_first_active_unused_serum3_orders(&mut account)
|
||||
.await?,
|
||||
);
|
||||
}
|
||||
|
||||
// in case of missing token slots
|
||||
// try again to create, as maybe deactivating the market slot resulted in some token being now unused
|
||||
// but this time, in case of error, propagate to caller
|
||||
if missing_tokens {
|
||||
ixs.append(
|
||||
self.find_existing_or_try_to_replace_token_positions(
|
||||
&mut account,
|
||||
&[payer_token.token_index, receiver_token.token_index],
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
}
|
||||
|
||||
ixs.push(
|
||||
self.serum3_create_open_orders_instruction(market_index),
|
||||
self.context.compute_estimates.cu_per_mango_instruction,
|
||||
);
|
||||
|
||||
let created_open_orders = self.serum3_create_open_orders_address(market_index);
|
||||
|
||||
account.create_serum3_orders(market_index)?.open_orders = created_open_orders;
|
||||
}
|
||||
|
||||
Ok(ixs)
|
||||
}
|
||||
|
||||
async fn deactivate_first_active_unused_serum3_orders(
|
||||
&self,
|
||||
account: &mut MangoAccountValue,
|
||||
) -> anyhow::Result<PreparedInstructions> {
|
||||
let mut serum3_closable_order_market_index = None;
|
||||
|
||||
for p in account.all_serum3_orders() {
|
||||
let open_orders_acc = self
|
||||
.account_fetcher
|
||||
.fetch_raw_account(&p.open_orders)
|
||||
.await?;
|
||||
let open_orders_bytes = open_orders_acc.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 is_closable = open_orders_data.free_slot_bits == u128::MAX
|
||||
&& open_orders_data.native_coin_total == 0
|
||||
&& open_orders_data.native_pc_total == 0;
|
||||
|
||||
if is_closable {
|
||||
serum3_closable_order_market_index = Some(p.market_index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let first_closable_slot =
|
||||
serum3_closable_order_market_index.expect("couldn't find any serum3 slot available");
|
||||
|
||||
let ixs = self.serum3_close_open_orders_instruction(first_closable_slot);
|
||||
|
||||
let first_closable_market = account.serum3_orders(first_closable_slot)?;
|
||||
let (tk1, tk2) = (
|
||||
first_closable_market.base_token_index,
|
||||
first_closable_market.quote_token_index,
|
||||
);
|
||||
account.token_position_mut(tk1)?.0.decrement_in_use();
|
||||
account.token_position_mut(tk2)?.0.decrement_in_use();
|
||||
account.deactivate_serum3_orders(first_closable_slot)?;
|
||||
|
||||
Ok(ixs)
|
||||
}
|
||||
|
||||
async fn find_existing_or_try_to_replace_token_positions(
|
||||
&self,
|
||||
account: &mut MangoAccountValue,
|
||||
token_indexes: &[TokenIndex],
|
||||
) -> anyhow::Result<PreparedInstructions> {
|
||||
let mut ixs = PreparedInstructions::new();
|
||||
|
||||
for token_index in token_indexes {
|
||||
let result = self
|
||||
.find_existing_or_try_to_replace_token_position(account, *token_index)
|
||||
.await?;
|
||||
if let Some(ix) = result {
|
||||
ixs.append(ix);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ixs)
|
||||
}
|
||||
|
||||
async fn find_existing_or_try_to_replace_token_position(
|
||||
&self,
|
||||
account: &mut MangoAccountValue,
|
||||
token_index: TokenIndex,
|
||||
) -> anyhow::Result<Option<PreparedInstructions>> {
|
||||
let token_position_missing = account
|
||||
.ensure_token_position(token_index)
|
||||
.is_anchor_error_with_code(MangoError::NoFreeTokenPositionIndex.error_code());
|
||||
|
||||
if !token_position_missing {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let ixs = self.deactivate_first_active_unused_token(account).await?;
|
||||
account.ensure_token_position(token_index)?;
|
||||
|
||||
Ok(Some(ixs))
|
||||
}
|
||||
|
||||
async fn deactivate_first_active_unused_token(
|
||||
&self,
|
||||
account: &mut MangoAccountValue,
|
||||
) -> anyhow::Result<PreparedInstructions> {
|
||||
let closable_tokens = account
|
||||
.all_token_positions()
|
||||
.enumerate()
|
||||
.filter(|(_, p)| p.is_active() && !p.is_in_use());
|
||||
|
||||
let mut closable_token_position_raw_index_opt = None;
|
||||
let mut closable_token_bank_opt = None;
|
||||
|
||||
for (closable_token_position_raw_index, closable_token_position) in closable_tokens {
|
||||
let bank = self.first_bank(closable_token_position.token_index).await?;
|
||||
let native_balance = closable_token_position.native(&bank);
|
||||
|
||||
if native_balance < I80F48::ZERO {
|
||||
continue;
|
||||
}
|
||||
if native_balance > I80F48::ONE {
|
||||
continue;
|
||||
}
|
||||
|
||||
closable_token_position_raw_index_opt = Some(closable_token_position_raw_index);
|
||||
closable_token_bank_opt = Some(bank);
|
||||
break;
|
||||
}
|
||||
|
||||
if closable_token_bank_opt.is_none() {
|
||||
return Err(AnchorError(MangoError::NoFreeTokenPositionIndex.into()).into());
|
||||
}
|
||||
|
||||
let withdraw_ixs = self
|
||||
.token_withdraw_instructions(
|
||||
&account,
|
||||
closable_token_bank_opt.unwrap().mint,
|
||||
u64::MAX,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
account.deactivate_token_position(closable_token_position_raw_index_opt.unwrap());
|
||||
return Ok(withdraw_ixs);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn serum3_place_order(
|
||||
&self,
|
||||
|
@ -798,9 +1049,12 @@ impl MangoClient {
|
|||
client_order_id: u64,
|
||||
limit: u16,
|
||||
) -> anyhow::Result<Signature> {
|
||||
let account = self.mango_account().await?;
|
||||
let mut account = self.mango_account().await?.clone();
|
||||
let market_index = self.context.serum3_market_index(name);
|
||||
let ixs = self
|
||||
let create_or_replace_ixs = self
|
||||
.serum3_create_or_replace_account_instruction(&mut account, market_index, side)
|
||||
.await?;
|
||||
let place_order_ixs = self
|
||||
.serum3_place_order_instruction(
|
||||
&account,
|
||||
market_index,
|
||||
|
@ -814,6 +1068,10 @@ impl MangoClient {
|
|||
limit,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut ixs = PreparedInstructions::new();
|
||||
ixs.append(create_or_replace_ixs);
|
||||
ixs.append(place_order_ixs);
|
||||
self.send_and_confirm_owner_tx(ixs.to_instructions()).await
|
||||
}
|
||||
|
||||
|
@ -1960,7 +2218,7 @@ impl MangoClient {
|
|||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum MangoClientError {
|
||||
#[error("Transaction simulation error. Error: {err:?}, Logs: {}",
|
||||
.logs.iter().join("; ")
|
||||
.logs.iter().join("; ")
|
||||
)]
|
||||
SendTransactionPreflightFailure {
|
||||
err: Option<TransactionError>,
|
||||
|
@ -1999,6 +2257,7 @@ pub enum FallbackOracleConfig {
|
|||
/// Every possible fallback oracle (may cause serious issues with the 64 accounts-per-tx limit)
|
||||
All,
|
||||
}
|
||||
|
||||
impl Default for FallbackOracleConfig {
|
||||
fn default() -> Self {
|
||||
FallbackOracleConfig::Dynamic
|
||||
|
|
Loading…
Reference in New Issue