2022-07-31 00:25:11 -07:00
|
|
|
use std::str::FromStr;
|
2022-07-16 05:37:15 -07:00
|
|
|
use std::sync::Arc;
|
2022-08-01 05:19:52 -07:00
|
|
|
use std::time::Duration;
|
2022-05-27 22:05:34 -07:00
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
use anchor_client::{ClientError, Cluster};
|
2022-05-27 22:05:34 -07:00
|
|
|
|
|
|
|
use anchor_lang::__private::bytemuck;
|
|
|
|
use anchor_lang::prelude::System;
|
2022-12-16 04:10:46 -08:00
|
|
|
use anchor_lang::{AccountDeserialize, Id};
|
2022-05-27 22:05:34 -07:00
|
|
|
use anchor_spl::associated_token::get_associated_token_address;
|
2022-07-16 05:37:15 -07:00
|
|
|
use anchor_spl::token::Token;
|
2022-05-27 22:05:34 -07:00
|
|
|
|
2022-08-04 08:01:00 -07:00
|
|
|
use bincode::Options;
|
2022-06-18 07:31:28 -07:00
|
|
|
use fixed::types::I80F48;
|
2022-12-16 04:10:46 -08:00
|
|
|
use futures::{stream, StreamExt, TryStreamExt};
|
2022-06-18 07:31:28 -07:00
|
|
|
use itertools::Itertools;
|
2022-09-29 03:59:55 -07:00
|
|
|
|
2023-02-14 23:42:07 -08:00
|
|
|
use mango_v4::accounts_ix::{Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side};
|
2022-09-15 00:57:48 -07:00
|
|
|
use mango_v4::state::{
|
2023-01-18 01:50:23 -08:00
|
|
|
Bank, Group, MangoAccountValue, PerpMarketIndex, PlaceOrderType, Serum3MarketIndex, Side,
|
|
|
|
TokenIndex,
|
2022-09-15 00:57:48 -07:00
|
|
|
};
|
2022-05-27 22:05:34 -07:00
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
use solana_address_lookup_table_program::state::AddressLookupTable;
|
2022-08-04 08:01:00 -07:00
|
|
|
use solana_client::nonblocking::rpc_client::RpcClient as RpcClientAsync;
|
2023-02-20 03:09:17 -08:00
|
|
|
use solana_client::rpc_config::RpcSendTransactionConfig;
|
2022-12-16 04:10:46 -08:00
|
|
|
use solana_sdk::address_lookup_table_account::AddressLookupTableAccount;
|
2023-02-20 03:09:17 -08:00
|
|
|
use solana_sdk::commitment_config::CommitmentLevel;
|
2022-12-16 04:10:46 -08:00
|
|
|
use solana_sdk::hash::Hash;
|
2022-07-31 00:25:11 -07:00
|
|
|
use solana_sdk::signer::keypair;
|
2023-02-16 01:55:35 -08:00
|
|
|
use solana_sdk::transaction::TransactionError;
|
2022-05-27 22:05:34 -07:00
|
|
|
|
2022-07-16 05:37:15 -07:00
|
|
|
use crate::account_fetcher::*;
|
2022-09-23 02:59:18 -07:00
|
|
|
use crate::context::{MangoGroupContext, Serum3MarketContext, TokenContext};
|
2022-12-16 04:10:46 -08:00
|
|
|
use crate::gpa::{fetch_anchor_account, fetch_mango_accounts};
|
2022-08-04 08:01:00 -07:00
|
|
|
use crate::jupiter;
|
2022-07-16 05:37:15 -07:00
|
|
|
|
2022-05-29 03:25:12 -07:00
|
|
|
use anyhow::Context;
|
2022-09-23 02:59:18 -07:00
|
|
|
use solana_sdk::account::ReadableAccount;
|
2022-05-27 22:05:34 -07:00
|
|
|
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-08-01 05:19:52 -07:00
|
|
|
// very close to anchor_client::Client, which unfortunately has no accessors or Clone
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub struct Client {
|
2022-05-27 22:05:34 -07:00
|
|
|
pub cluster: Cluster,
|
2022-08-01 05:19:52 -07:00
|
|
|
pub fee_payer: Arc<Keypair>,
|
2022-05-27 22:05:34 -07:00
|
|
|
pub commitment: CommitmentConfig,
|
2022-08-04 08:01:00 -07:00
|
|
|
pub timeout: Option<Duration>,
|
2023-02-20 03:09:17 -08:00
|
|
|
pub transaction_builder_config: TransactionBuilderConfig,
|
|
|
|
pub rpc_send_transaction_config: RpcSendTransactionConfig,
|
2022-08-01 05:19:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Client {
|
2022-08-04 08:01:00 -07:00
|
|
|
pub fn new(
|
|
|
|
cluster: Cluster,
|
|
|
|
commitment: CommitmentConfig,
|
2023-02-10 05:22:07 -08:00
|
|
|
fee_payer: Arc<Keypair>,
|
2022-08-04 08:01:00 -07:00
|
|
|
timeout: Option<Duration>,
|
2023-02-20 03:09:17 -08:00
|
|
|
transaction_builder_config: TransactionBuilderConfig,
|
2022-08-04 08:01:00 -07:00
|
|
|
) -> Self {
|
2022-08-01 05:19:52 -07:00
|
|
|
Self {
|
|
|
|
cluster,
|
2023-02-10 05:22:07 -08:00
|
|
|
fee_payer,
|
2022-08-01 05:19:52 -07:00
|
|
|
commitment,
|
2022-08-04 08:01:00 -07:00
|
|
|
timeout,
|
2023-02-20 03:09:17 -08:00
|
|
|
transaction_builder_config,
|
|
|
|
rpc_send_transaction_config: RpcSendTransactionConfig {
|
|
|
|
preflight_commitment: Some(CommitmentLevel::Processed),
|
|
|
|
..Default::default()
|
|
|
|
},
|
2022-08-01 05:19:52 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-04 08:01:00 -07:00
|
|
|
pub fn rpc_async(&self) -> RpcClientAsync {
|
|
|
|
let url = self.cluster.url().to_string();
|
|
|
|
if let Some(timeout) = self.timeout.as_ref() {
|
|
|
|
RpcClientAsync::new_with_timeout_and_commitment(url, *timeout, self.commitment)
|
|
|
|
} else {
|
|
|
|
RpcClientAsync::new_with_commitment(url, self.commitment)
|
|
|
|
}
|
2022-08-01 05:19:52 -07:00
|
|
|
}
|
2022-12-16 04:10:46 -08:00
|
|
|
|
|
|
|
// TODO: this function here is awkward, since it (intentionally) doesn't use MangoClient::account_fetcher
|
|
|
|
pub async fn rpc_anchor_account<T: AccountDeserialize>(
|
|
|
|
&self,
|
|
|
|
address: &Pubkey,
|
|
|
|
) -> anyhow::Result<T> {
|
|
|
|
fetch_anchor_account(&self.rpc_async(), address).await
|
|
|
|
}
|
2022-08-01 05:19:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// todo: might want to integrate geyser, websockets, or simple http polling for keeping data fresh
|
|
|
|
pub struct MangoClient {
|
|
|
|
pub client: Client,
|
2022-07-16 05:37:15 -07:00
|
|
|
|
|
|
|
// todo: possibly this object should have cache-functions, so there can be one getMultipleAccounts
|
|
|
|
// call to refresh banks etc -- if it's backed by websockets, these could just do nothing
|
|
|
|
pub account_fetcher: Arc<dyn AccountFetcher>,
|
|
|
|
|
2023-02-10 05:22:07 -08:00
|
|
|
pub owner: Arc<Keypair>,
|
2022-07-16 05:37:15 -07:00
|
|
|
pub mango_account_address: Pubkey,
|
|
|
|
|
|
|
|
pub context: MangoGroupContext,
|
2022-08-04 08:01:00 -07:00
|
|
|
|
|
|
|
pub http_client: reqwest::Client,
|
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 {
|
2022-07-14 03:57:19 -07:00
|
|
|
pub fn group_for_admin(admin: Pubkey, num: u32) -> Pubkey {
|
|
|
|
Pubkey::find_program_address(
|
|
|
|
&["Group".as_ref(), admin.as_ref(), num.to_le_bytes().as_ref()],
|
|
|
|
&mango_v4::ID,
|
|
|
|
)
|
|
|
|
.0
|
|
|
|
}
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
pub async fn find_accounts(
|
2022-08-04 08:01:00 -07:00
|
|
|
client: &Client,
|
|
|
|
group: Pubkey,
|
|
|
|
owner: &Keypair,
|
|
|
|
) -> anyhow::Result<Vec<(Pubkey, MangoAccountValue)>> {
|
2022-12-16 04:10:46 -08:00
|
|
|
fetch_mango_accounts(&client.rpc_async(), mango_v4::ID, group, owner.pubkey()).await
|
2022-08-04 08:01:00 -07:00
|
|
|
}
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
pub async fn find_or_create_account(
|
2022-08-01 05:19:52 -07:00
|
|
|
client: &Client,
|
2022-07-14 03:57:19 -07:00
|
|
|
group: Pubkey,
|
2022-08-04 08:01:00 -07:00
|
|
|
owner: &Keypair,
|
|
|
|
payer: &Keypair, // pays the SOL for the new account
|
2022-07-16 05:37:15 -07:00
|
|
|
mango_account_name: &str,
|
2022-08-01 05:19:52 -07:00
|
|
|
) -> anyhow::Result<Pubkey> {
|
2022-12-16 04:10:46 -08:00
|
|
|
let rpc = client.rpc_async();
|
|
|
|
let program = mango_v4::ID;
|
2022-07-13 05:04:20 -07:00
|
|
|
|
2022-05-29 03:25:12 -07:00
|
|
|
// Mango Account
|
2022-12-16 04:10:46 -08:00
|
|
|
let mut mango_account_tuples =
|
|
|
|
fetch_mango_accounts(&rpc, program, group, owner.pubkey()).await?;
|
2022-05-29 03:25:12 -07:00
|
|
|
let mango_account_opt = mango_account_tuples
|
|
|
|
.iter()
|
2022-07-25 07:07:53 -07:00
|
|
|
.find(|(_, account)| account.fixed.name() == mango_account_name);
|
2022-05-29 03:25:12 -07:00
|
|
|
if mango_account_opt.is_none() {
|
2022-07-25 07:07:53 -07:00
|
|
|
mango_account_tuples.sort_by(|a, b| {
|
|
|
|
a.1.fixed
|
|
|
|
.account_num
|
|
|
|
.partial_cmp(&b.1.fixed.account_num)
|
|
|
|
.unwrap()
|
|
|
|
});
|
2022-05-29 03:25:12 -07:00
|
|
|
let account_num = match mango_account_tuples.last() {
|
2022-07-25 07:07:53 -07:00
|
|
|
Some(tuple) => tuple.1.fixed.account_num + 1,
|
2022-08-01 09:46:45 -07:00
|
|
|
None => 0u32,
|
2022-05-29 03:25:12 -07:00
|
|
|
};
|
2022-08-04 08:01:00 -07:00
|
|
|
Self::create_account(client, group, owner, payer, account_num, mango_account_name)
|
2022-12-16 04:10:46 -08:00
|
|
|
.await
|
2022-05-29 03:25:12 -07:00
|
|
|
.context("Failed to create account...")?;
|
|
|
|
}
|
2022-12-16 04:10:46 -08:00
|
|
|
let mango_account_tuples =
|
|
|
|
fetch_mango_accounts(&rpc, program, group, owner.pubkey()).await?;
|
2022-05-29 03:25:12 -07:00
|
|
|
let index = mango_account_tuples
|
|
|
|
.iter()
|
2022-07-25 07:07:53 -07:00
|
|
|
.position(|tuple| tuple.1.fixed.name() == mango_account_name)
|
2022-05-29 03:25:12 -07:00
|
|
|
.unwrap();
|
2022-08-01 05:19:52 -07:00
|
|
|
Ok(mango_account_tuples[index].0)
|
|
|
|
}
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
pub async fn create_account(
|
2022-08-04 08:01:00 -07:00
|
|
|
client: &Client,
|
|
|
|
group: Pubkey,
|
|
|
|
owner: &Keypair,
|
|
|
|
payer: &Keypair, // pays the SOL for the new account
|
|
|
|
account_num: u32,
|
|
|
|
mango_account_name: &str,
|
|
|
|
) -> anyhow::Result<(Pubkey, Signature)> {
|
|
|
|
let account = Pubkey::find_program_address(
|
|
|
|
&[
|
|
|
|
group.as_ref(),
|
|
|
|
b"MangoAccount".as_ref(),
|
|
|
|
owner.pubkey().as_ref(),
|
|
|
|
&account_num.to_le_bytes(),
|
|
|
|
],
|
|
|
|
&mango_v4::id(),
|
|
|
|
)
|
|
|
|
.0;
|
2022-12-16 04:10:46 -08:00
|
|
|
let ix = Instruction {
|
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: anchor_lang::ToAccountMetas::to_account_metas(
|
|
|
|
&mango_v4::accounts::AccountCreate {
|
|
|
|
group,
|
|
|
|
owner: owner.pubkey(),
|
|
|
|
account,
|
|
|
|
payer: payer.pubkey(),
|
|
|
|
system_program: System::id(),
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
data: anchor_lang::InstructionData::data(&mango_v4::instruction::AccountCreate {
|
|
|
|
account_num,
|
|
|
|
name: mango_account_name.to_owned(),
|
|
|
|
token_count: 8,
|
|
|
|
serum3_count: 8,
|
|
|
|
perp_count: 8,
|
|
|
|
perp_oo_count: 8,
|
|
|
|
}),
|
|
|
|
};
|
|
|
|
|
|
|
|
let txsig = TransactionBuilder {
|
|
|
|
instructions: vec![ix],
|
|
|
|
address_lookup_tables: vec![],
|
|
|
|
payer: payer.pubkey(),
|
|
|
|
signers: vec![owner, payer],
|
2023-02-20 03:09:17 -08:00
|
|
|
config: client.transaction_builder_config,
|
2022-12-16 04:10:46 -08:00
|
|
|
}
|
2023-02-02 05:23:33 -08:00
|
|
|
.send_and_confirm(&client)
|
2022-12-16 04:10:46 -08:00
|
|
|
.await?;
|
2022-08-04 08:01:00 -07:00
|
|
|
|
|
|
|
Ok((account, txsig))
|
|
|
|
}
|
|
|
|
|
2022-08-01 05:19:52 -07:00
|
|
|
/// Conveniently creates a RPC based client
|
2022-12-16 04:10:46 -08:00
|
|
|
pub async fn new_for_existing_account(
|
2022-08-01 05:19:52 -07:00
|
|
|
client: Client,
|
|
|
|
account: Pubkey,
|
2023-02-10 05:22:07 -08:00
|
|
|
owner: Arc<Keypair>,
|
2022-08-01 05:19:52 -07:00
|
|
|
) -> anyhow::Result<Self> {
|
2022-12-16 04:10:46 -08:00
|
|
|
let rpc = client.rpc_async();
|
2023-01-04 07:15:46 -08:00
|
|
|
let account_fetcher = Arc::new(CachedAccountFetcher::new(Arc::new(RpcAccountFetcher {
|
|
|
|
rpc,
|
|
|
|
})));
|
2022-12-16 04:10:46 -08:00
|
|
|
let mango_account =
|
|
|
|
account_fetcher_fetch_mango_account(&*account_fetcher, &account).await?;
|
2022-08-01 05:19:52 -07:00
|
|
|
let group = mango_account.fixed.group;
|
|
|
|
if mango_account.fixed.owner != owner.pubkey() {
|
|
|
|
anyhow::bail!(
|
|
|
|
"bad owner for account: expected {} got {}",
|
|
|
|
mango_account.fixed.owner,
|
|
|
|
owner.pubkey()
|
|
|
|
);
|
|
|
|
}
|
2022-05-27 22:05:34 -07:00
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
let rpc = client.rpc_async();
|
|
|
|
let group_context = MangoGroupContext::new_from_rpc(&rpc, group).await?;
|
2022-08-01 05:19:52 -07:00
|
|
|
|
|
|
|
Self::new_detail(client, account, owner, group_context, account_fetcher)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Allows control of AccountFetcher and externally created MangoGroupContext
|
|
|
|
pub fn new_detail(
|
|
|
|
client: Client,
|
|
|
|
account: Pubkey,
|
2023-02-10 05:22:07 -08:00
|
|
|
owner: Arc<Keypair>,
|
2022-08-01 05:19:52 -07:00
|
|
|
// future: maybe pass Arc<MangoGroupContext>, so it can be extenally updated?
|
|
|
|
group_context: MangoGroupContext,
|
|
|
|
account_fetcher: Arc<dyn AccountFetcher>,
|
|
|
|
) -> anyhow::Result<Self> {
|
2022-05-27 22:05:34 -07:00
|
|
|
Ok(Self {
|
2022-08-01 05:19:52 -07:00
|
|
|
client,
|
2022-07-16 05:37:15 -07:00
|
|
|
account_fetcher,
|
2022-08-01 05:19:52 -07:00
|
|
|
owner,
|
|
|
|
mango_account_address: account,
|
2022-07-16 05:37:15 -07:00
|
|
|
context: group_context,
|
2022-08-04 08:01:00 -07:00
|
|
|
http_client: reqwest::Client::new(),
|
2022-05-27 22:05:34 -07:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-08-01 05:19:52 -07:00
|
|
|
pub fn owner(&self) -> Pubkey {
|
|
|
|
self.owner.pubkey()
|
2022-05-27 22:05:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn group(&self) -> Pubkey {
|
2022-07-16 05:37:15 -07:00
|
|
|
self.context.group
|
2022-05-27 22:05:34 -07:00
|
|
|
}
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
pub async fn mango_account(&self) -> anyhow::Result<MangoAccountValue> {
|
2022-09-23 02:59:18 -07:00
|
|
|
account_fetcher_fetch_mango_account(&*self.account_fetcher, &self.mango_account_address)
|
2022-12-16 04:10:46 -08:00
|
|
|
.await
|
2022-07-16 05:37:15 -07:00
|
|
|
}
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
pub async fn first_bank(&self, token_index: TokenIndex) -> anyhow::Result<Bank> {
|
2022-07-16 05:37:15 -07:00
|
|
|
let bank_address = self.context.mint_info(token_index).first_bank();
|
2022-12-16 04:10:46 -08:00
|
|
|
account_fetcher_fetch_anchor_account(&*self.account_fetcher, &bank_address).await
|
2022-05-27 22:05:34 -07:00
|
|
|
}
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
pub async fn derive_health_check_remaining_account_metas(
|
2022-05-27 22:05:34 -07:00
|
|
|
&self,
|
2022-08-04 08:01:00 -07:00
|
|
|
affected_tokens: Vec<TokenIndex>,
|
2023-01-18 01:50:23 -08:00
|
|
|
writable_banks: Vec<TokenIndex>,
|
|
|
|
affected_perp_markets: Vec<PerpMarketIndex>,
|
2022-07-16 05:37:15 -07:00
|
|
|
) -> anyhow::Result<Vec<AccountMeta>> {
|
2022-12-16 04:10:46 -08:00
|
|
|
let account = self.mango_account().await?;
|
2022-07-16 05:37:15 -07:00
|
|
|
self.context.derive_health_check_remaining_account_metas(
|
|
|
|
&account,
|
2022-08-04 08:01:00 -07:00
|
|
|
affected_tokens,
|
2022-07-16 05:37:15 -07:00
|
|
|
writable_banks,
|
2023-01-18 01:50:23 -08:00
|
|
|
affected_perp_markets,
|
2022-07-16 05:37:15 -07:00
|
|
|
)
|
2022-06-18 07:31:28 -07:00
|
|
|
}
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
pub async fn derive_liquidation_health_check_remaining_account_metas(
|
2022-06-18 07:31:28 -07:00
|
|
|
&self,
|
2022-07-25 07:07:53 -07:00
|
|
|
liqee: &MangoAccountValue,
|
2022-09-27 02:36:58 -07:00
|
|
|
writable_banks: &[TokenIndex],
|
2022-07-16 05:37:15 -07:00
|
|
|
) -> anyhow::Result<Vec<AccountMeta>> {
|
2022-12-16 04:10:46 -08:00
|
|
|
let account = self.mango_account().await?;
|
2022-09-29 03:59:55 -07:00
|
|
|
self.context
|
|
|
|
.derive_health_check_remaining_account_metas_two_accounts(
|
|
|
|
&account,
|
|
|
|
liqee,
|
|
|
|
writable_banks,
|
|
|
|
)
|
2022-05-27 22:05:34 -07:00
|
|
|
}
|
|
|
|
|
2023-01-04 00:24:40 -08:00
|
|
|
pub async fn token_deposit(
|
|
|
|
&self,
|
|
|
|
mint: Pubkey,
|
|
|
|
amount: u64,
|
|
|
|
reduce_only: bool,
|
|
|
|
) -> anyhow::Result<Signature> {
|
2022-08-04 08:01:00 -07:00
|
|
|
let token = self.context.token_by_mint(&mint)?;
|
|
|
|
let token_index = token.token_index;
|
|
|
|
let mint_info = token.mint_info;
|
2022-05-27 22:05:34 -07:00
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
let health_check_metas = self
|
2023-01-18 01:50:23 -08:00
|
|
|
.derive_health_check_remaining_account_metas(vec![token_index], vec![], vec![])
|
2022-12-16 04:10:46 -08:00
|
|
|
.await?;
|
2022-05-27 22:05:34 -07:00
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
let ix = Instruction {
|
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: {
|
|
|
|
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
|
|
|
|
&mango_v4::accounts::TokenDeposit {
|
|
|
|
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),
|
|
|
|
token_authority: self.owner(),
|
|
|
|
token_program: Token::id(),
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
ams.extend(health_check_metas.into_iter());
|
|
|
|
ams
|
|
|
|
},
|
|
|
|
data: anchor_lang::InstructionData::data(&mango_v4::instruction::TokenDeposit {
|
|
|
|
amount,
|
2023-01-04 00:24:40 -08:00
|
|
|
reduce_only,
|
2022-12-16 04:10:46 -08:00
|
|
|
}),
|
|
|
|
};
|
|
|
|
self.send_and_confirm_owner_tx(vec![ix]).await
|
2022-05-27 22:05:34 -07:00
|
|
|
}
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
pub async fn token_withdraw(
|
2022-08-10 00:30:43 -07:00
|
|
|
&self,
|
|
|
|
mint: Pubkey,
|
|
|
|
amount: u64,
|
|
|
|
allow_borrow: bool,
|
|
|
|
) -> anyhow::Result<Signature> {
|
|
|
|
let token = self.context.token_by_mint(&mint)?;
|
|
|
|
let token_index = token.token_index;
|
|
|
|
let mint_info = token.mint_info;
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
let health_check_metas = self
|
2023-01-18 01:50:23 -08:00
|
|
|
.derive_health_check_remaining_account_metas(vec![token_index], vec![], vec![])
|
2022-12-16 04:10:46 -08:00
|
|
|
.await?;
|
2022-08-10 00:30:43 -07:00
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
let ixs = vec![
|
|
|
|
spl_associated_token_account::instruction::create_associated_token_account_idempotent(
|
2022-08-10 00:30:43 -07:00
|
|
|
&self.owner(),
|
|
|
|
&self.owner(),
|
|
|
|
&mint,
|
2022-12-09 07:12:21 -08:00
|
|
|
&Token::id(),
|
2022-12-16 04:10:46 -08:00
|
|
|
),
|
|
|
|
Instruction {
|
2022-08-10 00:30:43 -07:00
|
|
|
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: mint_info.first_bank(),
|
|
|
|
vault: mint_info.first_vault(),
|
2022-08-22 02:02:01 -07:00
|
|
|
oracle: mint_info.oracle,
|
2022-08-10 00:30:43 -07:00
|
|
|
token_account: get_associated_token_address(
|
|
|
|
&self.owner(),
|
|
|
|
&mint_info.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,
|
|
|
|
}),
|
2022-12-16 04:10:46 -08:00
|
|
|
},
|
|
|
|
];
|
|
|
|
self.send_and_confirm_owner_tx(ixs).await
|
2022-08-10 00:30:43 -07:00
|
|
|
}
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
pub async fn get_oracle_price(
|
2022-05-27 22:05:34 -07:00
|
|
|
&self,
|
|
|
|
token_name: &str,
|
|
|
|
) -> Result<pyth_sdk_solana::Price, anyhow::Error> {
|
2022-07-16 05:37:15 -07:00
|
|
|
let token_index = *self.context.token_indexes_by_name.get(token_name).unwrap();
|
|
|
|
let mint_info = self.context.mint_info(token_index);
|
2022-12-16 04:10:46 -08:00
|
|
|
let oracle_account = self
|
|
|
|
.account_fetcher
|
|
|
|
.fetch_raw_account(&mint_info.oracle)
|
|
|
|
.await?;
|
2022-09-23 02:59:18 -07:00
|
|
|
Ok(pyth_sdk_solana::load_price(&oracle_account.data()).unwrap())
|
2022-05-27 22:05:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Serum3
|
|
|
|
//
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
pub async fn serum3_create_open_orders(&self, name: &str) -> anyhow::Result<Signature> {
|
2022-07-16 05:37:15 -07:00
|
|
|
let account_pubkey = self.mango_account_address;
|
2022-05-27 22:05:34 -07:00
|
|
|
|
2022-07-16 05:37:15 -07:00
|
|
|
let market_index = *self
|
|
|
|
.context
|
|
|
|
.serum3_market_indexes_by_name
|
|
|
|
.get(name)
|
|
|
|
.unwrap();
|
|
|
|
let serum3_info = self.context.serum3_markets.get(&market_index).unwrap();
|
2022-05-27 22:05:34 -07:00
|
|
|
|
|
|
|
let open_orders = Pubkey::find_program_address(
|
|
|
|
&[
|
|
|
|
account_pubkey.as_ref(),
|
|
|
|
b"Serum3OO".as_ref(),
|
2022-07-16 05:37:15 -07:00
|
|
|
serum3_info.address.as_ref(),
|
2022-05-27 22:05:34 -07:00
|
|
|
],
|
2022-12-16 04:10:46 -08:00
|
|
|
&mango_v4::ID,
|
2022-05-27 22:05:34 -07:00
|
|
|
)
|
|
|
|
.0;
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
let ix = 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_info.address,
|
|
|
|
serum_program: serum3_info.market.serum_program,
|
|
|
|
serum_market_external: serum3_info.market.serum_market_external,
|
|
|
|
open_orders,
|
|
|
|
owner: self.owner(),
|
|
|
|
payer: self.owner(),
|
|
|
|
system_program: System::id(),
|
|
|
|
rent: sysvar::rent::id(),
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
data: anchor_lang::InstructionData::data(
|
|
|
|
&mango_v4::instruction::Serum3CreateOpenOrders {},
|
|
|
|
),
|
|
|
|
};
|
|
|
|
self.send_and_confirm_owner_tx(vec![ix]).await
|
2022-05-27 22:05:34 -07:00
|
|
|
}
|
|
|
|
|
2022-08-31 05:41:04 -07:00
|
|
|
fn serum3_data_by_market_name<'a>(&'a self, name: &str) -> Result<Serum3Data<'a>, ClientError> {
|
2022-07-16 05:37:15 -07:00
|
|
|
let market_index = *self
|
|
|
|
.context
|
|
|
|
.serum3_market_indexes_by_name
|
|
|
|
.get(name)
|
|
|
|
.unwrap();
|
2022-08-31 05:41:04 -07:00
|
|
|
self.serum3_data_by_market_index(market_index)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn serum3_data_by_market_index<'a>(
|
|
|
|
&'a self,
|
|
|
|
market_index: Serum3MarketIndex,
|
|
|
|
) -> Result<Serum3Data<'a>, ClientError> {
|
2022-07-16 05:37:15 -07:00
|
|
|
let serum3_info = self.context.serum3_markets.get(&market_index).unwrap();
|
|
|
|
|
|
|
|
let quote_info = self.context.token(serum3_info.market.quote_token_index);
|
|
|
|
let base_info = self.context.token(serum3_info.market.base_token_index);
|
|
|
|
|
|
|
|
Ok(Serum3Data {
|
|
|
|
market_index,
|
|
|
|
market: serum3_info,
|
|
|
|
quote: quote_info,
|
|
|
|
base: base_info,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-05-27 22:05:34 -07:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
2022-12-16 04:10:46 -08:00
|
|
|
pub async fn serum3_place_order(
|
2022-05-27 22:05:34 -07:00
|
|
|
&self,
|
|
|
|
name: &str,
|
|
|
|
side: Serum3Side,
|
|
|
|
price: f64,
|
|
|
|
size: f64,
|
|
|
|
self_trade_behavior: Serum3SelfTradeBehavior,
|
|
|
|
order_type: Serum3OrderType,
|
|
|
|
client_order_id: u64,
|
|
|
|
limit: u16,
|
2022-07-15 01:00:01 -07:00
|
|
|
) -> anyhow::Result<Signature> {
|
2022-08-31 05:41:04 -07:00
|
|
|
let s3 = self.serum3_data_by_market_name(name)?;
|
2022-05-27 22:05:34 -07:00
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
let account = self.mango_account().await?;
|
2022-08-18 04:45:31 -07:00
|
|
|
let open_orders = account.serum3_orders(s3.market_index).unwrap().open_orders;
|
2022-05-27 22:05:34 -07:00
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
let health_check_metas = self
|
2023-01-18 01:50:23 -08:00
|
|
|
.derive_health_check_remaining_account_metas(vec![], vec![], vec![])
|
2022-12-16 04:10:46 -08:00
|
|
|
.await?;
|
2022-05-27 22:05:34 -07:00
|
|
|
|
|
|
|
// https://github.com/project-serum/serum-ts/blob/master/packages/serum/src/market.ts#L1306
|
|
|
|
let limit_price = {
|
2022-07-16 05:37:15 -07:00
|
|
|
(price * ((10u64.pow(s3.quote.decimals as u32) * s3.market.coin_lot_size) as f64))
|
2022-05-27 22:05:34 -07:00
|
|
|
as u64
|
2022-07-16 05:37:15 -07:00
|
|
|
/ (10u64.pow(s3.base.decimals as u32) * s3.market.pc_lot_size)
|
2022-05-27 22:05:34 -07:00
|
|
|
};
|
|
|
|
// https://github.com/project-serum/serum-ts/blob/master/packages/serum/src/market.ts#L1333
|
2022-07-16 05:37:15 -07:00
|
|
|
let max_base_qty =
|
|
|
|
{ (size * 10u64.pow(s3.base.decimals as u32) as f64) as u64 / s3.market.coin_lot_size };
|
2022-05-27 22:05:34 -07:00
|
|
|
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);
|
2022-07-16 05:37:15 -07:00
|
|
|
(s3.market.pc_lot_size as f64 * (1f64 + rates.0)) as u64 * (limit_price * max_base_qty)
|
2022-05-27 22:05:34 -07:00
|
|
|
};
|
2022-08-30 00:55:19 -07:00
|
|
|
let payer_mint_info = match side {
|
|
|
|
Serum3Side::Bid => s3.quote.mint_info,
|
|
|
|
Serum3Side::Ask => s3.base.mint_info,
|
|
|
|
};
|
2022-05-27 22:05:34 -07:00
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
let ix = Instruction {
|
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: {
|
|
|
|
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
|
|
|
|
&mango_v4::accounts::Serum3PlaceOrder {
|
2022-05-27 22:05:34 -07:00
|
|
|
group: self.group(),
|
2022-07-16 05:37:15 -07:00
|
|
|
account: self.mango_account_address,
|
2022-05-27 22:05:34 -07:00
|
|
|
open_orders,
|
2022-12-16 04:10:46 -08:00
|
|
|
payer_bank: payer_mint_info.first_bank(),
|
|
|
|
payer_vault: payer_mint_info.first_vault(),
|
|
|
|
payer_oracle: payer_mint_info.oracle,
|
2022-07-16 05:37:15 -07:00
|
|
|
serum_market: s3.market.address,
|
|
|
|
serum_program: s3.market.market.serum_program,
|
|
|
|
serum_market_external: s3.market.market.serum_market_external,
|
2022-12-16 04:10:46 -08:00
|
|
|
market_bids: s3.market.bids,
|
|
|
|
market_asks: s3.market.asks,
|
|
|
|
market_event_queue: s3.market.event_q,
|
|
|
|
market_request_queue: s3.market.req_q,
|
2022-07-16 05:37:15 -07:00
|
|
|
market_base_vault: s3.market.coin_vault,
|
|
|
|
market_quote_vault: s3.market.pc_vault,
|
|
|
|
market_vault_signer: s3.market.vault_signer,
|
2022-08-01 05:19:52 -07:00
|
|
|
owner: self.owner(),
|
2022-05-27 22:05:34 -07:00
|
|
|
token_program: Token::id(),
|
|
|
|
},
|
|
|
|
None,
|
2022-12-16 04:10:46 -08:00
|
|
|
);
|
|
|
|
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,
|
|
|
|
}),
|
|
|
|
};
|
|
|
|
self.send_and_confirm_owner_tx(vec![ix]).await
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn serum3_settle_funds(&self, name: &str) -> anyhow::Result<Signature> {
|
|
|
|
let s3 = self.serum3_data_by_market_name(name)?;
|
|
|
|
|
|
|
|
let account = self.mango_account().await?;
|
|
|
|
let open_orders = account.serum3_orders(s3.market_index).unwrap().open_orders;
|
|
|
|
|
|
|
|
let ix = Instruction {
|
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: anchor_lang::ToAccountMetas::to_account_metas(
|
|
|
|
&mango_v4::accounts::Serum3SettleFunds {
|
|
|
|
group: self.group(),
|
|
|
|
account: self.mango_account_address,
|
|
|
|
open_orders,
|
|
|
|
quote_bank: s3.quote.mint_info.first_bank(),
|
|
|
|
quote_vault: s3.quote.mint_info.first_vault(),
|
|
|
|
base_bank: s3.base.mint_info.first_bank(),
|
|
|
|
base_vault: s3.base.mint_info.first_vault(),
|
|
|
|
serum_market: s3.market.address,
|
|
|
|
serum_program: s3.market.market.serum_program,
|
|
|
|
serum_market_external: s3.market.market.serum_market_external,
|
|
|
|
market_base_vault: s3.market.coin_vault,
|
|
|
|
market_quote_vault: s3.market.pc_vault,
|
|
|
|
market_vault_signer: s3.market.vault_signer,
|
|
|
|
owner: self.owner(),
|
|
|
|
token_program: Token::id(),
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
data: anchor_lang::InstructionData::data(&mango_v4::instruction::Serum3SettleFunds {}),
|
|
|
|
};
|
|
|
|
self.send_and_confirm_owner_tx(vec![ix]).await
|
2022-05-27 22:05:34 -07:00
|
|
|
}
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
pub async fn serum3_cancel_all_orders(
|
|
|
|
&self,
|
|
|
|
market_name: &str,
|
|
|
|
) -> Result<Vec<u128>, anyhow::Error> {
|
2022-07-16 05:37:15 -07:00
|
|
|
let market_index = *self
|
|
|
|
.context
|
|
|
|
.serum3_market_indexes_by_name
|
|
|
|
.get(market_name)
|
|
|
|
.unwrap();
|
2022-12-16 04:10:46 -08:00
|
|
|
let account = self.mango_account().await?;
|
2022-08-18 04:45:31 -07:00
|
|
|
let open_orders = account.serum3_orders(market_index).unwrap().open_orders;
|
2022-12-16 04:10:46 -08:00
|
|
|
let open_orders_acc = self.account_fetcher.fetch_raw_account(&open_orders).await?;
|
2022-09-23 02:59:18 -07:00
|
|
|
let open_orders_bytes = open_orders_acc.data();
|
2022-05-27 22:05:34 -07:00
|
|
|
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)
|
2022-12-16 04:10:46 -08:00
|
|
|
.await
|
2022-05-27 22:05:34 -07:00
|
|
|
.ok();
|
|
|
|
self.serum3_cancel_order(market_name, Serum3Side::Ask, order_id)
|
2022-12-16 04:10:46 -08:00
|
|
|
.await
|
2022-05-27 22:05:34 -07:00
|
|
|
.ok();
|
|
|
|
|
|
|
|
orders.push(order_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(orders)
|
|
|
|
}
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
pub async fn serum3_liq_force_cancel_orders(
|
2022-08-31 05:41:04 -07:00
|
|
|
&self,
|
|
|
|
liqee: (&Pubkey, &MangoAccountValue),
|
|
|
|
market_index: Serum3MarketIndex,
|
|
|
|
open_orders: &Pubkey,
|
|
|
|
) -> anyhow::Result<Signature> {
|
|
|
|
let s3 = self.serum3_data_by_market_index(market_index)?;
|
|
|
|
|
|
|
|
let health_remaining_ams = self
|
|
|
|
.context
|
2023-01-18 01:50:23 -08:00
|
|
|
.derive_health_check_remaining_account_metas(liqee.1, vec![], vec![], vec![])
|
2022-08-31 05:41:04 -07:00
|
|
|
.unwrap();
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
let ix = Instruction {
|
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: {
|
|
|
|
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
|
|
|
|
&mango_v4::accounts::Serum3LiqForceCancelOrders {
|
|
|
|
group: self.group(),
|
|
|
|
account: *liqee.0,
|
|
|
|
open_orders: *open_orders,
|
|
|
|
serum_market: s3.market.address,
|
|
|
|
serum_program: s3.market.market.serum_program,
|
|
|
|
serum_market_external: s3.market.market.serum_market_external,
|
|
|
|
market_bids: s3.market.bids,
|
|
|
|
market_asks: s3.market.asks,
|
|
|
|
market_event_queue: s3.market.event_q,
|
|
|
|
market_base_vault: s3.market.coin_vault,
|
|
|
|
market_quote_vault: s3.market.pc_vault,
|
|
|
|
market_vault_signer: s3.market.vault_signer,
|
|
|
|
quote_bank: s3.quote.mint_info.first_bank(),
|
|
|
|
quote_vault: s3.quote.mint_info.first_vault(),
|
|
|
|
base_bank: s3.base.mint_info.first_bank(),
|
|
|
|
base_vault: s3.base.mint_info.first_vault(),
|
|
|
|
token_program: Token::id(),
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
ams.extend(health_remaining_ams.into_iter());
|
|
|
|
ams
|
|
|
|
},
|
|
|
|
data: anchor_lang::InstructionData::data(
|
|
|
|
&mango_v4::instruction::Serum3LiqForceCancelOrders { limit: 5 },
|
|
|
|
),
|
|
|
|
};
|
|
|
|
self.send_and_confirm_permissionless_tx(vec![ix]).await
|
2022-08-31 05:41:04 -07:00
|
|
|
}
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
pub async fn serum3_cancel_order(
|
2022-05-27 22:05:34 -07:00
|
|
|
&self,
|
|
|
|
market_name: &str,
|
|
|
|
side: Serum3Side,
|
|
|
|
order_id: u128,
|
2022-12-16 04:10:46 -08:00
|
|
|
) -> anyhow::Result<Signature> {
|
2022-08-31 05:41:04 -07:00
|
|
|
let s3 = self.serum3_data_by_market_name(market_name)?;
|
2022-05-27 22:05:34 -07:00
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
let account = self.mango_account().await?;
|
2022-08-18 04:45:31 -07:00
|
|
|
let open_orders = account.serum3_orders(s3.market_index).unwrap().open_orders;
|
2022-05-27 22:05:34 -07:00
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
let ix = Instruction {
|
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: {
|
|
|
|
anchor_lang::ToAccountMetas::to_account_metas(
|
|
|
|
&mango_v4::accounts::Serum3CancelOrder {
|
|
|
|
group: self.group(),
|
|
|
|
account: self.mango_account_address,
|
|
|
|
serum_market: s3.market.address,
|
|
|
|
serum_program: s3.market.market.serum_program,
|
|
|
|
serum_market_external: s3.market.market.serum_market_external,
|
|
|
|
open_orders,
|
|
|
|
market_bids: s3.market.bids,
|
|
|
|
market_asks: s3.market.asks,
|
|
|
|
market_event_queue: s3.market.event_q,
|
|
|
|
owner: self.owner(),
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
)
|
|
|
|
},
|
|
|
|
data: anchor_lang::InstructionData::data(&mango_v4::instruction::Serum3CancelOrder {
|
|
|
|
side,
|
|
|
|
order_id,
|
|
|
|
}),
|
|
|
|
};
|
|
|
|
self.send_and_confirm_owner_tx(vec![ix]).await
|
2022-05-27 22:05:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Perps
|
|
|
|
//
|
2023-01-18 01:50:23 -08:00
|
|
|
pub async fn perp_place_order(
|
|
|
|
&self,
|
|
|
|
market_index: PerpMarketIndex,
|
|
|
|
side: Side,
|
|
|
|
price_lots: i64,
|
|
|
|
max_base_lots: i64,
|
|
|
|
max_quote_lots: i64,
|
|
|
|
client_order_id: u64,
|
|
|
|
order_type: PlaceOrderType,
|
|
|
|
reduce_only: bool,
|
|
|
|
expiry_timestamp: u64,
|
|
|
|
limit: u8,
|
|
|
|
) -> anyhow::Result<Signature> {
|
|
|
|
let perp = self.context.perp(market_index);
|
|
|
|
|
|
|
|
let health_check_metas = self
|
|
|
|
.derive_health_check_remaining_account_metas(
|
|
|
|
vec![],
|
|
|
|
vec![],
|
|
|
|
vec![perp.market.perp_market_index],
|
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
let ix = Instruction {
|
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: {
|
|
|
|
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
|
|
|
|
&mango_v4::accounts::PerpPlaceOrder {
|
|
|
|
group: self.group(),
|
|
|
|
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,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
ams.extend(health_check_metas.into_iter());
|
|
|
|
ams
|
|
|
|
},
|
|
|
|
data: anchor_lang::InstructionData::data(&mango_v4::instruction::PerpPlaceOrder {
|
|
|
|
side,
|
|
|
|
price_lots,
|
|
|
|
max_base_lots,
|
|
|
|
max_quote_lots,
|
|
|
|
client_order_id,
|
|
|
|
order_type,
|
|
|
|
reduce_only,
|
|
|
|
expiry_timestamp,
|
|
|
|
limit,
|
|
|
|
}),
|
|
|
|
};
|
|
|
|
self.send_and_confirm_owner_tx(vec![ix]).await
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn perp_deactivate_position(
|
|
|
|
&self,
|
|
|
|
market_index: PerpMarketIndex,
|
|
|
|
) -> anyhow::Result<Signature> {
|
|
|
|
let perp = self.context.perp(market_index);
|
|
|
|
|
|
|
|
let health_check_metas = self
|
|
|
|
.derive_health_check_remaining_account_metas(vec![], vec![], vec![])
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
let ix = Instruction {
|
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: {
|
|
|
|
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
|
|
|
|
&mango_v4::accounts::PerpDeactivatePosition {
|
|
|
|
group: self.group(),
|
|
|
|
account: self.mango_account_address,
|
|
|
|
owner: self.owner(),
|
|
|
|
perp_market: perp.address,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
ams.extend(health_check_metas.into_iter());
|
|
|
|
ams
|
|
|
|
},
|
|
|
|
data: anchor_lang::InstructionData::data(
|
|
|
|
&mango_v4::instruction::PerpDeactivatePosition {},
|
|
|
|
),
|
|
|
|
};
|
|
|
|
self.send_and_confirm_owner_tx(vec![ix]).await
|
|
|
|
}
|
|
|
|
|
2023-02-20 03:09:17 -08:00
|
|
|
pub fn perp_settle_pnl_instruction(
|
2022-09-15 00:57:48 -07:00
|
|
|
&self,
|
|
|
|
market_index: PerpMarketIndex,
|
2022-09-29 03:59:55 -07:00
|
|
|
account_a: (&Pubkey, &MangoAccountValue),
|
2022-09-23 02:59:18 -07:00
|
|
|
account_b: (&Pubkey, &MangoAccountValue),
|
2023-02-20 03:09:17 -08:00
|
|
|
) -> anyhow::Result<Instruction> {
|
2022-09-23 02:59:18 -07:00
|
|
|
let perp = self.context.perp(market_index);
|
2022-09-29 05:13:28 -07:00
|
|
|
let settlement_token = self.context.token(perp.market.settle_token_index);
|
2022-09-23 02:59:18 -07:00
|
|
|
|
|
|
|
let health_remaining_ams = self
|
|
|
|
.context
|
2022-09-29 03:59:55 -07:00
|
|
|
.derive_health_check_remaining_account_metas_two_accounts(account_a.1, account_b.1, &[])
|
2022-09-23 02:59:18 -07:00
|
|
|
.unwrap();
|
|
|
|
|
2023-02-20 03:09:17 -08:00
|
|
|
Ok(Instruction {
|
2022-12-16 04:10:46 -08:00
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: {
|
|
|
|
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
|
|
|
|
&mango_v4::accounts::PerpSettlePnl {
|
|
|
|
group: self.group(),
|
|
|
|
settler: self.mango_account_address,
|
|
|
|
settler_owner: self.owner(),
|
|
|
|
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,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
ams.extend(health_remaining_ams.into_iter());
|
|
|
|
ams
|
|
|
|
},
|
|
|
|
data: anchor_lang::InstructionData::data(&mango_v4::instruction::PerpSettlePnl {}),
|
2023-02-20 03:09:17 -08:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn perp_settle_pnl(
|
|
|
|
&self,
|
|
|
|
market_index: PerpMarketIndex,
|
|
|
|
account_a: (&Pubkey, &MangoAccountValue),
|
|
|
|
account_b: (&Pubkey, &MangoAccountValue),
|
|
|
|
) -> anyhow::Result<Signature> {
|
|
|
|
let ix = self.perp_settle_pnl_instruction(market_index, account_a, account_b)?;
|
2022-12-16 04:10:46 -08:00
|
|
|
self.send_and_confirm_permissionless_tx(vec![ix]).await
|
2022-09-15 00:57:48 -07:00
|
|
|
}
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
pub async fn perp_liq_force_cancel_orders(
|
2022-09-15 00:57:48 -07:00
|
|
|
&self,
|
|
|
|
liqee: (&Pubkey, &MangoAccountValue),
|
|
|
|
market_index: PerpMarketIndex,
|
|
|
|
) -> anyhow::Result<Signature> {
|
2022-09-23 02:59:18 -07:00
|
|
|
let perp = self.context.perp(market_index);
|
2022-09-15 00:57:48 -07:00
|
|
|
|
|
|
|
let health_remaining_ams = self
|
|
|
|
.context
|
2023-01-18 01:50:23 -08:00
|
|
|
.derive_health_check_remaining_account_metas(liqee.1, vec![], vec![], vec![])
|
2022-09-15 00:57:48 -07:00
|
|
|
.unwrap();
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
let ix = Instruction {
|
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: {
|
|
|
|
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
|
|
|
|
&mango_v4::accounts::PerpLiqForceCancelOrders {
|
|
|
|
group: self.group(),
|
|
|
|
account: *liqee.0,
|
|
|
|
perp_market: perp.address,
|
|
|
|
bids: perp.market.bids,
|
|
|
|
asks: perp.market.asks,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
ams.extend(health_remaining_ams.into_iter());
|
|
|
|
ams
|
|
|
|
},
|
|
|
|
data: anchor_lang::InstructionData::data(
|
|
|
|
&mango_v4::instruction::PerpLiqForceCancelOrders { limit: 5 },
|
|
|
|
),
|
|
|
|
};
|
|
|
|
self.send_and_confirm_permissionless_tx(vec![ix]).await
|
2022-09-15 00:57:48 -07:00
|
|
|
}
|
|
|
|
|
2023-02-02 00:00:37 -08:00
|
|
|
pub async fn perp_liq_base_or_positive_pnl(
|
2022-09-15 00:57:48 -07:00
|
|
|
&self,
|
|
|
|
liqee: (&Pubkey, &MangoAccountValue),
|
|
|
|
market_index: PerpMarketIndex,
|
|
|
|
max_base_transfer: i64,
|
2023-02-02 00:00:37 -08:00
|
|
|
max_pnl_transfer: u64,
|
2022-09-15 00:57:48 -07:00
|
|
|
) -> anyhow::Result<Signature> {
|
2022-09-23 02:59:18 -07:00
|
|
|
let perp = self.context.perp(market_index);
|
2023-02-02 00:00:37 -08:00
|
|
|
let settle_token_info = self.context.token(perp.market.settle_token_index);
|
2022-09-15 00:57:48 -07:00
|
|
|
|
|
|
|
let health_remaining_ams = self
|
2022-09-27 02:36:58 -07:00
|
|
|
.derive_liquidation_health_check_remaining_account_metas(liqee.1, &[])
|
2022-12-16 04:10:46 -08:00
|
|
|
.await
|
2022-09-15 00:57:48 -07:00
|
|
|
.unwrap();
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
let ix = Instruction {
|
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: {
|
|
|
|
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
|
2023-02-02 00:00:37 -08:00
|
|
|
&mango_v4::accounts::PerpLiqBaseOrPositivePnl {
|
2022-12-16 04:10:46 -08:00
|
|
|
group: self.group(),
|
|
|
|
perp_market: perp.address,
|
|
|
|
oracle: perp.market.oracle,
|
|
|
|
liqor: self.mango_account_address,
|
|
|
|
liqor_owner: self.owner(),
|
|
|
|
liqee: *liqee.0,
|
2023-02-02 00:00:37 -08:00
|
|
|
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,
|
2022-12-16 04:10:46 -08:00
|
|
|
},
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
ams.extend(health_remaining_ams.into_iter());
|
|
|
|
ams
|
|
|
|
},
|
2023-02-02 00:00:37 -08:00
|
|
|
data: anchor_lang::InstructionData::data(
|
|
|
|
&mango_v4::instruction::PerpLiqBaseOrPositivePnl {
|
|
|
|
max_base_transfer,
|
|
|
|
max_pnl_transfer,
|
|
|
|
},
|
|
|
|
),
|
2022-12-16 04:10:46 -08:00
|
|
|
};
|
|
|
|
self.send_and_confirm_owner_tx(vec![ix]).await
|
2022-09-15 00:57:48 -07:00
|
|
|
}
|
2022-05-27 22:05:34 -07:00
|
|
|
|
2023-02-02 00:00:37 -08:00
|
|
|
pub async fn perp_liq_negative_pnl_or_bankruptcy(
|
2022-09-27 02:36:58 -07:00
|
|
|
&self,
|
|
|
|
liqee: (&Pubkey, &MangoAccountValue),
|
|
|
|
market_index: PerpMarketIndex,
|
|
|
|
max_liab_transfer: u64,
|
|
|
|
) -> anyhow::Result<Signature> {
|
|
|
|
let group = account_fetcher_fetch_anchor_account::<Group>(
|
|
|
|
&*self.account_fetcher,
|
|
|
|
&self.context.group,
|
2022-12-16 04:10:46 -08:00
|
|
|
)
|
|
|
|
.await?;
|
2022-09-27 02:36:58 -07:00
|
|
|
|
|
|
|
let perp = self.context.perp(market_index);
|
2022-09-29 05:13:28 -07:00
|
|
|
let settle_token_info = self.context.token(perp.market.settle_token_index);
|
2022-09-27 02:36:58 -07:00
|
|
|
|
|
|
|
let health_remaining_ams = self
|
|
|
|
.derive_liquidation_health_check_remaining_account_metas(liqee.1, &[])
|
2022-12-16 04:10:46 -08:00
|
|
|
.await
|
2022-09-27 02:36:58 -07:00
|
|
|
.unwrap();
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
let ix = Instruction {
|
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: {
|
|
|
|
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
|
2023-02-02 00:00:37 -08:00
|
|
|
&mango_v4::accounts::PerpLiqNegativePnlOrBankruptcy {
|
2022-12-16 04:10:46 -08:00
|
|
|
group: self.group(),
|
|
|
|
perp_market: perp.address,
|
2023-01-12 00:07:13 -08:00
|
|
|
oracle: perp.market.oracle,
|
2022-12-16 04:10:46 -08:00
|
|
|
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,
|
|
|
|
insurance_vault: group.insurance_vault,
|
|
|
|
token_program: Token::id(),
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
ams.extend(health_remaining_ams.into_iter());
|
|
|
|
ams
|
|
|
|
},
|
2023-01-12 00:07:13 -08:00
|
|
|
data: anchor_lang::InstructionData::data(
|
2023-02-02 00:00:37 -08:00
|
|
|
&mango_v4::instruction::PerpLiqNegativePnlOrBankruptcy { max_liab_transfer },
|
2023-01-12 00:07:13 -08:00
|
|
|
),
|
2022-12-16 04:10:46 -08:00
|
|
|
};
|
|
|
|
self.send_and_confirm_owner_tx(vec![ix]).await
|
2022-09-27 02:36:58 -07:00
|
|
|
}
|
|
|
|
|
2022-05-27 22:05:34 -07:00
|
|
|
//
|
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
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
pub async fn token_liq_with_token(
|
2022-06-18 07:31:28 -07:00
|
|
|
&self,
|
2022-07-25 07:07:53 -07:00
|
|
|
liqee: (&Pubkey, &MangoAccountValue),
|
2022-06-18 07:31:28 -07:00
|
|
|
asset_token_index: TokenIndex,
|
|
|
|
liab_token_index: TokenIndex,
|
|
|
|
max_liab_transfer: I80F48,
|
2022-07-15 01:00:01 -07:00
|
|
|
) -> anyhow::Result<Signature> {
|
2022-06-18 07:31:28 -07:00
|
|
|
let health_remaining_ams = self
|
|
|
|
.derive_liquidation_health_check_remaining_account_metas(
|
|
|
|
liqee.1,
|
2022-09-27 02:36:58 -07:00
|
|
|
&[asset_token_index, liab_token_index],
|
2022-06-18 07:31:28 -07:00
|
|
|
)
|
2022-12-16 04:10:46 -08:00
|
|
|
.await
|
2022-06-18 07:31:28 -07:00
|
|
|
.unwrap();
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
let ix = Instruction {
|
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: {
|
|
|
|
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
|
|
|
|
&mango_v4::accounts::TokenLiqWithToken {
|
|
|
|
group: self.group(),
|
|
|
|
liqee: *liqee.0,
|
|
|
|
liqor: self.mango_account_address,
|
|
|
|
liqor_owner: self.owner(),
|
2022-06-18 07:31:28 -07:00
|
|
|
},
|
2022-12-16 04:10:46 -08:00
|
|
|
None,
|
|
|
|
);
|
|
|
|
ams.extend(health_remaining_ams);
|
|
|
|
ams
|
|
|
|
},
|
|
|
|
data: anchor_lang::InstructionData::data(&mango_v4::instruction::TokenLiqWithToken {
|
|
|
|
asset_token_index,
|
|
|
|
liab_token_index,
|
|
|
|
max_liab_transfer,
|
|
|
|
}),
|
|
|
|
};
|
|
|
|
self.send_and_confirm_owner_tx(vec![ix]).await
|
2022-06-18 07:31:28 -07:00
|
|
|
}
|
2022-07-13 05:04:20 -07:00
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
pub async fn token_liq_bankruptcy(
|
2022-07-13 05:04:20 -07:00
|
|
|
&self,
|
2022-07-25 07:07:53 -07:00
|
|
|
liqee: (&Pubkey, &MangoAccountValue),
|
2022-07-13 05:04:20 -07:00
|
|
|
liab_token_index: TokenIndex,
|
|
|
|
max_liab_transfer: I80F48,
|
2022-07-15 01:00:01 -07:00
|
|
|
) -> anyhow::Result<Signature> {
|
2022-07-13 05:04:20 -07:00
|
|
|
let quote_token_index = 0;
|
|
|
|
|
2022-07-16 05:37:15 -07:00
|
|
|
let quote_info = self.context.token(quote_token_index);
|
|
|
|
let liab_info = self.context.token(liab_token_index);
|
2022-07-13 05:04:20 -07:00
|
|
|
|
2022-07-16 05:37:15 -07:00
|
|
|
let bank_remaining_ams = liab_info
|
|
|
|
.mint_info
|
2022-07-13 05:04:20 -07:00
|
|
|
.banks()
|
|
|
|
.iter()
|
2022-08-04 08:01:00 -07:00
|
|
|
.map(|bank_pubkey| to_writable_account_meta(*bank_pubkey))
|
2022-07-13 05:04:20 -07:00
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
let health_remaining_ams = self
|
|
|
|
.derive_liquidation_health_check_remaining_account_metas(
|
|
|
|
liqee.1,
|
2022-09-27 02:36:58 -07:00
|
|
|
&[quote_token_index, liab_token_index],
|
2022-07-13 05:04:20 -07:00
|
|
|
)
|
2022-12-16 04:10:46 -08:00
|
|
|
.await
|
2022-07-13 05:04:20 -07:00
|
|
|
.unwrap();
|
|
|
|
|
2022-07-16 05:37:15 -07:00
|
|
|
let group = account_fetcher_fetch_anchor_account::<Group>(
|
|
|
|
&*self.account_fetcher,
|
2022-09-23 02:59:18 -07:00
|
|
|
&self.context.group,
|
2022-12-16 04:10:46 -08:00
|
|
|
)
|
|
|
|
.await?;
|
2022-08-04 08:01:00 -07:00
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
let ix = Instruction {
|
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: {
|
|
|
|
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
|
|
|
|
&mango_v4::accounts::TokenLiqBankruptcy {
|
|
|
|
group: self.group(),
|
|
|
|
liqee: *liqee.0,
|
|
|
|
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(),
|
|
|
|
insurance_vault: group.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::TokenLiqBankruptcy {
|
|
|
|
max_liab_transfer,
|
|
|
|
}),
|
|
|
|
};
|
|
|
|
self.send_and_confirm_owner_tx(vec![ix]).await
|
2022-08-05 06:24:23 -07:00
|
|
|
}
|
2022-08-04 08:01:00 -07:00
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
pub async fn jupiter_route(
|
2022-08-05 06:24:23 -07:00
|
|
|
&self,
|
|
|
|
input_mint: Pubkey,
|
|
|
|
output_mint: Pubkey,
|
2022-08-05 11:28:14 -07:00
|
|
|
amount: u64,
|
2023-02-03 02:34:06 -08:00
|
|
|
slippage: u64,
|
2022-08-05 11:28:14 -07:00
|
|
|
swap_mode: JupiterSwapMode,
|
2022-08-05 06:24:23 -07:00
|
|
|
) -> anyhow::Result<jupiter::QueryRoute> {
|
2022-08-04 08:01:00 -07:00
|
|
|
let quote = self
|
|
|
|
.http_client
|
2022-12-16 04:10:46 -08:00
|
|
|
.get("https://quote-api.jup.ag/v4/quote")
|
2022-08-04 08:01:00 -07:00
|
|
|
.query(&[
|
|
|
|
("inputMint", input_mint.to_string()),
|
|
|
|
("outputMint", output_mint.to_string()),
|
2022-08-05 11:28:14 -07:00
|
|
|
("amount", format!("{}", amount)),
|
2022-08-04 08:01:00 -07:00
|
|
|
("onlyDirectRoutes", "true".into()),
|
2022-12-16 04:10:46 -08:00
|
|
|
("enforceSingleTx", "true".into()),
|
2022-08-04 08:01:00 -07:00
|
|
|
("filterTopNResult", "10".into()),
|
2022-12-16 04:10:46 -08:00
|
|
|
("slippageBps", format!("{}", slippage)),
|
2022-08-05 11:28:14 -07:00
|
|
|
(
|
|
|
|
"swapMode",
|
|
|
|
match swap_mode {
|
|
|
|
JupiterSwapMode::ExactIn => "ExactIn",
|
|
|
|
JupiterSwapMode::ExactOut => "ExactOut",
|
|
|
|
}
|
|
|
|
.into(),
|
|
|
|
),
|
2022-08-04 08:01:00 -07:00
|
|
|
])
|
|
|
|
.send()
|
|
|
|
.await
|
|
|
|
.context("quote request to jupiter")?
|
|
|
|
.json::<jupiter::QueryResult>()
|
|
|
|
.await
|
|
|
|
.context("receiving json response from jupiter quote request")?;
|
|
|
|
|
|
|
|
// Find the top route that doesn't involve Raydium (that has too many accounts)
|
|
|
|
let route = quote
|
|
|
|
.data
|
|
|
|
.iter()
|
|
|
|
.find(|route| {
|
|
|
|
!route
|
|
|
|
.market_infos
|
|
|
|
.iter()
|
|
|
|
.any(|mi| mi.label.contains("Raydium"))
|
|
|
|
})
|
|
|
|
.ok_or_else(|| {
|
|
|
|
anyhow::anyhow!(
|
|
|
|
"no route for swap. found {} routes, but none were usable",
|
|
|
|
quote.data.len()
|
|
|
|
)
|
|
|
|
})?;
|
|
|
|
|
2022-08-05 06:24:23 -07:00
|
|
|
Ok(route.clone())
|
|
|
|
}
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
pub async fn jupiter_swap(
|
2022-08-05 06:24:23 -07:00
|
|
|
&self,
|
|
|
|
input_mint: Pubkey,
|
|
|
|
output_mint: Pubkey,
|
2022-08-05 11:28:14 -07:00
|
|
|
amount: u64,
|
2023-02-03 02:34:06 -08:00
|
|
|
slippage: u64,
|
2022-08-05 11:28:14 -07:00
|
|
|
swap_mode: JupiterSwapMode,
|
2022-08-05 06:24:23 -07:00
|
|
|
) -> anyhow::Result<Signature> {
|
|
|
|
let source_token = self.context.token_by_mint(&input_mint)?;
|
|
|
|
let target_token = self.context.token_by_mint(&output_mint)?;
|
|
|
|
let route = self
|
2022-12-16 04:10:46 -08:00
|
|
|
.jupiter_route(input_mint, output_mint, amount, slippage, swap_mode)
|
2022-08-05 06:24:23 -07:00
|
|
|
.await?;
|
|
|
|
|
2022-08-04 08:01:00 -07:00
|
|
|
let swap = self
|
|
|
|
.http_client
|
2022-12-16 04:10:46 -08:00
|
|
|
.post("https://quote-api.jup.ag/v4/swap")
|
2022-08-04 08:01:00 -07:00
|
|
|
.json(&jupiter::SwapRequest {
|
|
|
|
route: route.clone(),
|
|
|
|
user_public_key: self.owner.pubkey().to_string(),
|
|
|
|
wrap_unwrap_sol: false,
|
|
|
|
})
|
|
|
|
.send()
|
|
|
|
.await
|
|
|
|
.context("swap transaction request to jupiter")?
|
|
|
|
.json::<jupiter::SwapResponse>()
|
|
|
|
.await
|
|
|
|
.context("receiving json response from jupiter swap transaction request")?;
|
|
|
|
|
|
|
|
if swap.setup_transaction.is_some() || swap.cleanup_transaction.is_some() {
|
|
|
|
anyhow::bail!(
|
|
|
|
"chosen jupiter route requires setup or cleanup transactions, can't execute"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
let jup_tx = bincode::options()
|
|
|
|
.with_fixint_encoding()
|
|
|
|
.reject_trailing_bytes()
|
2022-12-16 04:10:46 -08:00
|
|
|
.deserialize::<solana_sdk::transaction::VersionedTransaction>(
|
2022-08-04 08:01:00 -07:00
|
|
|
&base64::decode(&swap.swap_transaction)
|
|
|
|
.context("base64 decoding jupiter transaction")?,
|
|
|
|
)
|
|
|
|
.context("parsing jupiter transaction")?;
|
2022-08-09 05:30:53 -07:00
|
|
|
let ata_program = anchor_spl::associated_token::ID;
|
|
|
|
let token_program = anchor_spl::token::ID;
|
|
|
|
let is_setup_ix = |k: Pubkey| -> bool { k == ata_program || k == token_program };
|
2022-12-16 04:10:46 -08:00
|
|
|
let (jup_ixs, jup_alts) = self
|
|
|
|
.deserialize_instructions_and_alts(&jup_tx.message)
|
|
|
|
.await?;
|
|
|
|
let filtered_jup_ix = jup_ixs
|
2022-08-04 08:01:00 -07:00
|
|
|
.into_iter()
|
2022-08-09 05:30:53 -07:00
|
|
|
.filter(|ix| !is_setup_ix(ix.program_id))
|
2022-08-04 08:01:00 -07:00
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
let bank_ams = [
|
|
|
|
source_token.mint_info.first_bank(),
|
|
|
|
target_token.mint_info.first_bank(),
|
|
|
|
]
|
|
|
|
.into_iter()
|
|
|
|
.map(to_writable_account_meta)
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
let vault_ams = [
|
|
|
|
source_token.mint_info.first_vault(),
|
|
|
|
target_token.mint_info.first_vault(),
|
|
|
|
]
|
|
|
|
.into_iter()
|
|
|
|
.map(to_writable_account_meta)
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
let token_ams = [source_token.mint_info.mint, target_token.mint_info.mint]
|
|
|
|
.into_iter()
|
|
|
|
.map(|mint| {
|
|
|
|
to_writable_account_meta(
|
|
|
|
anchor_spl::associated_token::get_associated_token_address(
|
|
|
|
&self.owner(),
|
|
|
|
&mint,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
2022-08-07 11:09:28 -07:00
|
|
|
let loan_amounts = vec![
|
|
|
|
match swap_mode {
|
|
|
|
JupiterSwapMode::ExactIn => amount,
|
|
|
|
// in amount + slippage
|
2022-12-16 04:10:46 -08:00
|
|
|
JupiterSwapMode::ExactOut => u64::from_str(&route.other_amount_threshold).unwrap(),
|
2022-08-07 11:09:28 -07:00
|
|
|
},
|
|
|
|
0u64,
|
|
|
|
];
|
2023-04-14 06:18:02 -07:00
|
|
|
let num_loans: u8 = loan_amounts.len().try_into().unwrap();
|
2022-08-04 08:01:00 -07:00
|
|
|
|
|
|
|
// This relies on the fact that health account banks will be identical to the first_bank above!
|
|
|
|
let health_ams = self
|
|
|
|
.derive_health_check_remaining_account_metas(
|
|
|
|
vec![source_token.token_index, target_token.token_index],
|
2023-01-18 01:50:23 -08:00
|
|
|
vec![source_token.token_index, target_token.token_index],
|
|
|
|
vec![],
|
2022-08-04 08:01:00 -07:00
|
|
|
)
|
2022-12-16 04:10:46 -08:00
|
|
|
.await
|
2022-08-04 08:01:00 -07:00
|
|
|
.context("building health accounts")?;
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
let mut instructions = Vec::new();
|
2022-08-09 05:30:53 -07:00
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
instructions.push(
|
2022-12-09 07:12:21 -08:00
|
|
|
spl_associated_token_account::instruction::create_associated_token_account_idempotent(
|
|
|
|
&self.owner.pubkey(),
|
|
|
|
&self.owner.pubkey(),
|
|
|
|
&source_token.mint_info.mint,
|
|
|
|
&Token::id(),
|
|
|
|
),
|
|
|
|
);
|
2022-12-16 04:10:46 -08:00
|
|
|
instructions.push(
|
2022-12-09 07:12:21 -08:00
|
|
|
spl_associated_token_account::instruction::create_associated_token_account_idempotent(
|
|
|
|
&self.owner.pubkey(),
|
|
|
|
&self.owner.pubkey(),
|
|
|
|
&target_token.mint_info.mint,
|
|
|
|
&Token::id(),
|
|
|
|
),
|
|
|
|
);
|
2022-12-16 04:10:46 -08:00
|
|
|
instructions.push(Instruction {
|
2022-08-04 08:01:00 -07:00
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: {
|
|
|
|
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
|
|
|
|
&mango_v4::accounts::FlashLoanBegin {
|
2022-11-09 00:35:13 -08:00
|
|
|
account: self.mango_account_address,
|
|
|
|
owner: self.owner(),
|
2022-08-04 08:01:00 -07:00
|
|
|
token_program: Token::id(),
|
|
|
|
instructions: solana_sdk::sysvar::instructions::id(),
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
ams.extend(bank_ams);
|
|
|
|
ams.extend(vault_ams.clone());
|
|
|
|
ams.extend(token_ams.clone());
|
2022-08-16 01:19:15 -07:00
|
|
|
ams.push(to_readonly_account_meta(self.group()));
|
2022-08-04 08:01:00 -07:00
|
|
|
ams
|
|
|
|
},
|
|
|
|
data: anchor_lang::InstructionData::data(&mango_v4::instruction::FlashLoanBegin {
|
|
|
|
loan_amounts,
|
|
|
|
}),
|
|
|
|
});
|
2022-12-16 04:10:46 -08:00
|
|
|
for ix in filtered_jup_ix {
|
|
|
|
instructions.push(ix.clone());
|
2022-08-04 08:01:00 -07:00
|
|
|
}
|
2022-12-16 04:10:46 -08:00
|
|
|
instructions.push(Instruction {
|
2022-08-04 08:01:00 -07:00
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: {
|
|
|
|
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
|
|
|
|
&mango_v4::accounts::FlashLoanEnd {
|
|
|
|
account: self.mango_account_address,
|
|
|
|
owner: self.owner(),
|
|
|
|
token_program: Token::id(),
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
ams.extend(health_ams);
|
|
|
|
ams.extend(vault_ams);
|
|
|
|
ams.extend(token_ams);
|
2022-08-16 01:19:15 -07:00
|
|
|
ams.push(to_readonly_account_meta(self.group()));
|
2022-08-04 08:01:00 -07:00
|
|
|
ams
|
|
|
|
},
|
2023-04-14 06:18:02 -07:00
|
|
|
data: anchor_lang::InstructionData::data(&mango_v4::instruction::FlashLoanEndV2 {
|
|
|
|
num_loans,
|
2023-02-14 23:42:07 -08:00
|
|
|
flash_loan_type: mango_v4::accounts_ix::FlashLoanType::Swap,
|
2022-08-17 03:36:55 -07:00
|
|
|
}),
|
2022-08-04 08:01:00 -07:00
|
|
|
});
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
let payer = self.owner.pubkey(); // maybe use fee_payer? but usually it's the same
|
|
|
|
let mut address_lookup_tables = self.mango_address_lookup_tables().await?;
|
|
|
|
address_lookup_tables.extend(jup_alts.into_iter());
|
|
|
|
|
|
|
|
TransactionBuilder {
|
|
|
|
instructions,
|
|
|
|
address_lookup_tables,
|
|
|
|
payer,
|
2023-02-10 05:22:07 -08:00
|
|
|
signers: vec![&*self.owner],
|
2023-02-20 03:09:17 -08:00
|
|
|
config: self.client.transaction_builder_config,
|
2022-12-16 04:10:46 -08:00
|
|
|
}
|
2023-02-02 05:23:33 -08:00
|
|
|
.send_and_confirm(&self.client)
|
2022-12-16 04:10:46 -08:00
|
|
|
.await
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn fetch_address_lookup_table(
|
|
|
|
&self,
|
|
|
|
address: Pubkey,
|
|
|
|
) -> anyhow::Result<AddressLookupTableAccount> {
|
|
|
|
let raw = self
|
|
|
|
.account_fetcher
|
|
|
|
.fetch_raw_account_lookup_table(&address)
|
|
|
|
.await?;
|
|
|
|
let data = AddressLookupTable::deserialize(&raw.data())?;
|
|
|
|
Ok(AddressLookupTableAccount {
|
|
|
|
key: address,
|
|
|
|
addresses: data.addresses.to_vec(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-02-20 03:09:17 -08:00
|
|
|
pub async fn mango_address_lookup_tables(
|
|
|
|
&self,
|
|
|
|
) -> anyhow::Result<Vec<AddressLookupTableAccount>> {
|
2022-12-16 04:10:46 -08:00
|
|
|
stream::iter(self.context.address_lookup_tables.iter())
|
|
|
|
.then(|&k| self.fetch_address_lookup_table(k))
|
|
|
|
.try_collect::<Vec<_>>()
|
2022-08-04 08:01:00 -07:00
|
|
|
.await
|
|
|
|
}
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
async fn deserialize_instructions_and_alts(
|
|
|
|
&self,
|
|
|
|
message: &solana_sdk::message::VersionedMessage,
|
|
|
|
) -> anyhow::Result<(Vec<Instruction>, Vec<AddressLookupTableAccount>)> {
|
|
|
|
let lookups = message.address_table_lookups().unwrap_or_default();
|
|
|
|
let address_lookup_tables = stream::iter(lookups)
|
|
|
|
.then(|a| self.fetch_address_lookup_table(a.account_key))
|
|
|
|
.try_collect::<Vec<_>>()
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
let mut account_keys = message.static_account_keys().to_vec();
|
|
|
|
for (lookups, table) in lookups.iter().zip(address_lookup_tables.iter()) {
|
|
|
|
account_keys.extend(
|
|
|
|
lookups
|
|
|
|
.writable_indexes
|
|
|
|
.iter()
|
|
|
|
.map(|&index| table.addresses[index as usize]),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
for (lookups, table) in lookups.iter().zip(address_lookup_tables.iter()) {
|
|
|
|
account_keys.extend(
|
|
|
|
lookups
|
|
|
|
.readonly_indexes
|
|
|
|
.iter()
|
|
|
|
.map(|&index| table.addresses[index as usize]),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
let compiled_ix = message
|
|
|
|
.instructions()
|
|
|
|
.iter()
|
|
|
|
.map(|ci| solana_sdk::instruction::Instruction {
|
|
|
|
program_id: *ci.program_id(&account_keys),
|
|
|
|
accounts: ci
|
|
|
|
.accounts
|
|
|
|
.iter()
|
|
|
|
.map(|&index| AccountMeta {
|
|
|
|
pubkey: account_keys[index as usize],
|
|
|
|
is_signer: message.is_signer(index.into()),
|
|
|
|
is_writable: message.is_maybe_writable(index.into()),
|
|
|
|
})
|
|
|
|
.collect(),
|
|
|
|
data: ci.data.clone(),
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
Ok((compiled_ix, address_lookup_tables))
|
2022-08-04 08:01:00 -07:00
|
|
|
}
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
pub async fn send_and_confirm_owner_tx(
|
|
|
|
&self,
|
|
|
|
instructions: Vec<Instruction>,
|
|
|
|
) -> anyhow::Result<Signature> {
|
|
|
|
TransactionBuilder {
|
|
|
|
instructions,
|
|
|
|
address_lookup_tables: vec![],
|
|
|
|
payer: self.client.fee_payer.pubkey(),
|
2023-02-10 05:22:07 -08:00
|
|
|
signers: vec![&*self.owner, &*self.client.fee_payer],
|
2023-02-20 03:09:17 -08:00
|
|
|
config: self.client.transaction_builder_config,
|
2022-12-16 04:10:46 -08:00
|
|
|
}
|
2023-02-02 05:23:33 -08:00
|
|
|
.send_and_confirm(&self.client)
|
2022-12-16 04:10:46 -08:00
|
|
|
.await
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn send_and_confirm_permissionless_tx(
|
|
|
|
&self,
|
|
|
|
instructions: Vec<Instruction>,
|
|
|
|
) -> anyhow::Result<Signature> {
|
|
|
|
TransactionBuilder {
|
|
|
|
instructions,
|
|
|
|
address_lookup_tables: vec![],
|
|
|
|
payer: self.client.fee_payer.pubkey(),
|
|
|
|
signers: vec![&*self.client.fee_payer],
|
2023-02-20 03:09:17 -08:00
|
|
|
config: self.client.transaction_builder_config,
|
2022-12-16 04:10:46 -08:00
|
|
|
}
|
2023-02-02 05:23:33 -08:00
|
|
|
.send_and_confirm(&self.client)
|
2022-12-16 04:10:46 -08:00
|
|
|
.await
|
|
|
|
}
|
2022-05-27 22:05:34 -07:00
|
|
|
}
|
|
|
|
|
2022-07-16 05:37:15 -07:00
|
|
|
struct Serum3Data<'a> {
|
|
|
|
market_index: Serum3MarketIndex,
|
|
|
|
market: &'a Serum3MarketContext,
|
|
|
|
quote: &'a TokenContext,
|
|
|
|
base: &'a TokenContext,
|
2022-05-27 22:05:34 -07:00
|
|
|
}
|
2022-07-15 01:00:01 -07:00
|
|
|
|
2022-07-19 05:56:26 -07:00
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
|
|
pub enum MangoClientError {
|
2023-02-16 01:55:35 -08:00
|
|
|
#[error("Transaction simulation error. Error: {err:?}, Logs: {}",
|
|
|
|
.logs.iter().join("; ")
|
|
|
|
)]
|
|
|
|
SendTransactionPreflightFailure {
|
|
|
|
err: Option<TransactionError>,
|
|
|
|
logs: Vec<String>,
|
|
|
|
},
|
2022-07-19 05:56:26 -07:00
|
|
|
}
|
|
|
|
|
2023-02-20 03:09:17 -08:00
|
|
|
#[derive(Copy, Clone, Debug)]
|
|
|
|
pub struct TransactionBuilderConfig {
|
|
|
|
// adds a SetComputeUnitPrice instruction in front
|
|
|
|
pub prioritization_micro_lamports: Option<u64>,
|
|
|
|
}
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
pub struct TransactionBuilder<'a> {
|
2023-02-20 03:09:17 -08:00
|
|
|
pub instructions: Vec<Instruction>,
|
|
|
|
pub address_lookup_tables: Vec<AddressLookupTableAccount>,
|
|
|
|
pub signers: Vec<&'a Keypair>,
|
|
|
|
pub payer: Pubkey,
|
|
|
|
pub config: TransactionBuilderConfig,
|
2022-12-16 04:10:46 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> TransactionBuilder<'a> {
|
|
|
|
pub async fn transaction(
|
|
|
|
self,
|
|
|
|
rpc: &RpcClientAsync,
|
|
|
|
) -> anyhow::Result<solana_sdk::transaction::VersionedTransaction> {
|
|
|
|
let latest_blockhash = rpc.get_latest_blockhash().await?;
|
|
|
|
self.transaction_with_blockhash(latest_blockhash)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn transaction_with_blockhash(
|
2023-02-20 03:09:17 -08:00
|
|
|
mut self,
|
2022-12-16 04:10:46 -08:00
|
|
|
blockhash: Hash,
|
|
|
|
) -> anyhow::Result<solana_sdk::transaction::VersionedTransaction> {
|
2023-02-20 03:09:17 -08:00
|
|
|
if let Some(prio_price) = self.config.prioritization_micro_lamports {
|
|
|
|
self.instructions.insert(
|
|
|
|
0,
|
|
|
|
solana_sdk::compute_budget::ComputeBudgetInstruction::set_compute_unit_price(
|
|
|
|
prio_price,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
2022-12-16 04:10:46 -08:00
|
|
|
let v0_message = solana_sdk::message::v0::Message::try_compile(
|
|
|
|
&self.payer,
|
|
|
|
&self.instructions,
|
|
|
|
&self.address_lookup_tables,
|
|
|
|
blockhash,
|
|
|
|
)?;
|
|
|
|
let versioned_message = solana_sdk::message::VersionedMessage::V0(v0_message);
|
|
|
|
let signers = self
|
|
|
|
.signers
|
|
|
|
.into_iter()
|
|
|
|
.unique_by(|s| s.pubkey())
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
let tx =
|
|
|
|
solana_sdk::transaction::VersionedTransaction::try_new(versioned_message, &signers)?;
|
|
|
|
Ok(tx)
|
|
|
|
}
|
|
|
|
|
2023-02-20 03:09:17 -08:00
|
|
|
// These two send() functions don't really belong into the transaction builder!
|
|
|
|
|
|
|
|
pub async fn send(self, client: &Client) -> anyhow::Result<Signature> {
|
|
|
|
let rpc = client.rpc_async();
|
|
|
|
let tx = self.transaction(&rpc).await?;
|
|
|
|
rpc.send_transaction_with_config(&tx, client.rpc_send_transaction_config)
|
|
|
|
.await
|
|
|
|
.map_err(prettify_solana_client_error)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn send_and_confirm(self, client: &Client) -> anyhow::Result<Signature> {
|
2023-02-02 05:23:33 -08:00
|
|
|
let rpc = client.rpc_async();
|
|
|
|
let tx = self.transaction(&rpc).await?;
|
2023-02-20 03:09:17 -08:00
|
|
|
// TODO: Wish we could use client.rpc_send_transaction_config here too!
|
2022-12-16 04:10:46 -08:00
|
|
|
rpc.send_and_confirm_transaction(&tx)
|
|
|
|
.await
|
|
|
|
.map_err(prettify_solana_client_error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-15 01:00:01 -07:00
|
|
|
/// Do some manual unpacking on some ClientErrors
|
|
|
|
///
|
|
|
|
/// Unfortunately solana's RpcResponseError will very unhelpfully print [N log messages]
|
|
|
|
/// instead of showing the actual log messages. This unpacks the error to provide more useful
|
|
|
|
/// output.
|
2022-07-31 00:25:11 -07:00
|
|
|
pub fn prettify_client_error(err: anchor_client::ClientError) -> anyhow::Error {
|
2022-12-16 04:10:46 -08:00
|
|
|
match err {
|
|
|
|
anchor_client::ClientError::SolanaClientError(c) => prettify_solana_client_error(c),
|
|
|
|
_ => err.into(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn prettify_solana_client_error(
|
|
|
|
err: solana_client::client_error::ClientError,
|
|
|
|
) -> anyhow::Error {
|
2022-07-15 01:00:01 -07:00
|
|
|
use solana_client::client_error::ClientErrorKind;
|
|
|
|
use solana_client::rpc_request::{RpcError, RpcResponseErrorData};
|
2022-12-16 04:10:46 -08:00
|
|
|
match err.kind() {
|
|
|
|
ClientErrorKind::RpcError(RpcError::RpcResponseError { data, .. }) => match data {
|
|
|
|
RpcResponseErrorData::SendTransactionPreflightFailure(s) => {
|
2023-02-16 01:55:35 -08:00
|
|
|
return MangoClientError::SendTransactionPreflightFailure {
|
|
|
|
err: s.err.clone(),
|
|
|
|
logs: s.logs.clone().unwrap_or_default(),
|
2022-12-16 04:10:46 -08:00
|
|
|
}
|
2023-02-16 01:55:35 -08:00
|
|
|
.into();
|
2022-12-16 04:10:46 -08:00
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
},
|
2022-07-15 01:00:01 -07:00
|
|
|
_ => {}
|
|
|
|
};
|
|
|
|
err.into()
|
|
|
|
}
|
2022-07-31 00:25:11 -07:00
|
|
|
|
2022-08-07 11:09:28 -07:00
|
|
|
#[derive(Clone, Copy)]
|
2022-08-05 11:28:14 -07:00
|
|
|
pub enum JupiterSwapMode {
|
|
|
|
ExactIn,
|
|
|
|
ExactOut,
|
|
|
|
}
|
|
|
|
|
2022-07-31 00:25:11 -07:00
|
|
|
pub fn keypair_from_cli(keypair: &str) -> Keypair {
|
|
|
|
let maybe_keypair = keypair::read_keypair(&mut keypair.as_bytes());
|
|
|
|
match maybe_keypair {
|
|
|
|
Ok(keypair) => keypair,
|
|
|
|
Err(_) => {
|
|
|
|
let path = std::path::PathBuf::from_str(&*shellexpand::tilde(keypair)).unwrap();
|
|
|
|
keypair::read_keypair_file(path)
|
|
|
|
.unwrap_or_else(|_| panic!("Failed to read keypair from {}", keypair))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-08-01 08:53:13 -07:00
|
|
|
|
|
|
|
pub fn pubkey_from_cli(pubkey: &str) -> Pubkey {
|
|
|
|
match Pubkey::from_str(pubkey) {
|
|
|
|
Ok(p) => p,
|
|
|
|
Err(_) => keypair_from_cli(pubkey).pubkey(),
|
|
|
|
}
|
|
|
|
}
|
2022-08-04 08:01:00 -07:00
|
|
|
|
|
|
|
fn to_readonly_account_meta(pubkey: Pubkey) -> AccountMeta {
|
|
|
|
AccountMeta {
|
|
|
|
pubkey,
|
|
|
|
is_writable: false,
|
|
|
|
is_signer: false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn to_writable_account_meta(pubkey: Pubkey) -> AccountMeta {
|
|
|
|
AccountMeta {
|
|
|
|
pubkey,
|
|
|
|
is_writable: true,
|
|
|
|
is_signer: false,
|
|
|
|
}
|
|
|
|
}
|