lighthouse-sdk 2.0.0

A rust client for Lighthouse, a transaction safeguard Solana program
Documentation
#[allow(unused)]
#[allow(clippy::identity_op)]
mod generated;
mod hooked;
pub mod registry;

pub use generated::programs::LIGHTHOUSE_ID;
pub use generated::programs::LIGHTHOUSE_ID as ID;
use solana_program::pubkey::{Pubkey, PubkeyError};

pub use generated::types;
pub use lighthouse_common::{CompactU64, LEB128Vec};

pub mod instructions {
    pub use crate::generated::instructions::{
        AssertAccountDataBuilder, AssertAccountDeltaBuilder, AssertAccountInfoBuilder,
        AssertAccountInfoMultiBuilder, AssertBubblegumTreeConfigAccountBuilder,
        AssertMerkleTreeAccountBuilder, AssertMintAccountBuilder, AssertMintAccountMultiBuilder,
        AssertStakeAccountBuilder, AssertStakeAccountMultiBuilder, AssertSysvarClockBuilder,
        AssertTokenAccountBuilder, AssertTokenAccountMultiBuilder,
        AssertUpgradeableLoaderAccountBuilder, AssertUpgradeableLoaderAccountMultiBuilder,
        MemoryCloseBuilder, MemoryWriteBuilder,
    };
}

#[cfg(feature = "cpi")]
pub mod cpi {
    pub use crate::generated::instructions::{
        AssertAccountDataCpiBuilder, AssertAccountDeltaCpiBuilder, AssertAccountInfoCpiBuilder,
        AssertAccountInfoMultiCpiBuilder, AssertBubblegumTreeConfigAccountCpiBuilder,
        AssertMerkleTreeAccountCpiBuilder, AssertMintAccountCpiBuilder,
        AssertMintAccountMultiCpiBuilder, AssertStakeAccountCpiBuilder,
        AssertStakeAccountMultiCpiBuilder, AssertSysvarClockCpiBuilder,
        AssertTokenAccountCpiBuilder, AssertTokenAccountMultiCpiBuilder,
        AssertUpgradeableLoaderAccountCpiBuilder, AssertUpgradeableLoaderAccountMultiCpiBuilder,
        MemoryCloseCpiBuilder, MemoryWriteCpiBuilder,
    };
}

pub mod errors {
    pub use crate::generated::errors::*;
}

pub fn find_memory_pda(payer: Pubkey, memory_id: u8) -> (solana_program::pubkey::Pubkey, u8) {
    solana_program::pubkey::Pubkey::find_program_address(
        &["memory".to_string().as_ref(), payer.as_ref(), &[memory_id]],
        &crate::ID,
    )
}

pub fn find_memory_pda_bump_iterate(
    payer: Pubkey,
    memory_id: u8,
    bump_skip: u8,
    start_bump: Option<u8>,
) -> Option<(solana_program::pubkey::Pubkey, u8)> {
    let memory_ref = "memory".to_string();
    let seeds = [memory_ref.as_ref(), payer.as_ref(), &[memory_id]];

    let mut bump_seed = [start_bump.unwrap_or(std::u8::MAX)];
    let mut bump_skip = bump_skip as usize;

    for _ in 0..std::u8::MAX {
        let mut seeds_with_bump = seeds.to_vec();
        seeds_with_bump.push(&bump_seed);
        match Pubkey::create_program_address(&seeds_with_bump, &crate::ID) {
            Ok(address) => {
                if bump_skip == 0 {
                    return Some((address, bump_seed[0]));
                } else {
                    bump_skip -= 1;
                }
            }
            Err(PubkeyError::InvalidSeeds) => {}
            _ => break,
        }
        bump_seed[0] -= 1;

        println!("bump_seed: {:?}", bump_seed[0])
    }

    None
}

#[cfg(feature = "sdk")]
pub mod utils {
    use crate::generated::types::AssertionResult;
    use borsh::BorshDeserialize;
    use solana_sdk::{
        instruction::{AccountMeta, Instruction},
        message::{legacy, v0, CompileError, Message, VersionedMessage},
        signer::{Signer, SignerError},
        transaction::{Transaction, VersionedTransaction},
    };

    #[derive(Debug, thiserror::Error)]
    #[repr(u32)]
    pub enum ClientError {
        #[error("Transaction already signed")]
        TransactionAlreadySigned,
        #[error("Address table lookups not supported")]
        AddressTableLookupsNotSupported,
        #[error("Empty transaction")]
        EmptyTransaction,
        #[error("...")]
        CompileError(CompileError),
        #[error("...")]
        SignerError(SignerError),
        #[error("...")]
        Base64DecodeError(base64::DecodeError),
        #[error("...")]
        IOError(std::io::Error),
    }

    #[allow(deprecated)]
    pub fn parse_evaluation_payloads_from_logs(
        logs: Vec<&String>,
    ) -> Result<Vec<AssertionResult>, ClientError> {
        logs.iter()
            .filter(|log| log.contains("Program data: "))
            .map(|log| {
                let encoded = log.split("Program data: ").collect::<Vec<&str>>()[1];
                let decoded = base64::decode(encoded).map_err(ClientError::Base64DecodeError)?;
                AssertionResult::try_from_slice(&decoded).map_err(ClientError::IOError)
            })
            .collect()
    }

    pub fn append_instructions_to_transaction(
        transaction: &Transaction,
        ixs: Vec<Instruction>,
    ) -> Result<Transaction, ClientError> {
        if !transaction.signatures.is_empty() {
            return Err(ClientError::TransactionAlreadySigned);
        }

        let mut merged_ixs = decompile_instruction_from_transaction(transaction)?;
        merged_ixs.extend(ixs);

        let transaction = Transaction::new_unsigned(Message::new(
            &merged_ixs,
            Some(
                transaction
                    .message
                    .account_keys
                    .first()
                    .ok_or(ClientError::EmptyTransaction)?,
            ),
        ));

        Ok(transaction)
    }

    pub fn append_instructions_to_versioned_transaction(
        transaction: &VersionedTransaction,
        ixs: Vec<Instruction>,
        signers: &[&dyn Signer],
    ) -> Result<VersionedTransaction, ClientError> {
        if !transaction.signatures.is_empty() {
            return Err(ClientError::TransactionAlreadySigned);
        }

        if transaction.message.address_table_lookups().is_some() {
            return Err(ClientError::AddressTableLookupsNotSupported);
        }

        let mut merged_ixs = decompile_instruction_from_versioned_transaction(transaction)?;
        merged_ixs.extend(ixs);

        let payer = transaction
            .message
            .static_account_keys()
            .first()
            .ok_or(ClientError::EmptyTransaction)?;

        let versioned_messsage = match transaction.message {
            VersionedMessage::Legacy(_) => {
                VersionedMessage::Legacy(legacy::Message::new(&merged_ixs, Some(payer)))
            }
            VersionedMessage::V0(_) => VersionedMessage::V0(
                v0::Message::try_compile(
                    payer,
                    &merged_ixs,
                    &[],
                    *transaction.message.recent_blockhash(),
                )
                .map_err(ClientError::CompileError)?,
            ),
        };

        let mut transaction = VersionedTransaction::try_new(versioned_messsage, signers)
            .map_err(ClientError::SignerError)?;

        transaction
            .message
            .set_recent_blockhash(*transaction.message.recent_blockhash());

        Ok(transaction)
    }

    pub fn decompile_instruction_from_versioned_transaction(
        transaction: &VersionedTransaction,
    ) -> Result<Vec<Instruction>, ClientError> {
        if !transaction.signatures.is_empty() {
            return Err(ClientError::TransactionAlreadySigned);
        }

        if transaction.message.address_table_lookups().is_some() {
            return Err(ClientError::AddressTableLookupsNotSupported);
        }

        let mut modified_ixs = vec![];
        let compiled_ixs = transaction.message.instructions();

        for instruction in compiled_ixs {
            let account_keys = transaction.message.static_account_keys();

            modified_ixs.push(Instruction {
                program_id: account_keys[instruction.program_id_index as usize],
                accounts: instruction
                    .accounts
                    .iter()
                    .map(|index| AccountMeta {
                        pubkey: account_keys[*index as usize],
                        is_signer: index < &transaction.message.header().num_required_signatures,
                        is_writable: transaction.message.is_maybe_writable(*index as usize),
                    })
                    .collect(),
                data: instruction.data.clone(),
            });
        }

        Ok(modified_ixs)
    }

    pub fn decompile_instruction_from_transaction(
        transaction: &Transaction,
    ) -> Result<Vec<Instruction>, ClientError> {
        if !transaction.signatures.is_empty() {
            return Err(ClientError::TransactionAlreadySigned);
        }

        let mut modified_ixs = vec![];

        for instruction in &transaction.message.instructions {
            modified_ixs.push(Instruction {
                program_id: transaction.message.account_keys[instruction.program_id_index as usize],
                accounts: instruction
                    .accounts
                    .iter()
                    .map(|index| AccountMeta {
                        pubkey: transaction.message.account_keys[*index as usize],
                        is_signer: index < &transaction.message.header.num_required_signatures,
                        is_writable: transaction.message.is_writable(*index as usize),
                    })
                    .collect(),
                data: instruction.data.clone(),
            });
        }

        Ok(modified_ixs)
    }
}