Skip to content

rust ​

Send a bundle using Rust.

Dependencies ​

toml
[dependencies]
solana-sdk = "1.18"
solana-client = "1.18"
reqwest = { version = "0.11", features = ["json", "blocking"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
base64 = "0.21"
bincode = "1.3"

Types ​

rust
use serde::{Deserialize, Serialize};

#[derive(Serialize)]
pub struct BundleParams {
    pub encoding: String,
    #[serde(rename = "skipPreflight")]
    pub skip_preflight: bool,
}

#[derive(Serialize)]
pub struct JsonRpcRequest {
    pub jsonrpc: String,
    pub id: u64,
    pub method: String,
    pub params: (Vec<String>, BundleParams),
}

#[derive(Deserialize)]
pub struct BundleResult {
    pub bundle_id: String,
    pub signatures: Vec<String>,
}

#[derive(Deserialize)]
pub struct JsonRpcResponse {
    pub result: Option<BundleResult>,
    pub error: Option<RpcError>,
}

#[derive(Deserialize)]
pub struct RpcError {
    pub code: i32,
    pub message: String,
}

Send Bundle Function ​

rust
use reqwest::blocking::Client;
use solana_sdk::transaction::Transaction;

use crate::types::*;

pub const ENDPOINT: &str = "https://fra.blocksprint.io/bundle/v1/YOUR_API_KEY";

pub fn send_bundle(transactions: Vec<Transaction>) -> Result<BundleResult, Box<dyn std::error::Error>> {
    let client = Client::new();

    // Serialize transactions to base64
    let serialized_txs: Vec<String> = transactions
        .iter()
        .map(|tx| base64::encode(bincode::serialize(tx).unwrap()))
        .collect();

    let request = JsonRpcRequest {
        jsonrpc: "2.0".to_string(),
        id: 1,
        method: "sendBundle".to_string(),
        params: (
            serialized_txs,
            BundleParams {
                encoding: "base64".to_string(),
                skip_preflight: true,
            },
        ),
    };

    let response: JsonRpcResponse = client
        .post(ENDPOINT)
        .header("Content-Type", "application/json")
        .json(&request)
        .send()?
        .json()?;

    if let Some(error) = response.error {
        return Err(format!("RPC error {}: {}", error.code, error.message).into());
    }

    Ok(response.result.unwrap())
}

Full Example ​

rust
mod send_bundle;
mod types;

use send_bundle::send_bundle;
use solana_client::rpc_client::RpcClient;
use solana_sdk::{
    commitment_config::CommitmentConfig,
    message::Message,
    pubkey::Pubkey,
    signature::{Keypair, Signer},
    system_instruction,
    transaction::Transaction,
};
use std::str::FromStr;

// Your Solana RPC (for the blockhash)
const RPC_URL: &str = "https://api.mainnet-beta.solana.com";
// BlockSprint tip address, tip at least 0.001 SOL
const TIP_ADDRESS: &str = "Sp4JHSh9cksfzXbgK7Pq2ovtn8LirLQydaJKTsiNT77";

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = RpcClient::new_with_commitment(
        RPC_URL.to_string(),
        CommitmentConfig::confirmed(),
    );

    let wallet = Keypair::new();
    let recipient = Pubkey::from_str("RECIPIENT")?;
    let tip_address = Pubkey::from_str(TIP_ADDRESS)?;

    // Get blockhash (same for all transactions)
    let blockhash = client.get_latest_blockhash()?;

    // Transaction 1
    let tx1 = Transaction::new_signed_with_payer(
        &[system_instruction::transfer(&wallet.pubkey(), &recipient, 1_000_000)],
        Some(&wallet.pubkey()),
        &[&wallet],
        blockhash,
    );

    // Transaction 2 (with the 0.001 SOL tip)
    let tx2 = Transaction::new_signed_with_payer(
        &[
            system_instruction::transfer(&wallet.pubkey(), &recipient, 2_000_000),
            system_instruction::transfer(&wallet.pubkey(), &tip_address, 1_000_000),
        ],
        Some(&wallet.pubkey()),
        &[&wallet],
        blockhash,
    );

    // Send bundle
    let result = send_bundle(vec![tx1, tx2])?;

    println!("Bundle ID: {}", result.bundle_id);
    for (i, sig) in result.signatures.iter().enumerate() {
        println!("Tx {}: {}", i + 1, sig);
    }

    Ok(())
}

Async Version ​

rust
use reqwest::Client;
use solana_sdk::transaction::Transaction;

use crate::send_bundle::ENDPOINT;
use crate::types::*;

pub async fn send_bundle_async(
    transactions: Vec<Transaction>,
) -> Result<BundleResult, Box<dyn std::error::Error>> {
    let client = Client::new();

    let serialized_txs: Vec<String> = transactions
        .iter()
        .map(|tx| base64::encode(bincode::serialize(tx).unwrap()))
        .collect();

    let request = JsonRpcRequest {
        jsonrpc: "2.0".to_string(),
        id: 1,
        method: "sendBundle".to_string(),
        params: (
            serialized_txs,
            BundleParams {
                encoding: "base64".to_string(),
                skip_preflight: true,
            },
        ),
    };

    let response: JsonRpcResponse = client
        .post(ENDPOINT)
        .header("Content-Type", "application/json")
        .json(&request)
        .send()
        .await?
        .json()
        .await?;

    if let Some(error) = response.error {
        return Err(format!("RPC error {}: {}", error.code, error.message).into());
    }

    Ok(response.result.unwrap())
}