Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 28 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,16 @@ irm https://raw.githubusercontent.com/WithAutonomi/ant-client/main/install.ps1 |
### Store and retrieve a file (local devnet)

```bash
# 1. Start a local devnet (spins up nodes + a local Anvil EVM chain)
# See "Local Development with Devnet" below for details.
# 1. Start a local devnet (spins up 25 nodes + a local Anvil EVM chain)
cargo run --release --example start-local-devnet

# 2. Upload a file (public — anyone with the address can download)
# 2. Upload a file (the manifest is auto-written to the shared data dir)
SECRET_KEY=0x... ant file upload photo.jpg --public \
--devnet-manifest /tmp/devnet.json --allow-loopback --evm-network local
# Output: ADDRESS=abc123...
--devnet-manifest ~/.local/share/ant/devnet-manifest.json --allow-loopback --evm-network local

# 3. Download it back
ant file download abc123... -o photo_copy.jpg \
--devnet-manifest /tmp/devnet.json --allow-loopback --evm-network local
--devnet-manifest ~/.local/share/ant/devnet-manifest.json --allow-loopback --evm-network local
```

### Store and retrieve a file (Arbitrum mainnet)
Expand Down Expand Up @@ -454,29 +453,41 @@ Autonomi supports two payment strategies:

### Local Development with Devnet

`LocalDevnet` spins up a local Autonomi network with an embedded Anvil EVM blockchain for development and testing:
Two ready-made examples spin up a local network and write a manifest to the shared data directory (`~/.local/share/ant/` on Linux, `~/Library/Application Support/ant/` on macOS, `%APPDATA%\ant\` on Windows). Any consumer that checks this directory — ant-gui, ant-cli, ant-tui — will auto-detect the devnet.

```bash
# Local Anvil devnet (25 nodes + embedded EVM blockchain)
# Includes a pre-funded wallet key in the manifest
cargo run --release --example start-local-devnet

# Sepolia testnet devnet (25 nodes + real Arbitrum Sepolia contracts)
# No wallet key — connect your own funded Sepolia wallet
cargo run --release --example start-devnet-sepolia
```

Both write `devnet-manifest.json` to the shared data dir and clean it up on Ctrl+C.

The [ant-gui](https://github.com/WithAutonomi/ant-ui) desktop app auto-detects the manifest on startup and switches to devnet/Sepolia mode — no manual configuration needed. The manifest can also be passed to the CLI explicitly via `--devnet-manifest <path>`.

#### Programmatic usage

```rust
use ant_core::data::LocalDevnet;

// Start a minimal devnet (5 nodes + Anvil EVM chain)
let mut devnet = LocalDevnet::start_minimal().await?;
// Start a devnet (25 nodes + Anvil EVM chain)
let devnet = LocalDevnet::start(DevnetConfig::default()).await?;

// Create a client with a pre-funded wallet (ready to upload)
let client = devnet.create_funded_client().await?;

// Upload data
let result = client.data_upload(Bytes::from("test payload")).await?;

// Write manifest for CLI usage
devnet.write_manifest(Path::new("/tmp/devnet.json")).await?;
// Write manifest for discovery by other tools
let manifest_path = ant_core::config::data_dir()?.join("devnet-manifest.json");
devnet.write_manifest(&manifest_path).await?;

// Clean up
devnet.shutdown().await?;
```

The manifest JSON can be passed to the CLI via `--devnet-manifest` for local testing.

### Chunk Cache

The client includes an in-memory LRU cache for recently accessed chunks:
Expand Down Expand Up @@ -617,7 +628,7 @@ cargo fmt --all -- --check

# Run the CLI
cargo run --bin ant -- --help
cargo run --bin ant -- file upload photo.jpg --public --devnet-manifest /tmp/devnet.json --allow-loopback
cargo run --bin ant -- file upload photo.jpg --public --devnet-manifest ~/.local/share/ant/devnet-manifest.json --allow-loopback
cargo run --bin ant -- node daemon status
```

Expand Down
4 changes: 4 additions & 0 deletions ant-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ tokio-test = "0.4"
name = "start-local-devnet"
path = "examples/start-local-devnet.rs"

[[example]]
name = "start-devnet-sepolia"
path = "examples/start-devnet-sepolia.rs"

[[test]]
name = "e2e_chunk"
path = "tests/e2e_chunk.rs"
Expand Down
107 changes: 107 additions & 0 deletions ant-core/examples/start-devnet-sepolia.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//! Start a local devnet with 25 nodes using Arbitrum Sepolia for payments.
//!
//! Uses the existing deployed contracts on Arbitrum Sepolia.
//! Nodes verify payments against the real Sepolia PaymentVault.
//!
//! Writes a manifest to the shared ant data directory so any consumer
//! (ant-gui, ant-cli, ant-tui) auto-detects Sepolia mode on startup.
//!
//! Unlike the local Anvil devnet, no wallet key is provided — the user
//! must connect their own funded Sepolia wallet (e.g. via WalletConnect
//! in ant-gui, or SECRET_KEY env var in ant-cli).
//!
//! # Usage
//!
//! ```bash
//! cargo run --release --example start-devnet-sepolia
//! ```

use ant_core::data::EvmNetwork;
use ant_node::devnet::{Devnet, DevnetConfig};

fn manifest_path() -> std::path::PathBuf {
let data_dir = ant_core::config::data_dir().expect("Could not determine data directory");
std::fs::create_dir_all(&data_dir).ok();
data_dir.join("devnet-manifest.json")
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
)
.with_writer(std::io::stderr)
.init();

let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.thread_stack_size(8 * 1024 * 1024)
.build()?;

runtime.block_on(async {
let evm_network = EvmNetwork::ArbitrumSepoliaTest;

let rpc_url = evm_network.rpc_url().to_string();
let token_addr = format!("{}", evm_network.payment_token_address());
let vault_addr = format!("{}", evm_network.payment_vault_address());

println!("Starting Sepolia devnet...");
println!("RPC: {rpc_url}");
println!("Token: {token_addr}");
println!("Vault: {vault_addr}");

let config = DevnetConfig {
evm_network: Some(evm_network),
..DevnetConfig::default()
};

println!("Starting {} nodes...", config.node_count);

let mut devnet = Devnet::new(config).await?;
devnet.start().await?;

let bootstrap_addrs: Vec<String> = devnet
.bootstrap_addrs()
.iter()
.map(|a| format!("{a}"))
.collect();

let manifest = serde_json::json!({
"base_port": 0,
"node_count": devnet.config().node_count,
"bootstrap": devnet.bootstrap_addrs().iter().map(|a| a.to_string()).collect::<Vec<_>>(),
"data_dir": devnet.config().data_dir.to_string_lossy(),
"created_at": "",
"evm": {
"rpc_url": rpc_url,
"wallet_private_key": "",
"payment_token_address": token_addr,
"payment_vault_address": vault_addr,
}
});

let path = manifest_path();
std::fs::write(&path, serde_json::to_string_pretty(&manifest)?)?;

println!();
println!("=== Sepolia Devnet is running! ===");
println!();
println!("Nodes: {}", devnet.config().node_count);
println!("Bootstrap peers: {:?}", bootstrap_addrs);
println!("Manifest: {}", path.display());
println!();
println!("No wallet key is embedded — connect your own funded Sepolia wallet.");
println!();
println!("Press Ctrl+C to stop.");

tokio::signal::ctrl_c().await?;
println!("Shutting down...");

if path.exists() {
std::fs::remove_file(&path).ok();
}

Ok(())
})
}
92 changes: 52 additions & 40 deletions ant-core/examples/start-local-devnet.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
//! Start a local devnet with EVM payments.
//! Start a local devnet with 25 nodes and EVM payments.
//!
//! Launches a minimal Autonomi network (5 nodes) with an embedded Anvil
//! blockchain, writes a manifest to `/tmp/ant-devnet-manifest.json`,
//! and waits for Ctrl+C.
//! Launches an Autonomi network with an embedded Anvil blockchain,
//! writes a manifest to the shared ant data directory, and waits for Ctrl+C.
//!
//! Any consumer that checks `ant_core::config::data_dir()` for
//! `devnet-manifest.json` (ant-gui, ant-cli, ant-tui) will auto-detect
//! devnet mode on startup.
//!
//! # Usage
//!
//! ```bash
//! cargo run --example start-local-devnet
//! cargo run --release --example start-local-devnet
//! ```

use ant_core::data::LocalDevnet;
use std::path::PathBuf;
use ant_node::devnet::DevnetConfig;

fn manifest_path() -> std::path::PathBuf {
let data_dir = ant_core::config::data_dir().expect("Could not determine data directory");
std::fs::create_dir_all(&data_dir).ok();
data_dir.join("devnet-manifest.json")
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
Expand All @@ -23,36 +31,40 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.with_writer(std::io::stderr)
.init();

// Start a minimal local devnet with EVM payments
let devnet = LocalDevnet::start_minimal().await?;

// Write manifest so the CLI example can use it
let manifest_path = PathBuf::from("/tmp/ant-devnet-manifest.json");
devnet.write_manifest(&manifest_path).await?;

println!();
println!("=== Devnet is running! ===");
println!();
println!("Bootstrap peers: {:?}", devnet.bootstrap_addrs());
println!("Wallet key: {}", devnet.wallet_private_key());
println!("Manifest: {}", manifest_path.display());
println!();
println!("# Upload a file:");
println!(
"cargo run --example cli -- --devnet-manifest {} upload --file <YOUR_FILE>",
manifest_path.display()
);
println!();
println!("# Download it back:");
println!(
"cargo run --example cli -- --devnet-manifest {} download --datamap <HEX> --output <OUTPUT_PATH>",
manifest_path.display()
);
println!();
println!("Press Ctrl+C to stop.");

tokio::signal::ctrl_c().await?;
println!("Shutting down...");

Ok(())
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.thread_stack_size(8 * 1024 * 1024)
.build()?;

runtime.block_on(async {
let config = DevnetConfig::default();
println!(
"Starting local Anvil devnet with {} nodes...",
config.node_count
);

let devnet = LocalDevnet::start(config).await?;

let path = manifest_path();
devnet.write_manifest(&path).await?;

println!();
println!("=== Local Devnet is running! ===");
println!();
println!("Nodes: {}", devnet.manifest().node_count);
println!("Bootstrap peers: {:?}", devnet.bootstrap_addrs());
println!("Wallet key: {}", devnet.wallet_private_key());
println!("Manifest: {}", path.display());
println!();
println!("Press Ctrl+C to stop.");

tokio::signal::ctrl_c().await?;
println!("Shutting down...");

if path.exists() {
std::fs::remove_file(&path).ok();
}

Ok(())
})
}
Loading