soly 0.1.1

Simple Transaction Builder and Sender for Solana
Documentation
use {
    super::BlockHashCacheProvider,
    crate::{Result, TransactionRpcProvider},
    moka::future::Cache,
    solana_hash::Hash,
    solana_message::AddressLookupTableAccount,
    solana_pubkey::Pubkey,
    solana_rpc_client_api::response::RpcPrioritizationFee,
    solana_signature::Signature,
    std::time::Duration,
    tracing::{Level, event},
};

impl<T: TransactionRpcProvider> BlockHashCacheProvider<T> {
    pub fn new(client: T, ttl: Duration) -> Self {
        Self {
            inner: client,
            blockhash: Cache::builder().max_capacity(1).time_to_live(ttl).build(),
        }
    }
}

#[async_trait::async_trait]
impl<T: TransactionRpcProvider + Send + Sync> TransactionRpcProvider for BlockHashCacheProvider<T> {
    async fn get_latest_blockhash(&self) -> Result<Hash> {
        self.blockhash
            .try_get_with((), async {
                event!(Level::DEBUG, "blockhash cache miss");
                self.inner.get_latest_blockhash().await
            })
            .await
            .map_err(|arc_err| match std::sync::Arc::try_unwrap(arc_err) {
                Ok(e) => e,
                Err(arc) => crate::Error::MokaCacheError(arc.to_string()),
            })
    }

    async fn get_recent_prioritization_fees(
        &self,
        accounts: &[Pubkey],
    ) -> Result<Vec<RpcPrioritizationFee>> {
        self.inner.get_recent_prioritization_fees(accounts).await
    }

    async fn get_lookup_table_accounts(
        &self,
        pubkeys: &[Pubkey],
    ) -> Result<Vec<AddressLookupTableAccount>> {
        self.inner.get_lookup_table_accounts(pubkeys).await
    }

    async fn simulate_transaction(
        &self,
        tx: &solana_transaction::versioned::VersionedTransaction,
        config: solana_rpc_client_api::config::RpcSimulateTransactionConfig,
    ) -> Result<solana_rpc_client_api::response::RpcSimulateTransactionResult> {
        self.inner.simulate_transaction(tx, config).await
    }

    async fn send_and_confirm_transaction(
        &self,
        tx: &solana_transaction::versioned::VersionedTransaction,
        config: Option<solana_rpc_client_api::config::RpcSendTransactionConfig>,
    ) -> Result<Signature> {
        self.inner.send_and_confirm_transaction(tx, config).await
    }
}

#[cfg(test)]
mod tests {

    use {
        super::*,
        crate::{
            TransactionBuilder,
            TransactionRpcProvider,
            rpc::{
                CounterRpcProvider,
                noop::{NoopRpc, NoopRpcNative},
            },
        },
        solana_keypair::Keypair,
        solana_rpc_client_api::config::RpcSimulateTransactionConfig,
        solana_signer::Signer,
        tokio::time::sleep,
    };

    #[tokio::test]
    async fn test_blockhash_cache_provider() -> anyhow::Result<()> {
        let noop: NoopRpcNative = NoopRpc::default();
        let counter = CounterRpcProvider::new(noop);
        let hash_cache = BlockHashCacheProvider::new(counter.clone(), Duration::from_secs(1));
        hash_cache.get_latest_blockhash().await?;
        {
            assert_eq!(counter.get_counter(&crate::RpcMethod::Blockhash), 1);
        }
        sleep(Duration::from_millis(500)).await;
        hash_cache.get_latest_blockhash().await?;
        {
            assert_eq!(counter.get_counter(&crate::RpcMethod::Blockhash), 1);
        }
        sleep(Duration::from_millis(2000)).await;
        hash_cache.get_latest_blockhash().await?;
        assert_eq!(counter.get_counter(&crate::RpcMethod::Blockhash), 2);

        let _ = hash_cache
            .get_lookup_table_accounts(&[Pubkey::default()])
            .await?;
        let _ = hash_cache
            .get_recent_prioritization_fees(&[Pubkey::default()])
            .await?;

        let kp = Keypair::new();
        let tx: TransactionBuilder = spl_memo_interface::instruction::build_memo(
            &spl_memo_interface::v3::ID,
            String::from("memo").as_bytes(),
            &[&kp.pubkey()],
        )
        .into();
        let tx = tx.unsigned_tx(&kp.pubkey(), &hash_cache).await?;

        let _ = hash_cache
            .simulate_transaction(&tx, RpcSimulateTransactionConfig::default())
            .await?;

        let _ = hash_cache.send_and_confirm_transaction(&tx, None).await?;

        assert_eq!(1, counter.get_counter(&crate::RpcMethod::Fees));
        assert_eq!(1, counter.get_counter(&crate::RpcMethod::Lookup));
        assert_eq!(1, counter.get_counter(&crate::RpcMethod::Simulate));
        assert_eq!(1, counter.get_counter(&crate::RpcMethod::Send));

        counter.reset_counters();
        assert_eq!(0, counter.get_counter(&crate::RpcMethod::Fees));
        assert_eq!(0, counter.get_counter(&crate::RpcMethod::Lookup));
        assert_eq!(0, counter.get_counter(&crate::RpcMethod::Simulate));
        assert_eq!(0, counter.get_counter(&crate::RpcMethod::Send));
        Ok(())
    }
}