use std::str::FromStr;
use base64::{engine::general_purpose, Engine as _};
#[allow(unused_imports)]
pub use borsh::{BorshDeserialize, BorshSerialize};
pub use solana_address_lookup_table_program::state::AddressLookupTable;
pub use solana_client::{
rpc_client::RpcClient, rpc_config::RpcSendTransactionConfig, rpc_request::RpcRequest,
};
pub use solana_sdk::instruction::AccountMeta;
#[allow(unused_imports)]
pub use solana_sdk::{
address_lookup_table::AddressLookupTableAccount,
commitment_config::{CommitmentConfig, CommitmentLevel},
instruction::Instruction,
message::VersionedMessage,
pubkey::Pubkey,
signature::Signature,
signature::{Keypair, Signer},
signer::EncodableKey,
transaction::{Transaction, VersionedTransaction},
};
pub use solana_transaction_status::UiTransactionEncoding;
use crate::legacy_tx::get_discriminant;
pub fn call_with_lookup_table<T>(
connection: &RpcClient,
program_id: &Pubkey,
instruction_name: &str,
instruction_data: T,
lookup_table_key: &Pubkey,
payer: &Keypair,
signers: &[&Keypair],
accounts: Vec<AccountMeta>,
) -> Result<Signature, Box<dyn std::error::Error>>
where
T: BorshSerialize,
{
let lookup_table_account = connection.get_account(lookup_table_key)?;
let address_lookup_table = AddressLookupTable::deserialize(&lookup_table_account.data)?;
let address_lookup_table_account = AddressLookupTableAccount {
key: lookup_table_key.clone(),
addresses: address_lookup_table.addresses.to_vec(),
};
let instruction_discriminant = get_discriminant("global", instruction_name);
let ix = Instruction::new_with_borsh(
program_id.clone(),
&(instruction_discriminant, instruction_data),
accounts,
);
let blockhash = connection.get_latest_blockhash()?;
let tx = VersionedTransaction::try_new(
VersionedMessage::V0(solana_sdk::message::v0::Message::try_compile(
&payer.pubkey(),
&[ix],
&[address_lookup_table_account],
blockhash,
)?),
signers,
)?;
let serialized_tx = bincode::serialize(&tx)?;
let serialized_encoded_tx = general_purpose::STANDARD.encode(serialized_tx);
let config = RpcSendTransactionConfig {
skip_preflight: false,
preflight_commitment: Some(CommitmentLevel::Confirmed),
encoding: Some(UiTransactionEncoding::Base64),
..RpcSendTransactionConfig::default()
};
let signature = connection.send::<String>(
RpcRequest::SendTransaction,
serde_json::json!([serialized_encoded_tx, config]),
)?;
connection.confirm_transaction_with_commitment(
&Signature::from_str(signature.as_str())?,
CommitmentConfig::finalized(),
)?;
Ok(Signature::from_str(&signature)?)
}
pub fn create_lookup_table(
connection: &RpcClient,
payer: &Keypair,
latest_blockhash: solana_sdk::hash::Hash,
) -> Result<Pubkey, Box<dyn std::error::Error>> {
let recent_slot = connection.get_slot()?;
let (create_ix, table_pk) =
solana_address_lookup_table_program::instruction::create_lookup_table(
payer.pubkey(),
payer.pubkey(),
recent_slot,
);
connection.send_and_confirm_transaction(&Transaction::new_signed_with_payer(
&[create_ix],
Some(&payer.pubkey()),
&[&payer],
latest_blockhash,
))?;
Ok(table_pk)
}
pub fn extend_lookup_table(
connection: &RpcClient,
payer: &Keypair,
latest_blockhash: solana_sdk::hash::Hash,
table_pk: Pubkey,
new_accounts: Vec<Pubkey>,
) -> Result<bool, Box<dyn std::error::Error>> {
let extend_ix = solana_address_lookup_table_program::instruction::extend_lookup_table(
table_pk,
payer.pubkey(),
Some(payer.pubkey()),
new_accounts,
);
let signature =
connection.send_and_confirm_transaction(&Transaction::new_signed_with_payer(
&[extend_ix],
Some(&payer.pubkey()),
&[&payer],
latest_blockhash,
))?;
Ok(connection
.confirm_transaction_with_spinner(
&signature,
&latest_blockhash,
CommitmentConfig::confirmed(),
)
.is_ok())
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_create_lookup_table() {
let connection = RpcClient::new("https://api.devnet.solana.com");
let payer: Keypair =
Keypair::read_from_file("/Users/cenwadike/.config/solana/solfate-dev.json").unwrap();
let latest_blockhash = connection
.get_latest_blockhash()
.expect("latest block hash");
let lookup_table_pk = create_lookup_table(&connection, &payer, latest_blockhash);
assert!(lookup_table_pk.is_ok())
}
#[test]
fn test_update_lookup_table() {
let connection = RpcClient::new("https://api.devnet.solana.com");
let payer: Keypair =
Keypair::read_from_file("/Users/cenwadike/.config/solana/solfate-dev.json").unwrap();
let latest_blockhash = connection
.get_latest_blockhash()
.expect("latest block hash");
let table_pk = create_lookup_table(&connection, &payer, latest_blockhash).unwrap();
let new_accounts = vec![Pubkey::new_unique()];
let res = extend_lookup_table(
&connection,
&payer,
latest_blockhash,
table_pk,
new_accounts,
)
.unwrap();
assert!(res);
}
#[derive(BorshSerialize, BorshDeserialize)]
pub struct UpdateBlob {
pub data: Vec<u8>,
}
#[test]
fn test_call_with_lookup_table() {
let connection = RpcClient::new("https://api.devnet.solana.com");
let program_id = blob::ID;
let instruction_name = "update_blob";
let instruction_data = UpdateBlob {
data: "another data".as_bytes().to_vec(),
};
let payer: Keypair =
Keypair::read_from_file("/Users/cenwadike/.config/solana/solfate-dev.json").unwrap();
let signers = &[&payer];
let latest_blockhash = connection
.get_latest_blockhash()
.expect("latest block hash");
let table_pk = create_lookup_table(&connection, &payer, latest_blockhash).unwrap();
let new_accounts = vec![program_id, payer.pubkey()];
extend_lookup_table(
&connection,
&payer,
latest_blockhash,
table_pk,
new_accounts,
)
.unwrap();
let (blob_account, _) = Pubkey::find_program_address(&[&b"blob"[..]], &program_id);
let accounts = vec![
AccountMeta::new(blob_account, false),
AccountMeta::new(payer.pubkey(), true),
];
let res = call_with_lookup_table(
&connection,
&program_id,
instruction_name,
instruction_data,
&table_pk,
&payer,
signers,
accounts,
);
assert!(res.is_ok());
}
}