Join the AlphaWave competition
Developer toolsSDKsRust SDK

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(())
}

On this page