Crates.io | fireblocks-solana-signer |
lib.rs | fireblocks-solana-signer |
version | 1.0.14 |
created_at | 2025-06-29 08:50:02.068711+00 |
updated_at | 2025-07-27 11:09:41.383393+00 |
description | Implementation of a Solana Signer using Fireblocks as backend signer |
homepage | https://github.com/CarteraMesh/fireblocks-solana-signer |
repository | https://github.com/CarteraMesh/fireblocks-solana-signer |
max_upload_size | |
id | 1730557 |
size | 283,378 |
Implementation of a Solana Signer using Fireblocks as backend signer
A fireblocks account with API key. See developer portal and sign up for a sandbox account
Add this to your Cargo.toml
:
[dependencies]
fireblocks-solana-signer = "1"
Or install via cargo:
cargo add fireblocks-solana-signer@1
This signer automatically broadcasts transactions to the Solana network. When you call any signing method (like try_sign
), Fireblocks will:
This is a purposeful security design decision by Fireblocks to ensure transaction integrity. You do not need to (and should not) broadcast the transaction yourself after signing.
The transaction is already on-chain when the signing method returns successfully!
use {
fireblocks_solana_signer::FireblocksSigner,
solana_message::Message,
solana_rpc_client::rpc_client::{RpcClient, SerializableTransaction},
solana_sdk::instruction::Instruction,
solana_transaction::Transaction,
};
fn memo(message: &str) -> Instruction {
Instruction {
program_id: spl_memo::id(),
accounts: vec![],
data: message.as_bytes().to_vec(),
}
}
fn main() -> anyhow::Result<()> {
let signer: FireblocksSigner = FireblocksSigner::try_from_env(None)?;
let rpc = RpcClient::new(
std::env::var("RPC_URL")
.ok()
.unwrap_or("https://rpc.ankr.com/solana_devnet".to_string()),
);
let hash = rpc.get_latest_blockhash()?;
let message = Message::new(&[memo("fireblocks signer")], Some(&signer.pk));
let mut tx = Transaction::new_unsigned(message);
// ⚠️ This signs AND broadcasts the transaction automatically!
tx.try_sign(&[&signer], hash)?;
// ✅ Transaction is already on-chain, just get the signature
println!("Transaction broadcasted with signature: {}", tx.get_signature());
// ❌ DO NOT do this - transaction is already broadcasted!
// rpc.send_transaction(&tx)?; // This will likely fail
Ok(())
}
See example
Var | Example |
---|---|
FIREBLOCKS_SECRET | RSA private key of your API user |
FIREBLOCKS_API_KEY | uuid of api user |
FIREBLOCKS_ENDPOINT | https://sandbox-api.fireblocks.io |
FIREBLOCKS_PUBKEY | optional pubkey, or lookup based on FIREBLOCKS_VAULT |
FIREBLOCKS_DEVNET | set to any value if you are on devnet |
FIREBLOCKS_VAULT | your vault id |
FIREBLOCKS_POLL_TIMEOUT | in seconds, total time to check status of transaction |
FIREBLOCKS_POLL_INTERVAL | in seconds |
As an alternative to environment variables, you can use configuration files with the config
feature. This provides a more structured approach to managing multiple Fireblocks environments and credentials.
Add the config
feature to your Cargo.toml
:
[dependencies]
fireblocks-solana-signer = { version = "1", features = ["config"] }
The config feature uses the fireblocks-config
crate for configuration management. Configuration files are stored in the ~/.config/fireblocks/
directory using the microxdg
crate.
File Structure:
~/.config/fireblocks/default.toml
(always loaded)~/.config/fireblocks/{profile}.toml
(override default settings)Example ~/.config/fireblocks/default.toml
:
api_key = "your-sandbox-api-key-uuid"
secret_path = "/path/to/your/sandbox-private-key.pem"
url = "https://sandbox-api.fireblocks.io"
mainnet = false
[signer]
vault = "your-sandbox-vault-id"
poll_timeout = 30
poll_interval = 2
Example ~/.config/fireblocks/production.toml
:
api_key = "your-production-api-key"
secret_path = "/path/to/production-key.pem"
url = "https://api.fireblocks.io"
mainnet = true
[signer]
vault = "your-production-vault-id"
poll_timeout = 60
poll_interval = 3
use fireblocks_solana_signer::FireblocksSigner;
fn main() -> anyhow::Result<()> {
// Use default configuration profile
let signer = FireblocksSigner::try_from_config::<String>(
&[],
|tx_response| println!("Transaction status: {}", tx_response)
)?;
// Use specific configuration profiles
let signer = FireblocksSigner::try_from_config(
&["mainnet"],
|tx_response| eprintln!("Mainnet TX: {}", tx_response)
)?;
// Use multiple profiles (later profiles override earlier ones)
let signer = FireblocksSigner::try_from_config(
&["default", "production"],
|tx_response| println!("TX Update: {}", tx_response)
)?;
// Your transaction code here...
Ok(())
}
How Profile Loading Works:
&[]
: Loads only ~/.config/fireblocks/default.toml
&["production"]
: Loads default.toml
first, then production.toml
overrides any matching settings&["staging", "production"]
: Loads default.toml
, then staging.toml
, then production.toml
(each overriding previous values)Method | Best For | Pros | Cons |
---|---|---|---|
Environment Variables | Simple setups, CI/CD | Easy to set, widely supported | Hard to manage multiple environments |
Configuration Files | Complex setups, multiple environments | Organized, version-controllable, flexible | Requires additional feature, more setup |
For detailed configuration options and file locations, see the fireblocks-config
documentation.
Rust Nightly: Required for code formatting with advanced features
rustup install nightly
Environment Setup: Create a .env
file with your Fireblocks credentials
cp env-sample .env
# Edit .env with your actual Fireblocks API credentials
Clone the repository
git clone https://github.com/CarteraMesh/fireblocks-solana-signer.git
cd fireblocks-solana-signer
Set up environment
# Copy and configure environment variables
cp env-sample .env
# Install Rust nightly for formatting
rustup install nightly
Build and test
# Build the project
cargo build
# Run tests (requires valid Fireblocks credentials in .env)
cargo test
# Format code (requires nightly)
cargo +nightly fmt --all
This project uses advanced Rust formatting features that require nightly:
# Format all code
cargo +nightly fmt --all
# Check formatting
cargo +nightly fmt --all -- --check
# Make sure your .env file is configured first
cargo run --example memo