Arweave's ANS-104 Rust SDK
bundles-rs is a Rust SDK for creating, signing, managing and posting ANS-104 dataitems
About
A Rust SDK for creating, signing, managing and posting ANS-104 dataitems.
Warning: this repository is actively under development and could have breaking changes until reaching full API compatibility in v1.0.0.
Installation
Add to your Cargo.toml
:
[dependencies]
# main library
bundles_rs = { git = "https://github.com/loadnetwork/bundles-rs", branch = "main" }
# use individual crates
# or use branch/tag/rev -- we recommend checking and using the last client version
ans104 = { git = "https://github.com/loadnetwork/bundles-rs", version = "0.1.0" }
crypto = { git = "https://github.com/loadnetwork/bundles-rs", version = "0.1.0" }
Dev setup
git clone https://github.com/loadnetwork/bundles-rs.git
cd bundles-rs
cargo clippy --workspace --lib --examples --tests --benches --locked --all-features
cargo +nightly fmt
cargo check --all
Supported Signers
Arweave
RSA-PSS
Ethereum
secp256k1
Solana
Ed25519 (with base58 solana flavoring)
-
Ed25519Core (raw Ed25519)
Regarding Tags
This ANS-104 dataitems client fully implements the ANS-104 specification as-is
Maximum tags per data item
<= 128 tags
<= 128 tags
<= 128 tags
No max tags
Tag name max size
1024 bytes
1024 bytes
all keys + vals <= 4096 bytes
Can have empty strings
Tag value max size
3072 bytes
3072 bytes
Can have empty strings
val <= 3072 bytes
Empty names/values
non empty strings
non empty strings
Can have empty strings
Can have empty strings
Usage Examples
Quick start
use bundles_rs::{
ans104::{data_item::DataItem, tags::Tag},
crypto::ethereum::EthereumSigner,
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// create a signer
let signer = EthereumSigner::random()?;
// create tags (metadata)
let tags = vec![
Tag::new("Content-Type", "text/plain"),
Tag::new("App-Name", "Load-Network"),
];
// create and sign a dataitem
let data = b"Hello World Arweave!".to_vec();
// first None for Target and the second for Anchor
// let target = [0u8; 32]; -- 32-byte target address
// let anchor = b"unique-anchor".to_vec(); -- max 32 bytes
let item = DataItem::build_and_sign(&signer, None, None, tags, data)?;
// get the dataitem id
let id = item.arweave_id();
println!("dataitem id: {}", id);
// serialize for upload
let bytes = item.to_bytes()?;
println!("Ready to upload {} bytes", bytes.len());
Ok(())
}
Or for basic signed dataitem
use bundles_rs::ans104::{data_item::DataItem, tags::Tag};
// create unsigned data item
let tags = vec![Tag::new("Content-Type", "application/json")];
let data = br#"{"message": "Hello World"}"#.to_vec();
let mut item = DataItem::new(None, None, tags, data)?;
// sign dataitem
item.sign(&signer)?;
Working with signers
N.B: use random signer generation for testing purposes only
Arweave Signer
use bundles_rs::crypto::arweave::ArweaveSigner;
let signer = ArweaveSigner::from_jwk_file("wallet.json")?;
// from stringified JWK
let jwk_json = r#"{"kty":"RSA","n":"...","e":"AQAB","d":"..."}"#;
let signer = ArweaveSigner::from_jwk_str(jwk_json)?;
// random
let signer = ArweaveSigner::random()?;
// Arweave address
let address = signer.address();
println!("Arweave address: {}", address);
Ethereum Signer
use bundles_rs::crypto::ethereum::EthereumSigner;
// generate random key
let signer = EthereumSigner::random()?;
// or from private key bytes
let private_key = hex::decode("your_private_key_hex")?;
let signer = EthereumSigner::from_bytes(&private_key)?;
// EOA
let address = signer.address_string();
println!("Ethereum address: {}", address);
Solana Signer
use bundles_rs::crypto::solana::SolanaSigner;
// random
let signer = SolanaSigner::random();
// pk
let signer = SolanaSigner::from_base58("your_base58_private_key")?;
// from secret bytes
let secret = [0u8; 32]; // your secret bytes
let signer = SolanaSigner::from_secret_bytes(&secret)?;
// Get Solana address
let address = signer.address();
println!("Solana address: {}", address);
Ed25519Core Signer
use bundles_rs::crypto::ed25519::Ed25519Core;
// random
let signer = Ed25519Core::random();
// from seed bytes
let seed = [0u8; 32];
let signer = Ed25519Core::from_secret_bytes(&seed)?;
Verification
Manual
// verify signature and structure
item.verify()?;
// manual verification steps
assert_eq!(item.signature.len(), item.signature_type.signature_len());
assert_eq!(item.owner.len(), item.signature_type.owner_len());
With Signer
use bundles_rs::crypto::signer::Signer;
let message = item.signing_message();
let is_valid = signer.verify(&message, &item.signature)?;
assert!(is_valid);
Deep hash
use bundles_rs::ans104::deep_hash::{DeepHash, deep_hash_sync};
let data = b"custom data";
let hash_structure = DeepHash::List(vec![
DeepHash::Blob(b"custom"),
DeepHash::Blob(data),
]);
let hash = deep_hash_sync(&hash_structure);
println!("Deep hash hex: {}", hex::encode(hash));
Upload to Bundling services over HTTP (e.g. Turbo)
use reqwest::Client;
async fn upload_to_turbo(item: &DataItem) -> Result<String, Box<dyn std::error::Error>> {
let client = Client::new();
let bytes = item.to_bytes()?;
let response = client
.post("https://turbo.ardrive.io/tx/solana")
.header("Content-Type", "application/octet-stream")
.body(bytes)
.send()
.await?;
if response.status().is_success() {
let tx_id = response.text().await?;
Ok(tx_id)
} else {
Err(format!("Upload failed: {}", response.status()).into())
}
}
bundler crate
bundler
crate is Rust SDK to interact with Arweave (ANS-104) bundling services. This crate is designed to be backward compatible with existing bundling services and fine tuned for Turbo
Installation
[dependencies]
# main library
bundles_rs = { git = "https://github.com/loadnetwork/bundles-rs", branch = "main" }
# bundler only
bundler = { git = "https://github.com/loadnetwork/bundles-rs", branch = "main" }
Imports
use bundles_rs::bundler::BundlerClient;
use bundles_rs::ans104::{data_item::DataItem, tags::Tag};
use bundles_rs::crypto::solana::SolanaSigner;
Usage Example
Send Transaction (Solana)
let client = BundlerClient::new().url("https://upload.ardrive.io").build().unwrap();
let signer = SolanaSigner::random();
let tags = vec![Tag::new("content-type", "text/plain")];
let dataitem = DataItem::build_and_sign(&signer, None, None, tags, b"hello world".to_vec()).unwrap();
let tx = client.send_transaction(dataitem).await.unwrap();
println!("tx: {:?}", tx);
Send Transaction (Turbo)
let client = BundlerClient::turbo().build().unwrap();
let signer = SolanaSigner::random();
let tags = vec![Tag::new("content-type", "text/plain")];
let dataitem = DataItem::build_and_sign(&signer, None, None, tags, b"hello world turbo".to_vec()).unwrap();
let tx = client.send_transaction(dataitem).await.unwrap();
println!("tx: {:?}", tx);
Get Default Client Info
let client = BundlerClient::default().build().unwrap();
let info = client.info().await.unwrap();
println!("{:?}", info);
Get Turbo Client Info
let client = BundlerClient::turbo().build().unwrap();
let info = client.info().await.unwrap();
println!("{:?}", info);
Get Price for Bytes (Turbo)
let client = BundlerClient::turbo().build().unwrap();
let price = client.bytes_price(99999).await.unwrap();
println!("{:?}", price);
Get Rates (Turbo)
let client = BundlerClient::turbo().build().unwrap();
let rates = client.get_rates().await.unwrap();
println!("{:?}", rates);
Check Transaction Status (Turbo)
let client = BundlerClient::turbo().build().unwrap();
let status = client.status("w5n6r6PvqBRph2or4WiyjLumL9HE-IR_JgEcnct_3b0").await.unwrap();
println!("{:?}", status);
Turbo API References:
upload api: https://upload.ardrive.io/api-docs
payment api: https://payment.ardrive.io/api-docs
SDK source code: https://github.com/loadnetwork/bundles-rs
Last updated