Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
12 changes: 10 additions & 2 deletions src/chain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -18,6 +20,7 @@ pub enum Chain {
Bsc,
Tempo,
TempoTestnet,
Solana,
}

impl Chain {
Expand All @@ -33,6 +36,7 @@ impl Chain {
Chain::Bsc => 56,
Chain::Tempo => 4217,
Chain::TempoTestnet => 42429,
Chain::Solana => 0,
}
}

Expand All @@ -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",
}
}

Expand All @@ -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.
Expand All @@ -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",
}
}
}
Expand All @@ -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"),
}
}
}
109 changes: 109 additions & 0 deletions src/chain/solana.rs
Original file line number Diff line number Diff line change
@@ -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<u64> {
let pubkey = Pubkey::from_str(address)?;
let balance = self.client.get_balance(&pubkey)?;
Ok(balance)
}

pub fn balance_sol(&self, address: &str) -> Result<f64> {
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<String> {
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<u64> {
let pubkey = Pubkey::from_str(token_account)?;
let balance = self.client.get_token_account_balance(&pubkey)?;
Ok(balance.amount.parse::<u64>().unwrap_or(0))
}

pub fn transfer_spl_token(
&self,
from: &Keypair,
token_mint: &str,
to_wallet: &str,
amount: u64,
) -> Result<String> {
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())
}
}