diff --git a/Cargo.toml b/Cargo.toml index 97a453b..3abbf7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,11 @@ hex = "0.4" rand = "0.8" async-trait = "0.1" url = "2" +solana-client = "2.1" +solana-sdk = "2.1" +spl-token = "7" +spl-associated-token-account = "4" +anyhow = "1" [dev-dependencies] tokio-test = "0.4" diff --git a/src/chain/mod.rs b/src/chain/mod.rs index 72a3da7..8739263 100644 --- a/src/chain/mod.rs +++ b/src/chain/mod.rs @@ -4,6 +4,8 @@ use serde::{Deserialize, Serialize}; use std::fmt; mod connector; +mod solana; +pub use solana::SolanaChain; pub use connector::ChainConnector; /// Supported blockchain networks. @@ -18,6 +20,7 @@ pub enum Chain { Bsc, Tempo, TempoTestnet, + Solana, } impl Chain { @@ -33,6 +36,7 @@ impl Chain { Chain::Bsc => 56, Chain::Tempo => 4217, Chain::TempoTestnet => 42429, + Chain::Solana => 0, } } @@ -48,6 +52,7 @@ impl Chain { Chain::Bsc => "https://bsc-dataseed.binance.org", Chain::Tempo => "https://rpc.tempo.xyz", Chain::TempoTestnet => "https://rpc.testnet.tempo.xyz", + Chain::Solana => "https://api.mainnet-beta.solana.com", } } @@ -58,13 +63,14 @@ impl Chain { Chain::Avalanche => "AVAX", Chain::Polygon => "MATIC", Chain::Bsc => "BNB", - Chain::Tempo | Chain::TempoTestnet => "USDC", // Tempo has no native gas token + Chain::Tempo | Chain::TempoTestnet => "USDC", + Chain::Solana => "SOL", // Tempo has no native gas token } } /// Whether this chain uses stablecoins for gas (Tempo). pub fn stablecoin_gas(&self) -> bool { - matches!(self, Chain::Tempo | Chain::TempoTestnet) + matches!(self, Chain::Tempo | Chain::TempoTestnet | Chain::Solana) } /// Block explorer base URL. @@ -79,6 +85,7 @@ impl Chain { Chain::Bsc => "https://bscscan.com", Chain::Tempo => "https://explorer.tempo.xyz", Chain::TempoTestnet => "https://explorer.testnet.tempo.xyz", + Chain::Solana => "https://explorer.solana.com", } } } @@ -95,6 +102,7 @@ impl fmt::Display for Chain { Chain::Bsc => write!(f, "bsc"), Chain::Tempo => write!(f, "tempo"), Chain::TempoTestnet => write!(f, "tempo-testnet"), + Chain::Solana => write!(f, "solana"), } } } diff --git a/src/chain/solana.rs b/src/chain/solana.rs new file mode 100644 index 0000000..2877425 --- /dev/null +++ b/src/chain/solana.rs @@ -0,0 +1,109 @@ +use anyhow::Result; +use solana_client::rpc_client::RpcClient; +use solana_sdk::{ + native_token::LAMPORTS_PER_SOL, + pubkey::Pubkey, + signature::{Keypair, Signer}, + system_instruction, + transaction::Transaction, +}; +use spl_associated_token_account::get_associated_token_address; +use std::str::FromStr; + +pub struct SolanaChain { + client: RpcClient, +} + +impl SolanaChain { + pub fn new(rpc_url: &str) -> Self { + Self { + client: RpcClient::new(rpc_url.to_string()), + } + } + + pub fn connect(&self) -> Result<()> { + self.client.get_version()?; + Ok(()) + } + + pub fn balance(&self, address: &str) -> Result { + let pubkey = Pubkey::from_str(address)?; + let balance = self.client.get_balance(&pubkey)?; + Ok(balance) + } + + pub fn balance_sol(&self, address: &str) -> Result { + let lamports = self.balance(address)?; + Ok(lamports as f64 / LAMPORTS_PER_SOL as f64) + } + + pub fn transfer_sol( + &self, + from: &Keypair, + to: &str, + amount_sol: f64, + ) -> Result { + let to_pubkey = Pubkey::from_str(to)?; + let lamports = (amount_sol * LAMPORTS_PER_SOL as f64) as u64; + + let instruction = system_instruction::transfer( + &from.pubkey(), + &to_pubkey, + lamports, + ); + + let recent_blockhash = self.client.get_latest_blockhash()?; + let tx = Transaction::new_signed_with_payer( + &[instruction], + Some(&from.pubkey()), + &[from], + recent_blockhash, + ); + + let signature = self.client.send_and_confirm_transaction(&tx)?; + Ok(signature.to_string()) + } + + pub fn spl_token_balance( + &self, + token_account: &str, + ) -> Result { + let pubkey = Pubkey::from_str(token_account)?; + let balance = self.client.get_token_account_balance(&pubkey)?; + Ok(balance.amount.parse::().unwrap_or(0)) + } + + pub fn transfer_spl_token( + &self, + from: &Keypair, + token_mint: &str, + to_wallet: &str, + amount: u64, + ) -> Result { + let mint_pubkey = Pubkey::from_str(token_mint)?; + let to_pubkey = Pubkey::from_str(to_wallet)?; + + let from_ata = get_associated_token_address(&from.pubkey(), &mint_pubkey); + let to_ata = get_associated_token_address(&to_pubkey, &mint_pubkey); + + let instruction = spl_token::instruction::transfer( + &spl_token::ID, + &from_ata, + &to_ata, + &from.pubkey(), + &[], + amount, + )?; + + let recent_blockhash = self.client.get_latest_blockhash()?; + let tx = Transaction::new_signed_with_payer( + &[instruction], + Some(&from.pubkey()), + &[from], + recent_blockhash, + ); + + let signature = self.client.send_and_confirm_transaction(&tx)?; + Ok(signature.to_string()) + } +}