Skip to main content

hylo_sdk/
util.rs

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/// Default configuration to use in simulated transactions.
32#[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
42/// Deserializes an account into an address lookup table.
43///
44/// # Errors
45/// - Account data cannot be deserialized
46fn 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/// Abstracts the construction of client structs with `anchor_client::Program`.
58#[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  /// Constructs the given client with ID `Self::PROGRAM_ID`.
72  ///
73  /// # Errors
74  /// - Underlying Anchor program creation
75  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  /// Builds a versioned transaction from instructions and lookup tables.
87  ///
88  /// # Errors
89  /// - Failed to get the latest blockhash
90  /// - Failed to compile the message
91  /// - Failed to create the transaction
92  /// - Failed to send the transaction
93  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  /// Creates `remaining_accounts` array from LST registry table with all
118  /// headers writable.
119  ///
120  /// # Errors
121  /// - Lookup table account doesn't exist
122  /// - Malformed structure (preamble cannot be split at 16)
123  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  /// Loads an address lookup table by public key.
151  ///
152  /// # Errors
153  /// - Failed to fetch the account
154  /// - Failed to deserialize account data
155  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  /// Loads address lookup tables at given addresses.
164  /// # Errors
165  /// - Failed to fetch lookup table account
166  /// - Failed to deserialize
167  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}