rust
Send a transaction using Rust with the solana-* crates and reqwest.
Dependencies
toml
[dependencies]
solana-address = { version = "2.6", features = ["decode"] }
solana-hash = "4.3"
solana-keypair = "3.0"
solana-signer = "3.0"
solana-message = { version = "4.6", features = ["wincode"] }
solana-transaction = { version = "4.3", features = ["wincode"] }
solana-system-interface = { version = "2.0", features = ["bincode"] }
solana-compute-budget-interface = "3.0"
wincode = "0.6"
reqwest = { version = "0.12", features = ["blocking", "json"] }
serde_json = "1"
base64 = "0.22"
rand = "0.8"Full Example
rust
use std::error::Error;
use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
use rand::seq::SliceRandom;
use reqwest::blocking::Client;
use serde_json::{json, Value};
use solana_address::Address;
use solana_hash::Hash;
use solana_keypair::Keypair;
use solana_message::{Instruction, Message, VersionedMessage};
use solana_signer::Signer;
use solana_system_interface::instruction::transfer;
use solana_transaction::versioned::VersionedTransaction;
// Your Solana RPC: blockhash and confirmations
const RPC_URL: &str = "https://api.mainnet-beta.solana.com";
// BlockSprint: sending only
const ENDPOINT: &str = "https://fra.blocksprint.io/api/v1/YOUR_API_KEY";
// BlockSprint tip addresses
const TIP_ADDRESSES: &[&str] = &[
"Sp1x2AqpQckPLaWnWCJUNg8k6qQexfaEWcSRKf5JcDV",
"Sp4JHSh9cksfzXbgK7Pq2ovtn8LirLQydaJKTsiNT77",
"Sp1xMS2cbw83SZDNr4AGqkBYYLjb3LvVnmDSrTMaHkr",
"SpagSJmnh8E9cGT5Y431xPPaS2c1xLREGGCWN9yDeUf",
"SpWrza9E63MQuHeGnnfzmtLVCs3pBdjyKPXUABPo9nq",
"SpXyoqMHJEhsNQ9fHh3DTGGzrGroZ5zfDtJwFvN4kXi",
"Spv2g3L837bVeHwkyb18LhA5P1bvJbkvaZbxcZXbwC2",
"SpDUe5MyFGG97SmdBQMWNPbx2Et9bZt2TCbZm3TujgX",
"SpKZ28tFzQ13kVjHhsyDEiQfkPGjA7Wmk185KcEDyuh",
"SpxPqegfoR3QkBN7guRUiU6QfhtTW1CnKSTSSapumqm",
];
fn get_random_tip_address() -> Address {
let address = TIP_ADDRESSES.choose(&mut rand::thread_rng()).unwrap();
address.parse().unwrap()
}
/// JSON-RPC call to any endpoint. Returns `result`, or the JSON-RPC error.
fn rpc_call(client: &Client, url: &str, method: &str, params: Value) -> Result<Value, Box<dyn Error>> {
let body = json!({ "jsonrpc": "2.0", "id": 1, "method": method, "params": params });
// Errors come back as JSON-RPC errors with an HTTP 4xx/5xx status
let response: Value = client.post(url).json(&body).send()?.json()?;
if let Some(error) = response.get("error") {
return Err(format!("RPC Error: {}", error).into());
}
Ok(response["result"].clone())
}
fn main() -> Result<(), Box<dyn Error>> {
// Reuse one client so connections stay open between calls
let client = Client::new();
// Your funded wallet and the recipient
let wallet = Keypair::from_base58_string(&std::env::var("PAYER_SECRET_BASE58")?);
let recipient: Address = std::env::var("RECIPIENT")?.parse()?;
// Build instructions
let instructions: Vec<Instruction> = vec![
// Your transfer
transfer(&wallet.pubkey(), &recipient, 1_000_000),
// Tip instruction (0.001 SOL minimum)
transfer(&wallet.pubkey(), &get_random_tip_address(), 1_000_000),
];
// Get blockhash from your RPC
let latest = rpc_call(&client, RPC_URL, "getLatestBlockhash", json!([{ "commitment": "confirmed" }]))?;
let blockhash: Hash = latest["value"]["blockhash"].as_str().ok_or("no blockhash")?.parse()?;
// Build and sign
let message = Message::new_with_blockhash(&instructions, Some(&wallet.pubkey()), &blockhash);
let transaction = VersionedTransaction::try_new(VersionedMessage::Legacy(message), &[&wallet])?;
let tx_bytes = wincode::serialize(&transaction)?;
// Send through BlockSprint
let signature = rpc_call(
&client,
ENDPOINT,
"sendTransaction",
json!([BASE64.encode(&tx_bytes), { "encoding": "base64", "skipPreflight": true, "maxRetries": 0 }]),
)?;
// "Accepted", not "landed": check it with getSignatureStatuses on your RPC
println!("Signature: {}", signature.as_str().unwrap_or_default());
Ok(())
}With Priority Fee
rust
use solana_compute_budget_interface::ComputeBudgetInstruction;
let instructions: Vec<Instruction> = vec![
// Set priority fee (micro-lamports per compute unit)
ComputeBudgetInstruction::set_compute_unit_price(1_000_000),
// Set compute limit
ComputeBudgetInstruction::set_compute_unit_limit(200_000),
// Your instruction
your_instruction,
// Tip
transfer(&wallet.pubkey(), &get_random_tip_address(), 1_000_000),
];Raw HTTP Request
rust
use reqwest::blocking::Client;
use serde_json::json;
fn send_transaction_raw(
client: &Client,
endpoint: &str,
transaction_base64: &str,
) -> Result<String, Box<dyn std::error::Error>> {
let body = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "sendTransaction",
"params": [
transaction_base64,
{
"encoding": "base64",
"skipPreflight": true,
"maxRetries": 0
}
]
});
let response: serde_json::Value = client
.post(endpoint)
.json(&body)
.send()?
.json()?;
if let Some(error) = response.get("error") {
return Err(format!("RPC Error: {}", error).into());
}
Ok(response["result"].as_str().ok_or("missing result")?.to_string())
}INFO
Transaction V1 needs the solana-* crates 4.2 or later (the versions above). Compile the message with v1::Message::try_compile_with_config and a v1::TransactionConfig that sets the compute-unit limit and the loaded-accounts data size limit (in V1 an unset limit is 0), wrap it in VersionedMessage::V1, and serialize with wincode, not bincode.
