Tutorial: How to Post Data with WeaveVM Bundler

Tutorial: How to Post Data with WeaveVM Bundler

January 08, 2025

WeaveGM! Intern here with a simple tutorial on the WeaveVM Bundler. The Bundler is an easy way to post data to WeaveVM from inside a Rust program. Bundles can contain multiple transactions while incurring only one base fee. Transactions inside bundles are also processed in parallel, meaning they are independent of the network’s block time.

In this tutorial, we’ll use the WeaveVM Bundler Rust library first to test posting a string of data to WeaveVM, then to post the full contents of 3 books onchain.

WeaveVM Bundler is a data protocol specification and library that introduces the first bundled EVM transactions format. This protocol draws inspiration from Arweave’s ANS-102 specification. To view data from bundled transactions, use the https://bundler.wvm.network/v1/envelopes/<bundle_txid> endpoint.

Clone the code for this tutorial here

Step 1: Install Rust

Install Rust and Cargo (Rust’s package manager) using rustup:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Restart your terminal or update the shell environment:

source $HOME/.cargo/env

Verify Rust installation:

rustc --version
cargo --version

Step 2: Set Up Your Rust Project

Create a new Rust project:

cargo new weavevm_bundler_example
cd weavevm_bundler_example

Add dependencies to your Cargo.toml file:

[dependencies]
bundler = { git = "https://github.com/weaveVM/bundler", branch = "main" }
tokio = { version = "1", features = ["full"] }
serde_json = "1.0"
reqwest = { version = "0.11", features = ["json", "stream", "rustls-tls"] }
hex = "0.4"
eyre = "0.6"

Step 3: Write the Program

Example 1: Bundling a Single String

Open the src/main.rs file and replace its contents with the following:

use bundler::utils::core::bundle::Bundle;
use bundler::utils::core::envelope::Envelope;
use eyre::Result;
use reqwest::Client;
use serde_json::Value;
use hex;

#[tokio::main]
async fn main() -> Result<()> {
    
    let private_key = String::from("YOUR_PRIVATE_KEY_HERE");
    let data_string = "Hello, WeaveVM!";
    let envelope_data = serde_json::to_vec(&data_string)?;

    // build an envelope
    let envelope = Envelope::new()
        .data(Some(envelope_data))
        .target(None) 
        .build()?;

    // group the envelope into a bundle
    let bundle = Bundle::new()
        .private_key(private_key)
        .envelopes(vec![envelope]) 
        .build()?;

    // propagate the bundle
    let bundle_tx = bundle.propagate().await?;
    println!("Bundle sent successfully! Transaction Hash: {}", bundle_tx);

    // fetch posted data from endpoint
    let endpoint = format!("https://bundler.wvm.network/v1/envelopes/{}", bundle_tx);
    let client = Client::new();
    let response = client.get(&endpoint).send().await?;
    let body = response.text().await?;

    // parse & decode data
    let json: Value = serde_json::from_str(&body)?;
    let input_hex = json["envelopes"][0]["input"]
        .as_str()
        .expect("Failed to extract input");

    let decoded_input = hex::decode(input_hex.trim_start_matches("0x"))?;
    let decoded_string = String::from_utf8(decoded_input.clone())
        .expect("Failed to convert decoded input to string");

    // print
    println!("\n--- Retrieved Data ---");
    println!("Raw Input (Hex): {}", input_hex);
    println!("Decoded Input (String): {}", decoded_string);

    Ok(())
}

Replace YOUR_PRIVATE_KEY_HERE with your funded testnet private key. To get testnet WeaveVM tokens, use the faucet.

Run the program:

cargo run

Example 2: Bundling Multiple Text Files

Let’s grab some .txt files and upload a full directory as a single bundle.

Create a folder named data in your project root:

mkdir data

Add text files to the data folder:

echo "This is the content of book 1." > data/book1.txt
echo "This is the content of book 2." > data/book2.txt
echo "This is the content of book 3." > data/book3.txt

(Or get some .txt books from Project Gutenberg)

Update the src/main.rs file with the following:

use bundler::utils::core::bundle::Bundle;
use bundler::utils::core::envelope::Envelope;
use eyre::Result;
use reqwest::Client;
use serde_json::Value;
use hex;
use std::fs;
use std::path::Path;

#[tokio::main]
async fn main() -> Result<()> {
    let private_key = String::from("YOUR_PRIVATE_KEY_HERE");
    let folder_path = Path::new("data");
    
    if !folder_path.exists() {
        panic!("Folder 'data' does not exist. Please create it and add text files.");
    }

    let mut envelopes = vec![];
    for entry in fs::read_dir(folder_path)? {
        let entry = entry?;
        let file_path = entry.path();

        if file_path.is_file() {
            let file_content = fs::read_to_string(&file_path)
                .unwrap_or_else(|_| panic!("Failed to read the file: {:?}", file_path));
            
            println!("Processing file: {:?}", file_path);

            let envelope_data = serde_json::to_vec(&file_content)?;
            let envelope = Envelope::new()
                .data(Some(envelope_data))
                .target(None) 
                .build()?;
            envelopes.push(envelope);
        }
    }

    // package envelopes into a bundle
    let bundle = Bundle::new()
        .private_key(private_key)
        .envelopes(envelopes)
        .build()?;

    // propagate the bundle
    let bundle_tx = bundle.propagate().await?;
    println!("Bundle sent successfully! Transaction Hash: {}", bundle_tx);

    // fetch posted data from the endpoint
    let endpoint = format!("https://bundler.wvm.network/v1/envelopes/{}", bundle_tx);
    let client = Client::new();
    let response = client.get(&endpoint).send().await?;
    let body = response.text().await?;

    // parse & decode
    let json: Value = serde_json::from_str(&body)?;
    for (i, envelope) in json["envelopes"].as_array().unwrap().iter().enumerate() {
        let input_hex = envelope["input"].as_str().expect("Failed to extract input");
        let decoded_input = hex::decode(input_hex.trim_start_matches("0x"))?;
        let decoded_string = String::from_utf8(decoded_input.clone())
            .expect("Failed to convert decoded input to string");

        println!("\n--- File {} ---", i + 1);
        println!("Raw Input (Hex): {}", input_hex);
        println!("Decoded Input (Content):\n{}", decoded_string);
    }

    Ok(())
}

Run the program:

cargo run

Expected Output

For the text files in the data folder, you should see something like:

Processing file: "data/book1.txt"
Processing file: "data/book2.txt"
Processing file: "data/book3.txt"
Bundle sent successfully! Transaction Hash: 0xabc123def456...

--- File 1 ---
Raw Input (Hex): 0x22546869732069732074686520636f6e74656e74206f6620626f6f6b20312e22
Decoded Input (Content):
"This is the content of book 1."

--- File 2 ---
Raw Input (Hex): 0x22546869732069732074686520636f6e74656e74206f6620626f6f6b20322e22
Decoded Input (Content):
"This is the content of book 2."

--- File 3 ---
Raw Input (Hex): 0x22546869732069732074686520636f6e74656e74206f6620626f6f6b20332e22
Decoded Input (Content):
"This is the content of book 3."

To verify the data onchain, use the bundler endpoint at https://bundler.wvm.network/v1/envelopes/<txid>. It is also possible to view all bundles submitted by your account on the Explorer.