tuktuk_cli/
client.rs

1use std::sync::Arc;
2
3use solana_client::{
4    nonblocking::{rpc_client::RpcClient, tpu_client::TpuClient},
5    rpc_config::RpcSendTransactionConfig,
6    send_and_confirm_transactions_in_parallel::{
7        send_and_confirm_transactions_in_parallel_v2, SendAndConfirmConfigV2,
8    },
9    tpu_client::TpuClientConfig,
10};
11use solana_sdk::{
12    commitment_config::CommitmentConfig, instruction::Instruction, message::Message,
13    signature::Keypair, signer::Signer,
14};
15use solana_transaction_utils::{
16    pack::pack_instructions_into_transactions, priority_fee::auto_compute_limit_and_price,
17};
18
19use crate::{cmd::Opts, result::Result};
20
21pub struct CliClient {
22    pub rpc_client: Arc<RpcClient>,
23    pub payer: Keypair,
24    pub opts: Opts,
25}
26
27impl AsRef<RpcClient> for CliClient {
28    fn as_ref(&self) -> &RpcClient {
29        &self.rpc_client
30    }
31}
32
33impl CliClient {
34    pub async fn new(opts: &Opts) -> Result<Self> {
35        let rpc_client =
36            RpcClient::new_with_commitment(opts.rpc_url(), CommitmentConfig::confirmed());
37        let payer = opts.load_solana_keypair()?;
38        Ok(Self {
39            rpc_client: Arc::new(rpc_client),
40            payer,
41            opts: opts.clone(),
42        })
43    }
44}
45
46pub async fn send_instructions(
47    rpc_client: Arc<RpcClient>,
48    payer: &Keypair,
49    ws_url: &str,
50    ixs: &[Instruction],
51    extra_signers: &[Keypair],
52) -> Result<()> {
53    let (blockhash, _) = rpc_client
54        .as_ref()
55        .get_latest_blockhash_with_commitment(CommitmentConfig::finalized())
56        .await
57        .expect("Failed to get latest blockhash");
58    let txs = pack_instructions_into_transactions(&[ixs], None)?;
59    let mut with_auto_compute: Vec<Message> = Vec::new();
60    let keys: Vec<&dyn Signer> = std::iter::once(&payer as &dyn Signer)
61        .chain(extra_signers.iter().map(|k| k as &dyn Signer))
62        .collect();
63    for tx in txs {
64        // This is just a tx with compute ixs. Skip it
65        if tx.is_empty() {
66            continue;
67        }
68
69        let (computed, _) = auto_compute_limit_and_price(
70            &rpc_client,
71            &tx.instructions,
72            1.2,
73            &payer.pubkey(),
74            Some(blockhash),
75            None,
76        )
77        .await
78        .unwrap();
79        with_auto_compute.push(Message::new(&computed, Some(&payer.pubkey())));
80    }
81    if with_auto_compute.is_empty() {
82        return Ok(());
83    }
84
85    let tpu_client = TpuClient::new(
86        "tuktuk-cli",
87        rpc_client.clone(),
88        ws_url,
89        TpuClientConfig::default(),
90    )
91    .await?;
92
93    let results = send_and_confirm_transactions_in_parallel_v2(
94        rpc_client.clone(),
95        Some(tpu_client),
96        &with_auto_compute,
97        &keys,
98        SendAndConfirmConfigV2 {
99            with_spinner: true,
100            resign_txs_count: Some(5),
101            rpc_send_transaction_config: RpcSendTransactionConfig {
102                skip_preflight: true,
103                ..Default::default()
104            },
105        },
106    )
107    .await?;
108
109    if let Some(err) = results.into_iter().flatten().next() {
110        return Err(anyhow::Error::from(err));
111    }
112
113    Ok(())
114}