Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
d4db47e
Add port discovery for automatic daemon detection across all SDKs
Nic-dorman Mar 25, 2026
d1b2fd8
Fix cross-platform issues in port discovery
Nic-dorman Mar 25, 2026
75fea7b
Add stale port file detection via PID checking to all SDKs
Nic-dorman Mar 25, 2026
0146625
Add CORS hardening and wallet endpoints with SDK bindings
Nic-dorman Mar 25, 2026
22cbbc1
Refactor antd to use ant-core Client instead of raw ant-node
Nic-dorman Mar 26, 2026
45a979b
Add payment_mode parameter for merkle batch payments
Nic-dorman Mar 26, 2026
40827f0
Implement data and file endpoints using ant-core Client
Nic-dorman Mar 26, 2026
75ac06a
Update docs, MCP server, and READMEs for payment modes and implemente…
Nic-dorman Mar 26, 2026
06674f0
Implement cost estimation and wallet approve across antd and all SDKs
Nic-dorman Mar 26, 2026
ecc8336
Use HTTP 503 for missing wallet instead of 400/402
Nic-dorman Mar 26, 2026
e4a7446
Fix Swift/Kotlin binding gaps and sweep remaining 8080 references
Nic-dorman Mar 30, 2026
a0a264b
Rewrite FFI bindings from v1 autonomi crate to v2 ant-core
Nic-dorman Mar 30, 2026
6cea8fb
Remove v1 autonomi repo prerequisite and update gem metadata
Nic-dorman Mar 30, 2026
5e2f994
Add external signer support for two-phase uploads
Nic-dorman Mar 30, 2026
4353350
Update antd README and root prerequisites for current architecture
Nic-dorman Mar 30, 2026
b42228b
Pass merkle payments contract address through all start scripts
Nic-dorman Mar 30, 2026
599827a
Remove private key from state file and console output
Nic-dorman Mar 30, 2026
1cc3591
Add external signer support for data uploads (POST /v1/data/prepare)
Nic-dorman Mar 30, 2026
32b3c50
Add external signer support to FFI bindings
Nic-dorman Mar 30, 2026
e923ff4
Remove graph entries from SDK (out of scope for launch)
Nic-dorman Mar 31, 2026
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
63 changes: 56 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ant-sdk

A developer-friendly SDK for the [Autonomi](https://autonomi.com) decentralized network. Store data, build DAGs, and more — from Go, JavaScript/TypeScript, Python, C#, Kotlin, Swift, Ruby, PHP, Dart, Lua, Elixir, Zig, Rust, C++, Java, or AI agents.
A developer-friendly SDK for the [Autonomi](https://autonomi.com) decentralized network. Store data permanently and more — from Go, JavaScript/TypeScript, Python, C#, Kotlin, Swift, Ruby, PHP, Dart, Lua, Elixir, Zig, Rust, C++, Java, or AI agents.

## Architecture

Expand Down Expand Up @@ -29,14 +29,61 @@ A developer-friendly SDK for the [Autonomi](https://autonomi.com) decentralized

**antd** is a local gateway daemon (written in Rust) that exposes the Autonomi network via REST and gRPC APIs. The SDKs and MCP server talk to antd — your application code never touches the network directly.

### Port Discovery

All SDKs support automatic daemon discovery. When antd starts, it writes a `daemon.port` file containing the REST and gRPC ports to a platform-specific location:

| Platform | Path |
|----------|------|
| Windows | `%APPDATA%\ant\daemon.port` |
| Linux | `~/.local/share/ant/daemon.port` (or `$XDG_DATA_HOME/ant/`) |
| macOS | `~/Library/Application Support/ant/daemon.port` |

Every SDK provides an auto-discover constructor that reads this file and connects automatically:

```python
# Python
client, url = RestClient.auto_discover()
```

```go
// Go
client, url := antd.NewClientAutoDiscover()
```

```typescript
// TypeScript
const { client, url } = RestClient.autoDiscover();
```

This is especially useful in managed mode, where a parent process (e.g. indelible) spawns antd with `--rest-port 0` to let the OS assign a free port. The SDK discovers the actual port via the port file without any hardcoded configuration.

If no port file is found, all SDKs fall back to the default REST endpoint (`http://localhost:8082`) or gRPC target (`localhost:50051`).

### External Signer Support

All SDKs support two-phase uploads for applications that manage their own wallet (browser wallets, hardware signers, etc.):

1. **`prepare_upload(path)`** -- returns payment details (quote hashes, amounts, contract addresses, RPC URL)
2. Your application submits EVM payment transactions using its own signer
3. **`finalize_upload(upload_id, tx_hashes)`** -- confirms payments and stores data on the network

### Payment Modes

All data and file upload operations accept an optional `payment_mode` parameter (defaults to `"auto"`):

- **`auto`** — Uses merkle batch payments for uploads of 64+ chunks, single payments otherwise. Recommended for most use cases.
- **`merkle`** — Forces merkle batch payments regardless of chunk count (minimum 2 chunks). Saves gas on larger uploads.
- **`single`** — Forces per-chunk payments. Useful for small data or debugging.

## Components

### Infrastructure

| Component | Language | Description |
|-----------|----------|-------------|
| [`antd/`](antd/) | Rust | REST + gRPC gateway daemon |
| [`antd-mcp/`](antd-mcp/) | Python | MCP server exposing 14 tools for AI agents (Claude, etc.) |
| [`antd-mcp/`](antd-mcp/) | Python | MCP server exposing 19 tools for AI agents (Claude, etc.) |
| [`ant-dev/`](ant-dev/) | Python | Developer CLI for local environment management |

### Language SDKs
Expand Down Expand Up @@ -65,9 +112,9 @@ A developer-friendly SDK for the [Autonomi](https://autonomi.com) decentralized

**Required:**

- **Rust** toolchain — for building antd and the Autonomi network
- **Rust** toolchain — for building antd
- **Python 3.10+** — for the dev CLI (`ant-dev`) and MCP server
- **autonomi** repo cloned as a sibling: `git clone https://github.com/maidsafe/autonomi ../autonomi`
- **ant-node** repo cloned as sibling (for local testnet only): `git clone https://github.com/WithAutonomi/ant-node ../ant-node`

**Language-specific** (install only what you need):

Expand Down Expand Up @@ -131,14 +178,17 @@ client = AntdClient()
status = client.health()
print(f"Network: {status.network}")

# Store data on the network
# Store data on the network (payment_mode defaults to "auto")
result = client.data_put_public(b"Hello, Autonomi!")
print(f"Address: {result.address}")
print(f"Cost: {result.cost} atto tokens")

# Retrieve it back
data = client.data_get_public(result.address)
print(data.decode()) # "Hello, Autonomi!"

# For large uploads, you can explicitly set payment_mode:
# result = client.data_put_public(large_data, payment_mode="merkle")
```

### Write your first app (JavaScript/TypeScript)
Expand Down Expand Up @@ -216,7 +266,7 @@ import (
)

func main() {
client := antd.NewClient(antd.DefaultBaseURL)
client, _ := antd.NewClientAutoDiscover()
ctx := context.Background()

health, err := client.Health(ctx)
Expand Down Expand Up @@ -371,7 +421,6 @@ The Autonomi network provides these core primitives, all accessible through the
|-----------|-------------|
| **Data** | Store/retrieve arbitrary byte blobs (public or private/encrypted) |
| **Chunks** | Low-level content-addressed storage |
| **Graph Entries** | Append-only DAG nodes with parent/descendant links |
| **Files** | File/directory upload with archive manifests |

## Developer CLI Reference
Expand Down
29 changes: 25 additions & 4 deletions ant-dev/src/ant_dev/cmd_start.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,19 @@
)
from .process import start_process, wait_for_http

DEFAULT_REST_URL = "http://localhost:8082"

def _discover_rest_url() -> str:
"""Try to discover antd REST URL via port file, fall back to default."""
try:
from antd import discover_daemon_url
url = discover_daemon_url()
if url:
return url
except ImportError:
pass
return DEFAULT_REST_URL


# ── ANSI colours (disabled on Windows without VT support) ──

Expand Down Expand Up @@ -109,6 +122,13 @@ def run(args) -> None:
}
if wallet_key:
antd_env["AUTONOMI_WALLET_KEY"] = wallet_key
if manifest.get("evm"):
evm = manifest["evm"]
antd_env["EVM_RPC_URL"] = evm.get("rpc_url", "")
antd_env["EVM_PAYMENT_TOKEN_ADDRESS"] = evm.get("payment_token_address", "")
antd_env["EVM_DATA_PAYMENTS_ADDRESS"] = evm.get("data_payments_address", "")
if evm.get("merkle_payments_address"):
antd_env["EVM_MERKLE_PAYMENTS_ADDRESS"] = evm["merkle_payments_address"]

antd_cmd = ["cargo", "run", "--", "--network", "local"]
antd_proc = start_process(antd_cmd, cwd=antd_dir, env=antd_env, log_file=LOG_FILE)
Expand All @@ -122,21 +142,22 @@ def run(args) -> None:
save_state({
"devnet_pid": devnet_proc.pid,
"antd_pid": antd_proc.pid,
"wallet_key": wallet_key or "",
"wallet_configured": bool(wallet_key),
"bootstrap_peers": bootstrap_peers,
})

print()
if ready:
rest_url = _discover_rest_url()
print(green("=== Ready! ==="))
print()
print(white(" REST: http://localhost:8082"))
print(white(f" REST: {rest_url}"))
print(white(" gRPC: localhost:50051"))
if wallet_key:
print(white(f" Key: {wallet_key[:10]}..."))
print(white(" Wallet: configured"))
print()
print(gray("Quick test:"))
print(gray(" curl http://localhost:8082/health"))
print(gray(f" curl {rest_url}/health"))
print()
print(gray("To tear down:"))
print(gray(" ant dev stop"))
Expand Down
22 changes: 18 additions & 4 deletions ant-dev/src/ant_dev/cmd_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@
from .env import is_windows, load_state
from .process import is_alive

DEFAULT_REST_URL = "http://localhost:8082"

def _discover_rest_url() -> str:
"""Try to discover antd REST URL via port file, fall back to default."""
try:
from antd import discover_daemon_url
url = discover_daemon_url()
if url:
return url
except ImportError:
pass
return DEFAULT_REST_URL


def _color(code: str, text: str) -> str:
if is_windows() and "WT_SESSION" not in os.environ:
Expand Down Expand Up @@ -48,7 +61,8 @@ def run(args) -> None:
# Health check
print()
try:
r = httpx.get("http://localhost:8082/health", timeout=5)
rest_url = _discover_rest_url()
r = httpx.get(f"{rest_url}/health", timeout=5)
data = r.json()
ok = data.get("status") == "ok" or data.get("ok", False)
network = data.get("network", "unknown")
Expand All @@ -58,8 +72,8 @@ def run(args) -> None:
except (httpx.HTTPError, OSError):
print(f" REST health: {red('unreachable')}")

# Wallet key
if key := state.get("wallet_key"):
print(f" Wallet key: {key[:10]}...")
# Wallet status
if state.get("wallet_configured"):
print(f" Wallet: {green('configured')}")

print()
50 changes: 38 additions & 12 deletions ant-dev/src/ant_dev/cmd_wallet.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""``ant dev wallet [show|fund]`` — Show or fund the test wallet."""
"""``ant dev wallet [show|fund]`` — Show wallet info or fund the test wallet."""

from __future__ import annotations

Expand All @@ -8,33 +8,59 @@

from .env import load_state

DEFAULT_REST_URL = "http://localhost:8082"

def _discover_rest_url() -> str:
"""Try to discover antd REST URL via port file, fall back to default."""
try:
from antd import discover_daemon_url
url = discover_daemon_url()
if url:
return url
except ImportError:
pass
return DEFAULT_REST_URL


def run(args) -> None:
state = load_state()
action = args.action
rest_url = _discover_rest_url()

if action == "show":
key = state.get("wallet_key")
if not key:
print("No wallet key found. Is the local environment running?")
if not state.get("wallet_configured"):
print("No wallet configured. Is the local environment running?")
print(" ant dev start")
sys.exit(1)
print(f"Wallet key: {key[:10]}...{key[-6:]}")
print(f"Full key: {key}")
# Query wallet address and balance from antd (never display the key)
try:
addr_resp = httpx.get(f"{rest_url}/v1/wallet/address", timeout=5)
bal_resp = httpx.get(f"{rest_url}/v1/wallet/balance", timeout=5)
if addr_resp.status_code == 200 and bal_resp.status_code == 200:
address = addr_resp.json().get("address", "unknown")
balance = bal_resp.json().get("balance", "unknown")
gas = bal_resp.json().get("gas_balance", "unknown")
print(f"Address: {address}")
print(f"Balance: {balance} atto")
print(f"Gas balance: {gas} wei")
else:
print("Wallet not available. Is antd running with AUTONOMI_WALLET_KEY?")
sys.exit(1)
except (httpx.HTTPError, OSError):
print("Cannot reach antd daemon. Is it running?")
sys.exit(1)

elif action == "fund":
key = state.get("wallet_key")
if not key:
print("No wallet key found. Start the environment first.")
if not state.get("wallet_configured"):
print("No wallet configured. Start the environment first.")
sys.exit(1)
# On local testnet the EVM testnet already funds this key.
# Verify via health check.
try:
r = httpx.get("http://localhost:8082/health", timeout=5)
rest_url = _discover_rest_url()
r = httpx.get(f"{rest_url}/health", timeout=5)
data = r.json()
if data.get("status") == "ok" or data.get("ok", False):
print("Wallet is already funded on local testnet.")
print(f"Key: {key[:10]}...")
else:
print("Daemon is not healthy. Check: ant dev status")
except (httpx.HTTPError, OSError):
Expand Down
20 changes: 5 additions & 15 deletions antd-cpp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ cmake --build build
#include <iostream>

int main() {
antd::Client client; // defaults to http://localhost:8080
antd::Client client; // defaults to http://localhost:8082

// Check daemon health
auto health = client.health();
Expand Down Expand Up @@ -68,7 +68,7 @@ No additional dependencies are required — only C++20 `<future>`.
#include <iostream>

int main() {
antd::AsyncClient client; // defaults to http://localhost:8080
antd::AsyncClient client; // defaults to http://localhost:8082

// Fire off two requests concurrently
auto health_future = client.health();
Expand Down Expand Up @@ -131,7 +131,7 @@ for (auto& f : futures) {

## gRPC Transport

The SDK includes a `GrpcClient` class that provides the same 19 methods as the
The SDK includes a `GrpcClient` class that provides the same methods as the
REST `Client`, but communicates over gRPC. This can offer lower latency and
better streaming support for large data transfers.

Expand Down Expand Up @@ -197,14 +197,14 @@ ant dev start
## Configuration

```cpp
// Default: http://localhost:8080, 5 minute timeout
// Default: http://localhost:8082, 5 minute timeout
antd::Client client;

// Custom URL
antd::Client client("http://custom-host:9090");

// Custom URL and timeout (seconds)
antd::Client client("http://localhost:8080", 30);
antd::Client client("http://localhost:8082", 30);
```

## API Reference
Expand Down Expand Up @@ -234,15 +234,6 @@ All methods throw `antd::AntdError` (or a subclass) on failure.
| `chunk_put(data)` | Store a raw chunk |
| `chunk_get(address)` | Retrieve a chunk |

### Graph Entries (DAG Nodes)

| Method | Description |
|--------|-------------|
| `graph_entry_put(secret_key, parents, content, descendants)` | Create entry |
| `graph_entry_get(address)` | Read entry |
| `graph_entry_exists(address)` | Check if exists |
| `graph_entry_cost(public_key)` | Estimate creation cost |

### Files & Directories

| Method | Description |
Expand Down Expand Up @@ -303,5 +294,4 @@ See the [examples/](examples/) directory:
- `02-data` — Public data storage and retrieval
- `03-chunks` — Raw chunk operations
- `04-files` — File and directory upload/download
- `05-graph` — Graph entry (DAG node) operations
- `06-private-data` — Private encrypted data storage
2 changes: 1 addition & 1 deletion antd-cpp/examples/01-connect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

int main() {
try {
antd::Client client; // defaults to http://localhost:8080
antd::Client client; // defaults to http://localhost:8082

auto health = client.health();
std::cout << "OK: " << (health.ok ? "true" : "false") << "\n";
Expand Down
Loading