Examples
A series of Recall SDK examples.
The Recall SDK can be used to interact with both accounts and the actual bucket layer. This page outlines a handful of examples for different use cases.
Creating a new account
use ethers::utils::hex::ToHexExt;
use fendermint_vm_actor_interface::eam::EthAddress;
use recall_provider::fvm_shared::address::Address;
use recall_sdk::network::Network;
use recall_signer::key::random_secretkey;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Use testnet network defaults
Network::Testnet.init();
let sk = random_secretkey();
let pk = sk.public_key().serialize();
let eth_address = EthAddress::new_secp256k1(&pk)?;
let address = Address::from(eth_address);
let sk_hex = hex::encode(sk.serialize());
println!("Private key: {}", sk_hex);
println!("Address: {}", eth_address.encode_hex_with_prefix());
println!("FVM address: {}", address);
Ok(())
}
Depositing funds into an account
use std::env;
use anyhow::anyhow;
use ethers::utils::hex::ToHexExt;
use recall_provider::fvm_shared::econ::TokenAmount;
use recall_sdk::{account::Account, network::Network};
use recall_signer::{key::parse_secret_key, AccountKind, Signer, Wallet};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
return Err(anyhow!("missing hex-encoded private key"));
}
let pk_kex = &args[1];
let pk = parse_secret_key(pk_kex)?;
// Use testnet network defaults
let cfg = Network::Testnet.get_config();
// Setup local wallet using private key from arg
let signer = Wallet::new_secp256k1(pk, AccountKind::Ethereum, cfg.subnet_id.parent()?)?;
// Deposit some calibration funds into the subnet
let tx = Account::deposit(
&signer,
signer.address(),
cfg.parent_subnet_config()
.ok_or(anyhow!("network does not have parent"))?,
cfg.subnet_id,
TokenAmount::from_whole(1),
)
.await?;
println!(
"Deposited 1 RECALL to {}",
signer.eth_address()?.encode_hex_with_prefix()
);
println!(
"Transaction hash: 0x{}",
hex::encode(tx.transaction_hash.to_fixed_bytes())
);
Ok(())
}
Getting an account's balance
// Copyright 2025 Recall Contributors
// SPDX-License-Identifier: Apache-2.0, MIT
use std::env;
use anyhow::anyhow;
use ethers::utils::hex::ToHexExt;
use recall_sdk::{account::Account, ipc::subnet::EVMSubnet, network::Network};
use recall_signer::{key::parse_secret_key, AccountKind, Signer, Wallet};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
return Err(anyhow!("missing hex-encoded private key"));
}
let pk_kex = &args[1];
let pk = parse_secret_key(pk_kex)?;
// Use testnet network defaults
let cfg = Network::Testnet.get_config();
// Setup local wallet using private key from arg
let signer = Wallet::new_secp256k1(pk, AccountKind::Ethereum, cfg.subnet_id.parent()?)?;
// Deposit some calibration funds into the subnet
// Note: The debit account _must_ have Calibration
let balance = Account::balance(
&signer,
EVMSubnet {
auth_token: Some("some-secret".to_owned()),
..cfg.subnet_config()
},
)
.await?;
println!(
"Balance of {}: {}",
signer.eth_address()?.encode_hex_with_prefix(),
balance,
);
Ok(())
}
Bucket & object lifecycle
use std::collections::HashMap;
use std::env;
use anyhow::anyhow;
use rand::{thread_rng, Rng};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::time::{sleep, Duration};
use recall_provider::json_rpc::JsonRpcProvider;
use recall_sdk::{
machine::{
bucket::{AddOptions, Bucket, GetOptions, QueryOptions},
Machine,
},
network::Network,
};
use recall_signer::{key::parse_secret_key, AccountKind, Wallet};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let args: Vec<String> = env::args().collect();
if args.len() != 2 {
return Err(anyhow!("Usage: [private key]"));
}
let pk_kex = &args[2];
let pk = parse_secret_key(pk_kex)?;
// Use testnet network defaults
let cfg = Network::Testnet.get_config();
// Setup network provider
let provider = JsonRpcProvider::new_http(
cfg.rpc_url,
cfg.subnet_id.chain_id(),
None,
Some(cfg.object_api_url),
)?;
// Setup local wallet using private key from arg
let mut signer = Wallet::new_secp256k1(pk, AccountKind::Ethereum, cfg.subnet_id)?;
signer.init_sequence(&provider).await?;
// Create a new bucket
let (machine, tx) = Bucket::new(
&provider,
&mut signer,
None,
HashMap::new(),
Default::default(),
)
.await?;
println!("Created new bucket {}", machine.address());
println!("Transaction hash: 0x{}", tx.hash());
// Create a temp file to add
let mut file = async_tempfile::TempFile::new().await?;
let mut rng = thread_rng();
let mut random_data = vec![0; 1024 * 1024]; // 1 MiB
rng.fill(&mut random_data[..]);
file.write_all(&random_data).await?;
file.flush().await?;
// Add a file to the bucket
let key = "foo/my_file";
let mut metadata = HashMap::new();
metadata.insert("foo".to_string(), "bar".to_string());
let options = AddOptions {
overwrite: true,
metadata,
..Default::default()
};
let tx = machine
.add_from_path(&provider, &mut signer, key, file.file_path(), options)
.await?;
println!(
"Added 1MiB file to bucket {} with key {}",
machine.address(),
key,
);
println!("Transaction hash: 0x{}", tx.hash());
// Wait some time for the network to resolve the object
sleep(Duration::from_secs(2)).await;
// Query for the object
let options = QueryOptions {
prefix: "foo/".into(),
..Default::default()
};
tokio::time::sleep(Duration::from_secs(2)).await;
let list = machine.query(&provider, options).await?;
for (key_bytes, object) in list.objects {
let key = core::str::from_utf8(&key_bytes).unwrap_or_default();
println!("Query result for key {}: {}", key, object.hash);
}
// Download the actual object at `foo/my_file`
let obj_file = async_tempfile::TempFile::new().await?;
let obj_path = obj_file.file_path().to_owned();
println!("Downloading object to {}", obj_path.display());
let options = GetOptions {
range: Some("0-99".to_string()), // Get the first 100 bytes
..Default::default()
};
{
let open_file = obj_file.open_rw().await?;
machine.get(&provider, key, open_file, options).await?;
}
// Read the first 10 bytes of your downloaded 100 bytes
let mut read_file = tokio::fs::File::open(&obj_path).await?;
let mut contents = vec![0; 10];
read_file.read_exact(&mut contents).await?;
println!("Successfully read first 10 bytes of {}", obj_path.display());
// Now, delete the object
let tx = machine
.delete(&provider, &mut signer, key, Default::default())
.await?;
println!("Deleted object with key {} at tx 0x{}", key, tx.hash());
Ok(())
}