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
46 changes: 44 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,45 @@ 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`).

### 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
Expand Down Expand Up @@ -131,14 +170,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 +258,7 @@ import (
)

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

health, err := client.Health(ctx)
Expand Down
18 changes: 16 additions & 2 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 @@ -128,15 +141,16 @@ def run(args) -> None:

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()
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
16 changes: 15 additions & 1 deletion 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 Down
16 changes: 15 additions & 1 deletion ant-dev/src/ant_dev/cmd_wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@

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()
Expand All @@ -30,7 +43,8 @@ def run(args) -> None:
# 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.")
Expand Down
30 changes: 25 additions & 5 deletions antd-cpp/include/antd/client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@
#include <string_view>
#include <vector>

#include "discover.hpp"
#include "errors.hpp"
#include "models.hpp"

namespace antd {

/// Default address of the antd daemon.
inline constexpr const char* kDefaultBaseURL = "http://localhost:8080";
inline constexpr const char* kDefaultBaseURL = "http://localhost:8082";

/// Default request timeout in seconds (5 minutes).
inline constexpr int kDefaultTimeoutSeconds = 300;
Expand All @@ -34,6 +35,14 @@ class Client {
Client(Client&&) noexcept;
Client& operator=(Client&&) noexcept;

/// Create a client by auto-discovering the daemon port from the
/// daemon.port file. Falls back to kDefaultBaseURL if not found.
static Client auto_discover(int timeout_seconds = kDefaultTimeoutSeconds) {
auto url = discover_daemon_url();
if (url.empty()) url = kDefaultBaseURL;
return Client(url, timeout_seconds);
}

// --- Health ---

/// Check daemon status.
Expand All @@ -42,13 +51,13 @@ class Client {
// --- Data (Immutable) ---

/// Store public immutable data on the network.
PutResult data_put_public(const std::vector<uint8_t>& data);
PutResult data_put_public(const std::vector<uint8_t>& data, const std::string& payment_mode = "");

/// Retrieve public data by address.
std::vector<uint8_t> data_get_public(std::string_view address);

/// Store private encrypted data on the network.
PutResult data_put_private(const std::vector<uint8_t>& data);
PutResult data_put_private(const std::vector<uint8_t>& data, const std::string& payment_mode = "");

/// Retrieve private data using a data map.
std::vector<uint8_t> data_get_private(std::string_view data_map);
Expand Down Expand Up @@ -84,13 +93,13 @@ class Client {
// --- Files & Directories ---

/// Upload a local file to the network.
PutResult file_upload_public(std::string_view path);
PutResult file_upload_public(std::string_view path, const std::string& payment_mode = "");

/// Download a file from the network to a local path.
void file_download_public(std::string_view address, std::string_view dest_path);

/// Upload a local directory to the network.
PutResult dir_upload_public(std::string_view path);
PutResult dir_upload_public(std::string_view path, const std::string& payment_mode = "");

/// Download a directory from the network to a local path.
void dir_download_public(std::string_view address, std::string_view dest_path);
Expand All @@ -104,6 +113,17 @@ class Client {
/// Estimate the cost of uploading a file.
std::string file_cost(std::string_view path, bool is_public, bool include_archive);

// --- Wallet ---

/// Get the wallet address configured on the daemon.
WalletAddress wallet_address();

/// Get the wallet balance (tokens and gas).
WalletBalance wallet_balance();

/// Approve the wallet to spend tokens on payment contracts (one-time operation).
bool wallet_approve();

private:
struct Impl;
std::unique_ptr<Impl> impl_;
Expand Down
19 changes: 19 additions & 0 deletions antd-cpp/include/antd/discover.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once

#include <string>

namespace antd {

/// Read the daemon.port file written by antd on startup and return the REST
/// base URL (e.g. "http://127.0.0.1:8082").
/// Returns an empty string if the port file is missing, unreadable, or stale
/// (i.e. the recorded PID is no longer alive).
std::string discover_daemon_url();

/// Read the daemon.port file written by antd on startup and return the gRPC
/// target (e.g. "127.0.0.1:50051").
/// Returns an empty string if the port file has no gRPC line, is unreadable,
/// or stale (i.e. the recorded PID is no longer alive).
std::string discover_grpc_target();

} // namespace antd
7 changes: 7 additions & 0 deletions antd-cpp/include/antd/errors.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ class NetworkError : public AntdError {
NetworkError(const std::string& msg) : AntdError(502, msg) {}
};

/// Service unavailable, e.g. wallet not configured (HTTP 503).
class ServiceUnavailableError : public AntdError {
public:
ServiceUnavailableError(const std::string& msg) : AntdError(503, msg) {}
};

/// Throw the appropriate AntdError subclass for an HTTP status code.
[[noreturn]] inline void error_for_status(int code, const std::string& message) {
switch (code) {
Expand All @@ -73,6 +79,7 @@ class NetworkError : public AntdError {
case 413: throw TooLargeError(message);
case 500: throw InternalError(message);
case 502: throw NetworkError(message);
case 503: throw ServiceUnavailableError(message);
default: throw AntdError(code, message);
}
}
Expand Down
9 changes: 9 additions & 0 deletions antd-cpp/include/antd/grpc_client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <string_view>
#include <vector>

#include "discover.hpp"
#include "errors.hpp"
#include "models.hpp"

Expand Down Expand Up @@ -38,6 +39,14 @@ class GrpcClient {
GrpcClient(GrpcClient&&) noexcept;
GrpcClient& operator=(GrpcClient&&) noexcept;

/// Create a client by auto-discovering the daemon gRPC port from the
/// daemon.port file. Falls back to kDefaultGrpcTarget if not found.
static GrpcClient auto_discover() {
auto target = discover_grpc_target();
if (target.empty()) target = kDefaultGrpcTarget;
return GrpcClient(target);
}

// --- Health ---

/// Check daemon status.
Expand Down
11 changes: 11 additions & 0 deletions antd-cpp/include/antd/models.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,15 @@ struct Archive {
std::vector<ArchiveEntry> entries;
};

/// Wallet address response.
struct WalletAddress {
std::string address; // 0x-prefixed hex
};

/// Wallet balance response.
struct WalletBalance {
std::string balance; // atto tokens as string
std::string gas_balance; // atto tokens as string
};

} // namespace antd
Loading