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.
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 = "x.x.x" }
crypto = { git = "https://github.com/loadnetwork/bundles-rs", version = "x.x.x" }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| Blockchain | Signature Type |
|---|---|
| Arweave | RSA-PSS |
| Ethereum | secp256k1 |
| Solana | Ed25519 (with base58 solana flavoring) |
| - | Ed25519Core (raw Ed25519) |
This ANS-104 dataitems client fully implements the ANS-104 specification as-is
| Constraint | bundles-rs | Spec | arbundles js | HyperBEAM ar_bundles |
|---|---|---|---|---|
| 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 |
Special thanks for @nikooo777 for compiling this list.
bundles-rshas been added to the compiled list.
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(())
}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)?;N.B: use random signer generation for testing purposes only
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);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);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);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)?;// 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());use bundles_rs::crypto::signer::Signer;
let message = item.signing_message();
let is_valid = signer.verify(&message, &item.signature)?;
assert!(is_valid);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 (e.g. Turbo)
use bundles_rs::bundler::BundlerClient;
use bundles_rs::ans104::{data_item::DataItem, tags::Tag};
use bundles_rs::crypto::solana::SolanaSigner;
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);For fully detailed examples, checkout the bundler crate
Licensed at your option under either of:
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.