parent
544be727e3
commit
f389b6f856
4 changed files with 6475 additions and 0 deletions
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,14 @@ |
|||||||
|
[package] |
||||||
|
name = "mass-transfer-2" |
||||||
|
version = "0.1.0" |
||||||
|
edition = "2024" |
||||||
|
|
||||||
|
[dependencies] |
||||||
|
bs58 = "0.5.1" |
||||||
|
config = "0.15.11" |
||||||
|
futures = "0.3.31" |
||||||
|
serde = { version = "1.0.219", features = ["derive"] } |
||||||
|
serde_json = "1.0.140" |
||||||
|
solana-client = "2.2.7" |
||||||
|
solana-sdk = "2.2.2" |
||||||
|
tokio = { version = "1.45.0", features = ["macros", "rt-multi-thread"] } |
@ -0,0 +1,10 @@ |
|||||||
|
transfers: |
||||||
|
- sender: |
||||||
|
pubkey: BqrHGjskCWo9Sx6gWeoVHVZbpi6KoE9q6BspdaaGDkmc |
||||||
|
privkey: BEYTkFt8uZp2uKtXrWTvt8CzVHjF9BvjvMQnWKvAwGrB |
||||||
|
receiver: Sysvar1nstructions1111111111111111111111111 |
||||||
|
- sender: |
||||||
|
pubkey: EvsGrL9cXhCJn3KxfHnxKjRJwZgHt2tmHuvn7WMq1Vxu |
||||||
|
privkey: 6dWCFhZXSHsdQapb9wks6qR8EvkjgEQhZtcJnR5GS1dc |
||||||
|
receiver: HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny |
||||||
|
solana_url: https://api.devnet.solana.com |
@ -0,0 +1,162 @@ |
|||||||
|
use std::{error::Error, str::FromStr, sync::Arc}; |
||||||
|
|
||||||
|
use config::Config; |
||||||
|
use futures::future::join_all; |
||||||
|
use serde::{Deserialize, de}; |
||||||
|
use solana_client::{client_error::ClientError, nonblocking::rpc_client::RpcClient}; |
||||||
|
use solana_sdk::{ |
||||||
|
commitment_config::CommitmentConfig, |
||||||
|
native_token::LAMPORTS_PER_SOL, |
||||||
|
pubkey::Pubkey, |
||||||
|
signature::{Keypair, Signature}, |
||||||
|
signer::Signer, |
||||||
|
system_instruction, |
||||||
|
transaction::Transaction, |
||||||
|
}; |
||||||
|
|
||||||
|
#[derive(Deserialize)] |
||||||
|
struct Cfg { |
||||||
|
transfers: Vec<CfgTransfer>, |
||||||
|
solana_url: String, |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Deserialize)] |
||||||
|
struct CfgTransfer { |
||||||
|
#[serde(deserialize_with = "deser_keypair")] |
||||||
|
sender: Keypair, |
||||||
|
#[serde(deserialize_with = "deser_pubkey")] |
||||||
|
receiver: Pubkey, |
||||||
|
} |
||||||
|
|
||||||
|
pub fn deser_keypair<'de, D>(deserializer: D) -> Result<Keypair, D::Error> |
||||||
|
where |
||||||
|
D: de::Deserializer<'de>, |
||||||
|
{ |
||||||
|
#[derive(Deserialize)] |
||||||
|
struct CfgKeyPair { |
||||||
|
pubkey: String, |
||||||
|
privkey: String, |
||||||
|
} |
||||||
|
|
||||||
|
let pair: CfgKeyPair = de::Deserialize::deserialize(deserializer)?; |
||||||
|
let privkey = bs58::decode(pair.privkey) |
||||||
|
.into_vec() |
||||||
|
.map_err(de::Error::custom)?; |
||||||
|
let pubkey = bs58::decode(pair.pubkey) |
||||||
|
.into_vec() |
||||||
|
.map_err(de::Error::custom)?; |
||||||
|
|
||||||
|
let mut bytes = [0u8; 64]; |
||||||
|
bytes[..32].copy_from_slice(&privkey); |
||||||
|
bytes[32..].copy_from_slice(&pubkey); |
||||||
|
|
||||||
|
Keypair::from_bytes(&bytes).map_err(de::Error::custom) |
||||||
|
} |
||||||
|
|
||||||
|
pub fn deser_pubkey<'de, D>(deserializer: D) -> Result<Pubkey, D::Error> |
||||||
|
where |
||||||
|
D: de::Deserializer<'de>, |
||||||
|
{ |
||||||
|
let s: String = de::Deserialize::deserialize(deserializer)?; |
||||||
|
Pubkey::from_str(&s).map_err(de::Error::custom) |
||||||
|
} |
||||||
|
|
||||||
|
#[tokio::main] |
||||||
|
async fn main() -> Result<(), Box<dyn Error>> { |
||||||
|
let config = { |
||||||
|
let config_parser = Config::builder() |
||||||
|
.add_source(config::File::with_name("config.yaml")) |
||||||
|
.build()?; |
||||||
|
|
||||||
|
config_parser.try_deserialize::<Cfg>()? |
||||||
|
}; |
||||||
|
let client = Arc::new(RpcClient::new_with_commitment( |
||||||
|
config.solana_url.clone(), |
||||||
|
CommitmentConfig::confirmed(), |
||||||
|
)); |
||||||
|
|
||||||
|
let accounts_from = config |
||||||
|
.transfers |
||||||
|
.iter() |
||||||
|
.map(|t| t.sender.pubkey()) |
||||||
|
.collect::<Vec<_>>(); |
||||||
|
let accounts_to = config |
||||||
|
.transfers |
||||||
|
.iter() |
||||||
|
.map(|s| s.receiver) |
||||||
|
.collect::<Vec<_>>(); |
||||||
|
|
||||||
|
// for acc in &accounts_from {
|
||||||
|
// println!("requesting airdrop for {acc}");
|
||||||
|
// let signature = client.request_airdrop(&acc, LAMPORTS_PER_SOL).await?;
|
||||||
|
// wait_for_confirmation(client.clone(), &[signature]).await?;
|
||||||
|
// }
|
||||||
|
|
||||||
|
let all_accounts = accounts_from.iter().chain(&accounts_to); |
||||||
|
print_balances(client.clone(), all_accounts.clone()).await?; |
||||||
|
|
||||||
|
let transfer_amount = LAMPORTS_PER_SOL / 100; |
||||||
|
println!("sending {transfer_amount} SOL"); |
||||||
|
|
||||||
|
let transactions = run_batch(config.transfers.iter().map(|tr| { |
||||||
|
let instruction = |
||||||
|
system_instruction::transfer(&tr.sender.pubkey(), &tr.receiver, transfer_amount); |
||||||
|
let mut transaction = |
||||||
|
Transaction::new_with_payer(&[instruction], Some(&tr.sender.pubkey())); |
||||||
|
let client = client.clone(); |
||||||
|
async move { |
||||||
|
let blockhash = client.get_latest_blockhash().await?; |
||||||
|
transaction.sign(&[&tr.sender], blockhash); |
||||||
|
client.send_transaction(&transaction).await |
||||||
|
} |
||||||
|
})) |
||||||
|
.await?; |
||||||
|
|
||||||
|
println!("Pending transactions: \n{transactions:#?}"); |
||||||
|
println!("Waiting for confirmation..."); |
||||||
|
wait_for_confirmation(client.clone(), &transactions).await?; |
||||||
|
|
||||||
|
println!("Mass-transfer is done"); |
||||||
|
print_balances(client.clone(), all_accounts).await?; |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
async fn wait_for_confirmation( |
||||||
|
client: Arc<RpcClient>, |
||||||
|
signatures: &[Signature], |
||||||
|
) -> Result<(), ClientError> { |
||||||
|
for sgn in signatures { |
||||||
|
'conf: loop { |
||||||
|
let confirmed = client.confirm_transaction(sgn).await?; |
||||||
|
if confirmed { |
||||||
|
break 'conf; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
async fn run_batch<T>( |
||||||
|
tasks: impl IntoIterator<Item = impl Future<Output = Result<T, ClientError>>>, |
||||||
|
) -> Result<Vec<T>, ClientError> { |
||||||
|
join_all(tasks).await.into_iter().collect() |
||||||
|
} |
||||||
|
|
||||||
|
async fn print_balances( |
||||||
|
client: Arc<RpcClient>, |
||||||
|
accounts: impl IntoIterator<Item = &Pubkey> + Clone, |
||||||
|
) -> Result<(), ClientError> { |
||||||
|
let balances = run_batch(accounts.clone().into_iter().map(|acc| { |
||||||
|
let client = client.clone(); |
||||||
|
async move { |
||||||
|
let balance = client.get_balance(acc).await?; |
||||||
|
Ok(balance as f64 / LAMPORTS_PER_SOL as f64) |
||||||
|
} |
||||||
|
})) |
||||||
|
.await?; |
||||||
|
|
||||||
|
for (acc, bal) in accounts.into_iter().zip(balances) { |
||||||
|
println!("{acc}: {bal} SOL"); |
||||||
|
} |
||||||
|
Ok(()) |
||||||
|
} |
Loading…
Reference in new issue