A comprehensive guide to using the Autonomi network with the C++ SDK.
# Prerequisites
# - C++20 compiler (GCC 12+, Clang 15+, MSVC 2022+)
# - CMake 3.24+
# - antd daemon running (ant dev start)Add to your CMakeLists.txt:
include(FetchContent)
FetchContent_Declare(
antd
GIT_REPOSITORY https://github.com/WithAutonomi/ant-sdk.git
GIT_TAG main
SOURCE_SUBDIR antd-cpp
)
FetchContent_MakeAvailable(antd)
target_link_libraries(my_app PRIVATE antd::antd)Or scaffold a new project:
ant dev init cpp --name my-project#include <antd/antd.hpp>
int main() {
// REST transport (default)
auto client = antd::Client::create();
// Custom endpoint
auto client2 = antd::Client::builder()
.transport("rest")
.base_url("http://localhost:8082")
.timeout(std::chrono::seconds(30))
.build();
// gRPC transport
auto client3 = antd::Client::builder()
.transport("grpc")
.target("localhost:50051")
.build();
return 0;
}The client is synchronous. All methods block until the operation completes or throws.
auto status = client.health();
std::println("Healthy: {}", status.ok);
std::println("Network: {}", status.network); // "local", "default", "alpha"#include <antd/antd.hpp>
#include <vector>
#include <string>
// Store
std::vector<uint8_t> payload(
std::string_view("Hello, Autonomi!").begin(),
std::string_view("Hello, Autonomi!").end()
);
auto result = client.data_put_public(payload);
std::println("Address: {}", result.address);
std::println("Cost: {} atto tokens", result.cost);
// Retrieve
auto data = client.data_get_public(result.address);
std::string text(data.begin(), data.end());
std::println("{}", text); // "Hello, Autonomi!"
// Cost estimation — returns size, chunks, gas, and payment mode
auto est = client.data_cost(payload);
std::println("Estimate: {} bytes in {} chunks, {} atto, gas {} wei, mode {}",
est.file_size, est.chunk_count, est.cost, est.estimated_gas_cost_wei, est.payment_mode);// Store (self-encrypting)
std::vector<uint8_t> secret(
std::string_view("secret message").begin(),
std::string_view("secret message").end()
);
auto result = client.data_put(secret);
auto data_map = result.data_map; // Keep this secret!
// Retrieve (decrypt)
auto data = client.data_get(data_map);
std::string text(data.begin(), data.end());
std::println("{}", text);// Upload a file
auto result = client.file_put_public("/path/to/file.txt");
std::println("File address: {}", result.address);
// Download a file
client.file_get_public(result.address, "/path/to/output.txt");
// Cost estimation — returns UploadCostEstimate with size, chunks, gas, payment mode
auto est = client.file_cost("/path/to/file.txt");A pointer is an owner-signed address you can move to new data. antd signs with the owner keys it loaded at startup (antd --pointer-keys <file-or-dir>, key files from ant pointer keygen), so owner is the pointer's address, never a key. Every create, update and transfer pays (about the price of one chunk) and is never retried; reads are free. A transfer hands the pointer over to another pointer for good. Needs antd 0.16.0 or later (the first release with pointers). Walk-through: Tutorial: Point at a Public File and Update It.
const std::string owner = client.pointer_keys().at(0).address; // a pointer antd can write
auto bytes = [](std::string s) { return std::vector<uint8_t>(s.begin(), s.end()); };
auto v1 = client.data_put_public(bytes("version 1"));
auto v2 = client.data_put_public(bytes("version 2"));
client.pointer_create(owner, v1.address); // pays; counter 0
auto w = client.pointer_update(owner, v2.address); // pays again; counter 1
auto target = client.pointer_resolve(owner).target; // == v2.address (free)
auto f = client.pointer_finality(owner); // free
if (f.status == antd::PointerFinalityStatus::Open) {
std::println("open at counter {}", f.counter.value_or(0));
}Counters are std::uint64_t; unset values are std::optional. pointer_transfer, pointer_get, pointer_controller, pointer_prepare and pointer_finalize complete the set, on GrpcClient too.
Runnable example: antd-cpp/examples/08-pointers.cpp. Set ANTD_POINTER_OWNER to an address the daemon holds a key for, one it has not written yet (each run creates the pointer and pays for its writes), and ANTD_BASE_URL for a daemon other than http://localhost:8082:
cd antd-cpp
cmake -S . -B build && cmake --build build
ANTD_POINTER_OWNER=<address> ./build/08-pointers#include <antd/antd.hpp>
try {
client.data_get_public("nonexistent");
} catch (const antd::NotFoundException&) {
std::println("Not found");
} catch (const antd::PaymentError&) {
std::println("Payment issue");
} catch (const antd::NetworkError&) {
std::println("Network unreachable");
} catch (const antd::AntdError& e) {
std::println("Error ({}): {}", e.status_code(), e.what());
}Exception hierarchy (all inherit from antd::AntdError):
| Exception | HTTP Code | When |
|---|---|---|
antd::BadRequestError |
400 | Invalid parameters |
antd::PaymentError |
402 | Insufficient funds |
antd::NotFoundException |
404 | Resource not found |
antd::AlreadyExistsError |
409 | Duplicate creation |
antd::ForkError |
409 | Version conflict |
antd::TooLargeError |
413 | Payload too large |
antd::InternalError |
500 | Server error |
antd::NetworkError |
502 | Network unreachable |
A manifest describes a set of files as one ant://manifest/ link, or as a .ant file when the link would be too long. Each entry is either a public file's address or an embedded DataMap (the manifest is then the key to a private file), with an optional path. Building and decoding upload and pay for nothing: upload the files first, then describe them. Needs antd 0.16.0 or later. Walk-through: Tutorial: Share a Set of Files as One Link.
auto bytes_of = [](std::string s) { return std::vector<uint8_t>(s.begin(), s.end()); };
auto one = client.data_put_public(bytes_of("first")); // pays
auto two = client.data_put_public(bytes_of("second")); // pays
antd::BuildManifestOptions opts;
opts.name = "example";
auto built = client.build_manifest({ // free
{.path = "notes/one.txt", .address = one.address},
{.path = "notes/two.txt", .address = two.address},
}, opts);
// built.manifest: the .ant bytes; built.link: empty if it would pass 1,500 bytes, so share the bytes instead
auto decoded = client.decode_manifest_link(built.link, /*resolve=*/true); // free
for (const auto& e : decoded.entries) {
std::cout << e.effective_name << " " << e.kind << " "
<< (e.exact_size ? std::to_string(*e.exact_size) : e.error) << "\n";
}decode_manifest(bytes, resolve) takes raw .ant bytes. Set .data_map instead of .address for an embedded entry; BuildManifestOptions::keep_shrunk_data_maps (default false) keeps shrunk DataMaps as given. Sizes are std::optional<std::uint64_t>. On AsyncClient and GrpcClient too.
Every upload result (DataPutResult, DataPutPublicResult, FilePutResult, FilePutPublicResult and, for external-signer uploads, FinalizeUploadResult) also has std::optional<std::string> sha256 (empty when none): the plaintext's SHA-256 (hex). To record it in a manifest, pass it as the entry's sha256: ManifestEntryInput::sha256 (set the optional) records one, and ManifestEntryInfo::sha256 reads it back. Requires antd 0.17.0 or later; the SDK sends it only when set and does not check it.
Runnable example: antd-cpp/examples/09-manifests.cpp. Its two uploads pay; the manifest calls do not.
cd antd-cpp
cmake -S . -B build && cmake --build build
ANTD_BASE_URL=http://localhost:8082 ./build/09-manifestscd antd-cpp/examples
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
./build/01_connect
./build/02_data
./build/03_chunks
./build/04_files
./build/06_privateOr use the dev CLI:
ant dev example data -l cpp
ant dev example all -l cpp