2023-07-10 01:40:48 -07:00
|
|
|
use std::ops::Deref;
|
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
|
|
|
|
2023-06-15 08:20:31 -07:00
|
|
|
use anchor_client::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};
|
2023-07-03 05:09:11 -07:00
|
|
|
use mango_v4::accounts_zerocopy::KeyedAccountSharedData;
|
2022-09-15 00:57:48 -07:00
|
|
|
use mango_v4::state::{
|
2023-05-15 01:40:41 -07:00
|
|
|
Bank, Group, MangoAccountValue, PerpMarketIndex, PlaceOrderType, SelfTradeBehavior,
|
|
|
|
Serum3MarketIndex, Side, TokenIndex, INSURANCE_TOKEN_INDEX,
|
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::*;
|
2023-06-15 08:20:31 -07:00
|
|
|
use crate::context::MangoGroupContext;
|
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,
|
2023-07-10 01:40:48 -07:00
|
|
|
owner: Arc<Keypair>,
|
|
|
|
payer: Arc<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;
|
2023-07-10 01:40:48 -07:00
|
|
|
let owner_pk = owner.pubkey();
|
2022-07-13 05:04:20 -07:00
|
|
|
|
2022-05-29 03:25:12 -07:00
|
|
|
// Mango Account
|
2023-07-10 01:40:48 -07:00
|
|
|
let mut mango_account_tuples = fetch_mango_accounts(&rpc, program, group, owner_pk).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
|
|
|
};
|
2023-07-10 01:40:48 -07:00
|
|
|
Self::create_account(
|
|
|
|
client,
|
|
|
|
group,
|
|
|
|
owner.clone(),
|
|
|
|
payer,
|
|
|
|
account_num,
|
|
|
|
mango_account_name,
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
.context("Failed to create account...")?;
|
2022-05-29 03:25:12 -07:00
|
|
|
}
|
2023-07-10 01:40:48 -07:00
|
|
|
let mango_account_tuples = fetch_mango_accounts(&rpc, program, group, owner_pk).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,
|
2023-07-10 01:40:48 -07:00
|
|
|
owner: Arc<Keypair>,
|
|
|
|
payer: Arc<Keypair>, // pays the SOL for the new account
|
2022-08-04 08:01:00 -07:00
|
|
|
account_num: u32,
|
|
|
|
mango_account_name: &str,
|
|
|
|
) -> anyhow::Result<(Pubkey, Signature)> {
|
|
|
|
let account = Pubkey::find_program_address(
|
|
|
|
&[
|
|
|
|
b"MangoAccount".as_ref(),
|
2023-07-07 08:20:03 -07:00
|
|
|
group.as_ref(),
|
2022-08-04 08:01:00 -07:00
|
|
|
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,
|
2023-04-17 02:30:27 -07:00
|
|
|
affected_tokens: Vec<u16>,
|
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,
|
2023-04-17 02:30:27 -07:00
|
|
|
&affected_tokens,
|
2022-09-29 03:59:55 -07:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2023-07-03 05:09:11 -07:00
|
|
|
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 oracle = self
|
|
|
|
.account_fetcher
|
|
|
|
.fetch_raw_account(&mint_info.oracle)
|
|
|
|
.await?;
|
|
|
|
let price = bank.oracle_price(
|
|
|
|
&KeyedAccountSharedData::new(mint_info.oracle, oracle.into()),
|
|
|
|
None,
|
|
|
|
)?;
|
|
|
|
Ok(price)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn perp_oracle_price(
|
|
|
|
&self,
|
|
|
|
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()),
|
|
|
|
None,
|
|
|
|
)?;
|
|
|
|
Ok(price)
|
|
|
|
}
|
|
|
|
|
2022-05-27 22:05:34 -07:00
|
|
|
//
|
|
|
|
// Serum3
|
|
|
|
//
|
|
|
|
|
2023-06-15 08:20:31 -07:00
|
|
|
pub fn serum3_create_open_orders_instruction(
|
|
|
|
&self,
|
|
|
|
market_index: Serum3MarketIndex,
|
|
|
|
) -> Instruction {
|
2022-07-16 05:37:15 -07:00
|
|
|
let account_pubkey = self.mango_account_address;
|
2023-06-15 08:20:31 -07:00
|
|
|
let s3 = self.context.serum3(market_index);
|
2022-05-27 22:05:34 -07:00
|
|
|
|
|
|
|
let open_orders = Pubkey::find_program_address(
|
|
|
|
&[
|
|
|
|
b"Serum3OO".as_ref(),
|
2023-06-15 08:20:31 -07:00
|
|
|
account_pubkey.as_ref(),
|
|
|
|
s3.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;
|
|
|
|
|
2023-06-15 08:20:31 -07:00
|
|
|
Instruction {
|
2022-12-16 04:10:46 -08:00
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: anchor_lang::ToAccountMetas::to_account_metas(
|
|
|
|
&mango_v4::accounts::Serum3CreateOpenOrders {
|
|
|
|
group: self.group(),
|
|
|
|
account: account_pubkey,
|
2023-06-15 08:20:31 -07:00
|
|
|
serum_market: s3.address,
|
|
|
|
serum_program: s3.market.serum_program,
|
|
|
|
serum_market_external: s3.market.serum_market_external,
|
2022-12-16 04:10:46 -08:00
|
|
|
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 {},
|
|
|
|
),
|
2023-06-15 08:20:31 -07:00
|
|
|
}
|
2022-08-31 05:41:04 -07:00
|
|
|
}
|
|
|
|
|
2023-06-15 08:20:31 -07:00
|
|
|
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);
|
|
|
|
self.send_and_confirm_owner_tx(vec![ix]).await
|
2022-07-16 05:37:15 -07:00
|
|
|
}
|
|
|
|
|
2022-05-27 22:05:34 -07:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
2023-06-15 08:20:31 -07:00
|
|
|
pub fn serum3_place_order_instruction(
|
2022-05-27 22:05:34 -07:00
|
|
|
&self,
|
2023-06-15 08:20:31 -07:00
|
|
|
account: &MangoAccountValue,
|
|
|
|
market_index: Serum3MarketIndex,
|
2022-05-27 22:05:34 -07:00
|
|
|
side: Serum3Side,
|
2023-06-15 08:20:31 -07:00
|
|
|
limit_price: u64,
|
|
|
|
max_base_qty: u64,
|
|
|
|
max_native_quote_qty_including_fees: u64,
|
2022-05-27 22:05:34 -07:00
|
|
|
self_trade_behavior: Serum3SelfTradeBehavior,
|
|
|
|
order_type: Serum3OrderType,
|
|
|
|
client_order_id: u64,
|
|
|
|
limit: u16,
|
2023-06-15 08:20:31 -07:00
|
|
|
) -> anyhow::Result<Instruction> {
|
|
|
|
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 health_check_metas = self.context.derive_health_check_remaining_account_metas(
|
|
|
|
account,
|
|
|
|
vec![],
|
|
|
|
vec![],
|
|
|
|
vec![],
|
|
|
|
)?;
|
2022-05-27 22:05:34 -07:00
|
|
|
|
2022-08-30 00:55:19 -07:00
|
|
|
let payer_mint_info = match side {
|
2023-06-15 08:20:31 -07:00
|
|
|
Serum3Side::Bid => quote.mint_info,
|
|
|
|
Serum3Side::Ask => base.mint_info,
|
2022-08-30 00:55:19 -07:00
|
|
|
};
|
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,
|
2023-06-15 08:20:31 -07:00
|
|
|
serum_market: s3.address,
|
|
|
|
serum_program: s3.market.serum_program,
|
|
|
|
serum_market_external: s3.market.serum_market_external,
|
|
|
|
market_bids: s3.bids,
|
|
|
|
market_asks: s3.asks,
|
|
|
|
market_event_queue: s3.event_q,
|
|
|
|
market_request_queue: s3.req_q,
|
|
|
|
market_base_vault: s3.coin_vault,
|
|
|
|
market_quote_vault: s3.pc_vault,
|
|
|
|
market_vault_signer: s3.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,
|
|
|
|
}),
|
|
|
|
};
|
2023-06-15 08:20:31 -07:00
|
|
|
|
|
|
|
Ok(ix)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
|
|
pub async fn serum3_place_order(
|
|
|
|
&self,
|
|
|
|
name: &str,
|
|
|
|
side: Serum3Side,
|
|
|
|
limit_price: u64,
|
|
|
|
max_base_qty: u64,
|
|
|
|
max_native_quote_qty_including_fees: u64,
|
|
|
|
self_trade_behavior: Serum3SelfTradeBehavior,
|
|
|
|
order_type: Serum3OrderType,
|
|
|
|
client_order_id: u64,
|
|
|
|
limit: u16,
|
|
|
|
) -> anyhow::Result<Signature> {
|
|
|
|
let account = self.mango_account().await?;
|
|
|
|
let market_index = self.context.serum3_market_index(name);
|
|
|
|
let ix = self.serum3_place_order_instruction(
|
|
|
|
&account,
|
|
|
|
market_index,
|
|
|
|
side,
|
|
|
|
limit_price,
|
|
|
|
max_base_qty,
|
|
|
|
max_native_quote_qty_including_fees,
|
|
|
|
self_trade_behavior,
|
|
|
|
order_type,
|
|
|
|
client_order_id,
|
|
|
|
limit,
|
|
|
|
)?;
|
2022-12-16 04:10:46 -08:00
|
|
|
self.send_and_confirm_owner_tx(vec![ix]).await
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn serum3_settle_funds(&self, name: &str) -> anyhow::Result<Signature> {
|
2023-06-15 08:20:31 -07:00
|
|
|
let market_index = self.context.serum3_market_index(name);
|
|
|
|
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);
|
2022-12-16 04:10:46 -08:00
|
|
|
|
|
|
|
let account = self.mango_account().await?;
|
2023-06-15 08:20:31 -07:00
|
|
|
let open_orders = account.serum3_orders(market_index).unwrap().open_orders;
|
2022-12-16 04:10:46 -08:00
|
|
|
|
|
|
|
let ix = Instruction {
|
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: anchor_lang::ToAccountMetas::to_account_metas(
|
2023-06-15 08:34:56 -07:00
|
|
|
&mango_v4::accounts::Serum3SettleFundsV2 {
|
|
|
|
v1: mango_v4::accounts::Serum3SettleFunds {
|
|
|
|
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(),
|
|
|
|
serum_market: s3.address,
|
|
|
|
serum_program: s3.market.serum_program,
|
|
|
|
serum_market_external: s3.market.serum_market_external,
|
|
|
|
market_base_vault: s3.coin_vault,
|
|
|
|
market_quote_vault: s3.pc_vault,
|
|
|
|
market_vault_signer: s3.vault_signer,
|
|
|
|
owner: self.owner(),
|
|
|
|
token_program: Token::id(),
|
|
|
|
},
|
|
|
|
v2: mango_v4::accounts::Serum3SettleFundsV2Extra {
|
|
|
|
quote_oracle: quote.mint_info.oracle,
|
|
|
|
base_oracle: base.mint_info.oracle,
|
|
|
|
},
|
2022-12-16 04:10:46 -08:00
|
|
|
},
|
|
|
|
None,
|
|
|
|
),
|
2023-06-15 08:34:56 -07:00
|
|
|
data: anchor_lang::InstructionData::data(&mango_v4::instruction::Serum3SettleFundsV2 {
|
|
|
|
fees_to_dao: true,
|
|
|
|
}),
|
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
|
|
|
}
|
|
|
|
|
2023-06-15 08:20:31 -07:00
|
|
|
pub fn serum3_cancel_all_orders_instruction(
|
|
|
|
&self,
|
|
|
|
account: &MangoAccountValue,
|
|
|
|
market_index: Serum3MarketIndex,
|
|
|
|
limit: u8,
|
|
|
|
) -> anyhow::Result<Instruction> {
|
|
|
|
let s3 = self.context.serum3(market_index);
|
|
|
|
let open_orders = account.serum3_orders(market_index)?.open_orders;
|
|
|
|
|
|
|
|
let ix = Instruction {
|
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: anchor_lang::ToAccountMetas::to_account_metas(
|
|
|
|
&mango_v4::accounts::Serum3CancelAllOrders {
|
|
|
|
group: self.group(),
|
|
|
|
account: self.mango_account_address,
|
|
|
|
open_orders,
|
|
|
|
market_bids: s3.bids,
|
|
|
|
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,
|
|
|
|
owner: self.owner(),
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
data: anchor_lang::InstructionData::data(
|
|
|
|
&mango_v4::instruction::Serum3CancelAllOrders { limit },
|
|
|
|
),
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(ix)
|
|
|
|
}
|
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
pub async fn serum3_cancel_all_orders(
|
|
|
|
&self,
|
|
|
|
market_name: &str,
|
|
|
|
) -> Result<Vec<u128>, anyhow::Error> {
|
2023-06-15 08:20:31 -07:00
|
|
|
let market_index = self.context.serum3_market_index(market_name);
|
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> {
|
2023-06-15 08:20:31 -07:00
|
|
|
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);
|
2022-08-31 05:41:04 -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-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,
|
2023-06-15 08:20:31 -07:00
|
|
|
serum_market: s3.address,
|
|
|
|
serum_program: s3.market.serum_program,
|
|
|
|
serum_market_external: s3.market.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(),
|
2022-12-16 04:10:46 -08:00
|
|
|
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> {
|
2023-06-15 08:20:31 -07:00
|
|
|
let market_index = self.context.serum3_market_index(market_name);
|
|
|
|
let s3 = self.context.serum3(market_index);
|
2022-05-27 22:05:34 -07:00
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
let account = self.mango_account().await?;
|
2023-06-15 08:20:31 -07:00
|
|
|
let open_orders = account.serum3_orders(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,
|
2023-06-15 08:20:31 -07:00
|
|
|
serum_market: s3.address,
|
|
|
|
serum_program: s3.market.serum_program,
|
|
|
|
serum_market_external: s3.market.serum_market_external,
|
2022-12-16 04:10:46 -08:00
|
|
|
open_orders,
|
2023-06-15 08:20:31 -07:00
|
|
|
market_bids: s3.bids,
|
|
|
|
market_asks: s3.asks,
|
|
|
|
market_event_queue: s3.event_q,
|
2022-12-16 04:10:46 -08:00
|
|
|
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-06-15 08:20:31 -07:00
|
|
|
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
2023-05-09 03:46:45 -07:00
|
|
|
pub fn perp_place_order_instruction(
|
2023-01-18 01:50:23 -08:00
|
|
|
&self,
|
2023-05-09 03:46:45 -07:00
|
|
|
account: &MangoAccountValue,
|
2023-01-18 01:50:23 -08:00
|
|
|
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,
|
2023-05-15 01:40:41 -07:00
|
|
|
self_trade_behavior: SelfTradeBehavior,
|
2023-05-09 03:46:45 -07:00
|
|
|
) -> anyhow::Result<Instruction> {
|
2023-01-18 01:50:23 -08:00
|
|
|
let perp = self.context.perp(market_index);
|
2023-05-09 03:46:45 -07:00
|
|
|
let health_remaining_metas = self.context.derive_health_check_remaining_account_metas(
|
|
|
|
account,
|
|
|
|
vec![],
|
|
|
|
vec![],
|
|
|
|
vec![market_index],
|
|
|
|
)?;
|
2023-01-18 01:50:23 -08:00
|
|
|
|
|
|
|
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,
|
|
|
|
);
|
2023-05-09 03:46:45 -07:00
|
|
|
ams.extend(health_remaining_metas.into_iter());
|
2023-01-18 01:50:23 -08:00
|
|
|
ams
|
|
|
|
},
|
2023-05-15 01:40:41 -07:00
|
|
|
data: anchor_lang::InstructionData::data(&mango_v4::instruction::PerpPlaceOrderV2 {
|
2023-01-18 01:50:23 -08:00
|
|
|
side,
|
|
|
|
price_lots,
|
|
|
|
max_base_lots,
|
|
|
|
max_quote_lots,
|
|
|
|
client_order_id,
|
|
|
|
order_type,
|
|
|
|
reduce_only,
|
|
|
|
expiry_timestamp,
|
|
|
|
limit,
|
2023-05-15 01:40:41 -07:00
|
|
|
self_trade_behavior,
|
2023-01-18 01:50:23 -08:00
|
|
|
}),
|
|
|
|
};
|
2023-05-09 03:46:45 -07:00
|
|
|
|
|
|
|
Ok(ix)
|
|
|
|
}
|
|
|
|
|
2023-06-15 08:20:31 -07:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
2023-05-09 03:46:45 -07: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,
|
2023-05-15 01:40:41 -07:00
|
|
|
self_trade_behavior: SelfTradeBehavior,
|
2023-05-09 03:46:45 -07:00
|
|
|
) -> anyhow::Result<Signature> {
|
|
|
|
let account = self.mango_account().await?;
|
|
|
|
let ix = self.perp_place_order_instruction(
|
|
|
|
&account,
|
|
|
|
market_index,
|
|
|
|
side,
|
|
|
|
price_lots,
|
|
|
|
max_base_lots,
|
|
|
|
max_quote_lots,
|
|
|
|
client_order_id,
|
|
|
|
order_type,
|
|
|
|
reduce_only,
|
|
|
|
expiry_timestamp,
|
|
|
|
limit,
|
2023-05-15 01:40:41 -07:00
|
|
|
self_trade_behavior,
|
2023-05-09 03:46:45 -07:00
|
|
|
)?;
|
2023-01-18 01:50:23 -08:00
|
|
|
self.send_and_confirm_owner_tx(vec![ix]).await
|
|
|
|
}
|
|
|
|
|
2023-05-09 03:46:45 -07:00
|
|
|
pub fn perp_cancel_all_orders_instruction(
|
|
|
|
&self,
|
|
|
|
market_index: PerpMarketIndex,
|
|
|
|
limit: u8,
|
|
|
|
) -> anyhow::Result<Instruction> {
|
|
|
|
let perp = self.context.perp(market_index);
|
|
|
|
|
|
|
|
let ix = Instruction {
|
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: {
|
|
|
|
anchor_lang::ToAccountMetas::to_account_metas(
|
|
|
|
&mango_v4::accounts::PerpCancelAllOrders {
|
|
|
|
group: self.group(),
|
|
|
|
account: self.mango_account_address,
|
|
|
|
owner: self.owner(),
|
|
|
|
perp_market: perp.address,
|
|
|
|
bids: perp.market.bids,
|
|
|
|
asks: perp.market.asks,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
)
|
|
|
|
},
|
|
|
|
data: anchor_lang::InstructionData::data(&mango_v4::instruction::PerpCancelAllOrders {
|
|
|
|
limit,
|
|
|
|
}),
|
|
|
|
};
|
|
|
|
Ok(ix)
|
|
|
|
}
|
|
|
|
|
2023-01-18 01:50:23 -08:00
|
|
|
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
|
2023-04-17 02:30:27 -07:00
|
|
|
.derive_health_check_remaining_account_metas_two_accounts(
|
|
|
|
account_a.1,
|
|
|
|
account_b.1,
|
2023-04-19 05:15:32 -07:00
|
|
|
&[],
|
2023-04-17 02:30:27 -07:00
|
|
|
&[],
|
|
|
|
)
|
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
|
2023-04-17 02:30:27 -07:00
|
|
|
.derive_liquidation_health_check_remaining_account_metas(liqee.1, vec![], &[])
|
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);
|
2023-05-17 06:50:05 -07:00
|
|
|
let insurance_token_info = self.context.token(INSURANCE_TOKEN_INDEX);
|
2022-09-27 02:36:58 -07:00
|
|
|
|
|
|
|
let health_remaining_ams = self
|
2023-04-17 02:30:27 -07:00
|
|
|
.derive_liquidation_health_check_remaining_account_metas(
|
|
|
|
liqee.1,
|
|
|
|
vec![INSURANCE_TOKEN_INDEX],
|
|
|
|
&[],
|
|
|
|
)
|
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-05-17 06:50:05 -07:00
|
|
|
&mango_v4::accounts::PerpLiqNegativePnlOrBankruptcyV2 {
|
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,
|
2023-05-17 06:50:05 -07:00
|
|
|
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,
|
2022-12-16 04:10:46 -08:00
|
|
|
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-05-17 06:50:05 -07:00
|
|
|
&mango_v4::instruction::PerpLiqNegativePnlOrBankruptcyV2 { 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,
|
2023-04-17 02:30:27 -07:00
|
|
|
vec![],
|
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,
|
2023-04-17 02:30:27 -07:00
|
|
|
vec![INSURANCE_TOKEN_INDEX],
|
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
|
|
|
|
2023-07-03 05:09:11 -07:00
|
|
|
pub async fn token_conditional_swap_trigger(
|
|
|
|
&self,
|
|
|
|
liqee: (&Pubkey, &MangoAccountValue),
|
|
|
|
token_conditional_swap_id: u64,
|
|
|
|
max_buy_token_to_liqee: u64,
|
|
|
|
max_sell_token_to_liqor: u64,
|
|
|
|
) -> anyhow::Result<Signature> {
|
|
|
|
let (tcs_index, tcs) = liqee
|
|
|
|
.1
|
|
|
|
.token_conditional_swap_by_id(token_conditional_swap_id)?;
|
|
|
|
|
|
|
|
let health_remaining_ams = self
|
|
|
|
.derive_liquidation_health_check_remaining_account_metas(
|
|
|
|
liqee.1,
|
|
|
|
vec![tcs.buy_token_index, tcs.sell_token_index],
|
|
|
|
&[tcs.buy_token_index, tcs.sell_token_index],
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let ix = Instruction {
|
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: {
|
|
|
|
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
|
|
|
|
&mango_v4::accounts::TokenConditionalSwapTrigger {
|
|
|
|
group: self.group(),
|
|
|
|
liqee: *liqee.0,
|
|
|
|
liqor: self.mango_account_address,
|
|
|
|
liqor_authority: self.owner(),
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
ams.extend(health_remaining_ams);
|
|
|
|
ams
|
|
|
|
},
|
|
|
|
data: anchor_lang::InstructionData::data(
|
|
|
|
&mango_v4::instruction::TokenConditionalSwapTrigger {
|
|
|
|
token_conditional_swap_id,
|
|
|
|
token_conditional_swap_index: tcs_index.try_into().unwrap(),
|
|
|
|
max_buy_token_to_liqee,
|
|
|
|
max_sell_token_to_liqor,
|
|
|
|
},
|
|
|
|
),
|
|
|
|
};
|
|
|
|
self.send_and_confirm_owner_tx(vec![ix]).await
|
|
|
|
}
|
|
|
|
|
2023-05-09 03:46:45 -07:00
|
|
|
// health region
|
|
|
|
|
|
|
|
pub fn health_region_begin_instruction(
|
|
|
|
&self,
|
|
|
|
account: &MangoAccountValue,
|
|
|
|
affected_tokens: Vec<TokenIndex>,
|
|
|
|
writable_banks: Vec<TokenIndex>,
|
|
|
|
affected_perp_markets: Vec<PerpMarketIndex>,
|
|
|
|
) -> anyhow::Result<Instruction> {
|
|
|
|
let health_remaining_metas = self.context.derive_health_check_remaining_account_metas(
|
|
|
|
account,
|
|
|
|
affected_tokens,
|
|
|
|
writable_banks,
|
|
|
|
affected_perp_markets,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
let ix = Instruction {
|
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: {
|
|
|
|
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
|
|
|
|
&mango_v4::accounts::HealthRegionBegin {
|
|
|
|
group: self.group(),
|
|
|
|
account: self.mango_account_address,
|
|
|
|
instructions: solana_sdk::sysvar::instructions::id(),
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
ams.extend(health_remaining_metas.into_iter());
|
|
|
|
ams
|
|
|
|
},
|
|
|
|
data: anchor_lang::InstructionData::data(&mango_v4::instruction::HealthRegionBegin {}),
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(ix)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn health_region_end_instruction(
|
|
|
|
&self,
|
|
|
|
account: &MangoAccountValue,
|
|
|
|
affected_tokens: Vec<TokenIndex>,
|
|
|
|
writable_banks: Vec<TokenIndex>,
|
|
|
|
affected_perp_markets: Vec<PerpMarketIndex>,
|
|
|
|
) -> anyhow::Result<Instruction> {
|
|
|
|
let health_remaining_metas = self.context.derive_health_check_remaining_account_metas(
|
|
|
|
account,
|
|
|
|
affected_tokens,
|
|
|
|
writable_banks,
|
|
|
|
affected_perp_markets,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
let ix = Instruction {
|
|
|
|
program_id: mango_v4::id(),
|
|
|
|
accounts: {
|
|
|
|
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
|
|
|
|
&mango_v4::accounts::HealthRegionEnd {
|
|
|
|
account: self.mango_account_address,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
ams.extend(health_remaining_metas.into_iter());
|
|
|
|
ams
|
|
|
|
},
|
|
|
|
data: anchor_lang::InstructionData::data(&mango_v4::instruction::HealthRegionEnd {}),
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(ix)
|
|
|
|
}
|
|
|
|
|
|
|
|
// jupiter
|
|
|
|
|
2023-07-03 05:09:11 -07:00
|
|
|
async fn http_error_handling<T: serde::de::DeserializeOwned>(
|
|
|
|
response: reqwest::Response,
|
|
|
|
) -> anyhow::Result<T> {
|
|
|
|
let status = response.status();
|
|
|
|
let response_text = response
|
|
|
|
.text()
|
|
|
|
.await
|
2023-08-08 09:17:23 -07:00
|
|
|
.context("awaiting body of http request")?;
|
2023-07-03 05:09:11 -07:00
|
|
|
if !status.is_success() {
|
2023-08-08 09:17:23 -07:00
|
|
|
anyhow::bail!("http request failed, status: {status}, body: {response_text}");
|
2023-07-03 05:09:11 -07:00
|
|
|
}
|
|
|
|
serde_json::from_str::<T>(&response_text)
|
|
|
|
.with_context(|| format!("response has unexpected format, body: {response_text}"))
|
|
|
|
}
|
|
|
|
|
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,
|
2023-07-10 01:40:48 -07:00
|
|
|
only_direct_routes: bool,
|
2022-08-05 06:24:23 -07:00
|
|
|
) -> anyhow::Result<jupiter::QueryRoute> {
|
2023-07-03 05:09:11 -07:00
|
|
|
let response = self
|
2022-08-04 08:01:00 -07:00
|
|
|
.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)),
|
2023-07-10 01:40:48 -07:00
|
|
|
("onlyDirectRoutes", only_direct_routes.to_string()),
|
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
|
2023-07-03 05:09:11 -07:00
|
|
|
.context("quote request to jupiter")?;
|
|
|
|
let quote: jupiter::QueryResult =
|
|
|
|
Self::http_error_handling(response).await.with_context(|| {
|
|
|
|
format!("error requesting jupiter route between {input_mint} and {output_mint}")
|
2022-08-04 08:01:00 -07:00
|
|
|
})?;
|
|
|
|
|
2023-07-03 05:09:11 -07:00
|
|
|
let route = quote.data.first().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())
|
|
|
|
}
|
|
|
|
|
2023-07-10 01:40:48 -07:00
|
|
|
/// Find the instructions and account lookup tables for a jupiter swap through mango
|
|
|
|
///
|
|
|
|
/// It would be nice if we didn't have to pass input_mint/output_mint - the data is
|
|
|
|
/// definitely in QueryRoute - but it's unclear how.
|
|
|
|
pub async fn prepare_jupiter_swap_transaction(
|
2022-08-05 06:24:23 -07:00
|
|
|
&self,
|
|
|
|
input_mint: Pubkey,
|
|
|
|
output_mint: Pubkey,
|
2023-07-10 01:40:48 -07:00
|
|
|
route: &jupiter::QueryRoute,
|
|
|
|
) -> anyhow::Result<TransactionBuilder> {
|
2022-08-05 06:24:23 -07:00
|
|
|
let source_token = self.context.token_by_mint(&input_mint)?;
|
|
|
|
let target_token = self.context.token_by_mint(&output_mint)?;
|
|
|
|
|
2023-07-03 05:09:11 -07:00
|
|
|
let swap_response = self
|
2022-08-04 08:01:00 -07:00
|
|
|
.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,
|
2023-05-05 00:11:12 -07:00
|
|
|
compute_unit_price_micro_lamports: None, // we already prioritize
|
2022-08-04 08:01:00 -07:00
|
|
|
})
|
|
|
|
.send()
|
|
|
|
.await
|
2023-07-03 05:09:11 -07:00
|
|
|
.context("swap transaction request to jupiter")?;
|
|
|
|
|
|
|
|
let swap: jupiter::SwapResponse = Self::http_error_handling(swap_response)
|
2022-08-04 08:01:00 -07:00
|
|
|
.await
|
2023-07-03 05:09:11 -07:00
|
|
|
.context("error requesting jupiter swap")?;
|
2022-08-04 08:01:00 -07:00
|
|
|
|
|
|
|
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;
|
2023-05-05 00:11:12 -07:00
|
|
|
let compute_budget_program = solana_sdk::compute_budget::ID;
|
2023-07-10 01:40:48 -07:00
|
|
|
// these setup instructions should be placed outside of flashloan begin-end
|
2023-05-05 00:11:12 -07:00
|
|
|
let is_setup_ix = |k: Pubkey| -> bool {
|
|
|
|
k == ata_program || k == token_program || k == compute_budget_program
|
|
|
|
};
|
2022-12-16 04:10:46 -08:00
|
|
|
let (jup_ixs, jup_alts) = self
|
|
|
|
.deserialize_instructions_and_alts(&jup_tx.message)
|
|
|
|
.await?;
|
2023-07-10 01:40:48 -07:00
|
|
|
let jup_action_ix_begin = jup_ixs
|
2023-05-05 00:11:12 -07:00
|
|
|
.iter()
|
2023-07-10 01:40:48 -07:00
|
|
|
.position(|ix| !is_setup_ix(ix.program_id))
|
|
|
|
.ok_or_else(|| {
|
|
|
|
anyhow::anyhow!("jupiter swap response only had setup-like instructions")
|
|
|
|
})?;
|
|
|
|
let jup_action_ix_end = jup_ixs.len()
|
|
|
|
- jup_ixs
|
|
|
|
.iter()
|
|
|
|
.rev()
|
|
|
|
.position(|ix| !is_setup_ix(ix.program_id))
|
|
|
|
.unwrap();
|
2022-08-04 08:01:00 -07:00
|
|
|
|
|
|
|
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<_>>();
|
|
|
|
|
2023-07-10 01:40:48 -07:00
|
|
|
let source_loan = if route.swap_mode == "ExactIn" {
|
|
|
|
u64::from_str(&route.amount).unwrap()
|
|
|
|
} else if route.swap_mode == "ExactOut" {
|
|
|
|
u64::from_str(&route.other_amount_threshold).unwrap()
|
|
|
|
} else {
|
|
|
|
anyhow::bail!("unknown swap mode: {}", route.swap_mode);
|
|
|
|
};
|
|
|
|
let loan_amounts = vec![source_loan, 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
|
|
|
|
2023-07-10 01:40:48 -07:00
|
|
|
for ix in &jup_ixs[..jup_action_ix_begin] {
|
2023-05-05 00:11:12 -07:00
|
|
|
instructions.push(ix.clone());
|
|
|
|
}
|
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,
|
|
|
|
}),
|
|
|
|
});
|
2023-07-10 01:40:48 -07:00
|
|
|
for ix in &jup_ixs[jup_action_ix_begin..jup_action_ix_end] {
|
2022-12-16 04:10:46 -08:00
|
|
|
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
|
|
|
});
|
2023-07-10 01:40:48 -07:00
|
|
|
for ix in &jup_ixs[jup_action_ix_end..] {
|
|
|
|
instructions.push(ix.clone());
|
|
|
|
}
|
2022-08-04 08:01:00 -07:00
|
|
|
|
2022-12-16 04:10:46 -08:00
|
|
|
let mut address_lookup_tables = self.mango_address_lookup_tables().await?;
|
|
|
|
address_lookup_tables.extend(jup_alts.into_iter());
|
|
|
|
|
2023-07-10 01:40:48 -07:00
|
|
|
let payer = self.owner.pubkey(); // maybe use fee_payer? but usually it's the same
|
|
|
|
|
|
|
|
Ok(TransactionBuilder {
|
2022-12-16 04:10:46 -08:00
|
|
|
instructions,
|
|
|
|
address_lookup_tables,
|
|
|
|
payer,
|
2023-07-10 01:40:48 -07:00
|
|
|
signers: vec![self.owner.clone()],
|
2023-02-20 03:09:17 -08:00
|
|
|
config: self.client.transaction_builder_config,
|
2023-07-10 01:40:48 -07:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn jupiter_swap(
|
|
|
|
&self,
|
|
|
|
input_mint: Pubkey,
|
|
|
|
output_mint: Pubkey,
|
|
|
|
amount: u64,
|
|
|
|
slippage: u64,
|
|
|
|
swap_mode: JupiterSwapMode,
|
|
|
|
only_direct_routes: bool,
|
|
|
|
) -> anyhow::Result<Signature> {
|
|
|
|
let route = self
|
|
|
|
.jupiter_route(
|
|
|
|
input_mint,
|
|
|
|
output_mint,
|
|
|
|
amount,
|
|
|
|
slippage,
|
|
|
|
swap_mode,
|
|
|
|
only_direct_routes,
|
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
let tx_builder = self
|
|
|
|
.prepare_jupiter_swap_transaction(input_mint, output_mint, &route)
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
tx_builder.send_and_confirm(&self.client).await
|
2022-12-16 04:10:46 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
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-07-10 01:40:48 -07:00
|
|
|
signers: vec![self.owner.clone(), self.client.fee_payer.clone()],
|
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(),
|
2023-07-10 01:40:48 -07:00
|
|
|
signers: vec![self.client.fee_payer.clone()],
|
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-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>,
|
|
|
|
}
|
|
|
|
|
2023-07-10 01:40:48 -07:00
|
|
|
pub struct TransactionBuilder {
|
2023-02-20 03:09:17 -08:00
|
|
|
pub instructions: Vec<Instruction>,
|
|
|
|
pub address_lookup_tables: Vec<AddressLookupTableAccount>,
|
2023-07-10 01:40:48 -07:00
|
|
|
pub signers: Vec<Arc<Keypair>>,
|
2023-02-20 03:09:17 -08:00
|
|
|
pub payer: Pubkey,
|
|
|
|
pub config: TransactionBuilderConfig,
|
2022-12-16 04:10:46 -08:00
|
|
|
}
|
|
|
|
|
2023-07-10 01:40:48 -07:00
|
|
|
impl TransactionBuilder {
|
2022-12-16 04:10:46 -08:00
|
|
|
pub async fn transaction(
|
2023-07-10 01:40:48 -07:00
|
|
|
&self,
|
2022-12-16 04:10:46 -08:00
|
|
|
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-07-10 01:40:48 -07:00
|
|
|
&self,
|
2022-12-16 04:10:46 -08:00
|
|
|
blockhash: Hash,
|
|
|
|
) -> anyhow::Result<solana_sdk::transaction::VersionedTransaction> {
|
2023-07-10 01:40:48 -07:00
|
|
|
let mut ix = self.instructions.clone();
|
2023-02-20 03:09:17 -08:00
|
|
|
if let Some(prio_price) = self.config.prioritization_micro_lamports {
|
2023-07-10 01:40:48 -07:00
|
|
|
ix.insert(
|
2023-02-20 03:09:17 -08:00
|
|
|
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,
|
2023-07-10 01:40:48 -07:00
|
|
|
&ix,
|
2022-12-16 04:10:46 -08:00
|
|
|
&self.address_lookup_tables,
|
|
|
|
blockhash,
|
|
|
|
)?;
|
|
|
|
let versioned_message = solana_sdk::message::VersionedMessage::V0(v0_message);
|
|
|
|
let signers = self
|
|
|
|
.signers
|
2023-07-10 01:40:48 -07:00
|
|
|
.iter()
|
2022-12-16 04:10:46 -08:00
|
|
|
.unique_by(|s| s.pubkey())
|
2023-07-10 01:40:48 -07:00
|
|
|
.map(|v| v.deref())
|
2022-12-16 04:10:46 -08:00
|
|
|
.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!
|
|
|
|
|
2023-07-10 01:40:48 -07:00
|
|
|
pub async fn send(&self, client: &Client) -> anyhow::Result<Signature> {
|
2023-02-20 03:09:17 -08:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2023-07-10 01:40:48 -07:00
|
|
|
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)
|
|
|
|
}
|
2023-07-10 01:40:48 -07:00
|
|
|
|
|
|
|
pub fn transaction_size_ok(&self) -> anyhow::Result<bool> {
|
|
|
|
let tx = self.transaction_with_blockhash(solana_sdk::hash::Hash::default())?;
|
|
|
|
let bytes = bincode::serialize(&tx)?;
|
|
|
|
Ok(bytes.len() <= solana_sdk::packet::PACKET_DATA_SIZE)
|
|
|
|
}
|
2022-12-16 04:10:46 -08:00
|
|
|
}
|
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|