1use std::sync::Arc;
2
3use anchor_client::solana_client::rpc_config::RpcSimulateTransactionConfig;
4use anchor_client::solana_sdk::account::Account;
5use anchor_client::solana_sdk::address_lookup_table::state::AddressLookupTable;
6use anchor_client::solana_sdk::address_lookup_table::AddressLookupTableAccount;
7use anchor_client::solana_sdk::commitment_config::CommitmentConfig;
8use anchor_client::solana_sdk::instruction::Instruction;
9use anchor_client::solana_sdk::message::{v0, VersionedMessage};
10use anchor_client::solana_sdk::pubkey;
11use anchor_client::solana_sdk::pubkey::Pubkey;
12use anchor_client::solana_sdk::signature::{Keypair, Signature};
13use anchor_client::solana_sdk::transaction::VersionedTransaction;
14use anchor_client::{Client, Cluster, Program};
15use anchor_lang::prelude::AccountMeta;
16use anyhow::{anyhow, Result};
17use itertools::Itertools;
18
19pub const EXCHANGE_LOOKUP_TABLE: Pubkey =
20 pubkey!("E1jD3vdypYukwy9SWgWCnAJEvKC4Uj7MEc3c4S2LogD9");
21
22pub const STABILITY_POOL_LOOKUP_TABLE: Pubkey =
23 pubkey!("Gb35n7SYMZCwCZbmxJMqoFsFX1mVhdSXmwo8wAJ8whWC");
24
25pub const LST_REGISTRY_LOOKUP_TABLE: Pubkey =
26 pubkey!("9Mb2Mt76AN7eNY3BBA4LgfTicARXhcEEokTBfsN47noK");
27
28pub const SOL_USD_PYTH_FEED: Pubkey =
29 pubkey!("7UVimffxr9ow1uXYxsr4LHAcV58mLzhmwaeKvJ1pjLiE");
30
31#[must_use]
33pub fn simulation_config() -> RpcSimulateTransactionConfig {
34 RpcSimulateTransactionConfig {
35 sig_verify: false,
36 replace_recent_blockhash: true,
37 commitment: Some(CommitmentConfig::confirmed()),
38 ..Default::default()
39 }
40}
41
42fn deserialize_lookup_table(
47 key: &Pubkey,
48 account: &Account,
49) -> Result<AddressLookupTableAccount> {
50 let table = AddressLookupTable::deserialize(&account.data)?;
51 Ok(AddressLookupTableAccount {
52 key: *key,
53 addresses: table.addresses.to_vec(),
54 })
55}
56
57#[async_trait::async_trait]
59pub trait ProgramClient: Sized {
60 const PROGRAM_ID: Pubkey;
61
62 fn build_client(
63 program: Program<Arc<Keypair>>,
64 keypair: Arc<Keypair>,
65 ) -> Self;
66
67 fn program(&self) -> &Program<Arc<Keypair>>;
68
69 fn keypair(&self) -> Arc<Keypair>;
70
71 fn new_from_keypair(
76 cluster: Cluster,
77 keypair: Keypair,
78 config: CommitmentConfig,
79 ) -> Result<Self> {
80 let keypair = Arc::new(keypair);
81 let client = Client::new_with_options(cluster, keypair.clone(), config);
82 let program = client.program(Self::PROGRAM_ID)?;
83 Ok(Self::build_client(program, keypair))
84 }
85
86 async fn send_v0_transaction(
94 &self,
95 instructions: &[Instruction],
96 lookup_tables: &[AddressLookupTableAccount],
97 ) -> Result<Signature> {
98 let recent_blockhash = self.program().rpc().get_latest_blockhash().await?;
99 let message = v0::Message::try_compile(
100 &self.program().payer(),
101 instructions,
102 lookup_tables,
103 recent_blockhash,
104 )?;
105 let tx = VersionedTransaction::try_new(
106 VersionedMessage::V0(message),
107 &[self.keypair()],
108 )?;
109 let sig = self
110 .program()
111 .rpc()
112 .send_and_confirm_transaction(&tx)
113 .await?;
114 Ok(sig)
115 }
116
117 async fn load_lst_registry(
124 &self,
125 ) -> Result<(Vec<AccountMeta>, AddressLookupTableAccount)> {
126 let table = self.load_lookup_table(&LST_REGISTRY_LOOKUP_TABLE).await?;
127 if let Some((preamble, blocks)) = table.addresses.split_at_checked(16) {
128 let preamble = preamble
129 .iter()
130 .map(|key| AccountMeta::new_readonly(*key, false));
131 let blocks =
132 blocks
133 .iter()
134 .tuples()
135 .flat_map(|(header, mint, vault, pool_state)| {
136 [
137 AccountMeta::new(*header, false),
138 AccountMeta::new_readonly(*mint, false),
139 AccountMeta::new_readonly(*vault, false),
140 AccountMeta::new_readonly(*pool_state, false),
141 ]
142 });
143 let remaining_accounts = preamble.chain(blocks).collect_vec();
144 Ok((remaining_accounts, table))
145 } else {
146 Err(anyhow!("Malformed LST registry preamble."))
147 }
148 }
149
150 async fn load_lookup_table(
156 &self,
157 key: &Pubkey,
158 ) -> Result<AddressLookupTableAccount> {
159 let account = self.program().rpc().get_account(key).await?;
160 deserialize_lookup_table(key, &account)
161 }
162
163 async fn load_multiple_lookup_tables(
168 &self,
169 pubkeys: &[Pubkey],
170 ) -> Result<Vec<AddressLookupTableAccount>> {
171 self
172 .program()
173 .rpc()
174 .get_multiple_accounts(pubkeys)
175 .await?
176 .iter()
177 .zip(pubkeys)
178 .map(|(opt, key)| {
179 if let Some(account) = opt {
180 deserialize_lookup_table(key, account)
181 } else {
182 Err(anyhow!("No lookup table found at address {key}."))
183 }
184 })
185 .try_collect()
186 }
187}