Ruby SDK for the antd daemon — the gateway to the Autonomi decentralized network.
Add to your Gemfile:
gem "antd"Or install directly:
gem install antdThis gem talks to a running antd daemon; it does not join the network itself. Ruby 3.1+. Tested against antd 0.12.x. The REST client has no runtime dependencies; the gRPC transport (Antd::GrpcClient) needs the grpc gem, which is deliberately not a runtime dependency of this gem — add gem "grpc" yourself to use it.
require "antd"
client = Antd::Client.new
# Check daemon health
health = client.health
puts "OK: #{health.ok}, Network: #{health.network}"
# Store data
result = client.data_put_public("Hello, Autonomi!")
puts "Stored at #{result.address} (chunks: #{result.chunks_stored})"
# Retrieve data
data = client.data_get_public(result.address)
puts "Retrieved: #{data}"The SDK includes an Antd::GrpcClient class that provides the same methods
as the REST Antd::Client, but communicates over gRPC.
Install the gRPC gem (listed as an optional development dependency):
gem install grpc grpc-toolsGenerate the Ruby protobuf/gRPC stubs from the proto definitions:
grpc_tools_ruby_protoc \
-I../../antd/proto \
--ruby_out=lib --grpc_out=lib \
antd/v1/common.proto antd/v1/health.proto antd/v1/data.proto \
antd/v1/chunks.proto antd/v1/files.protoThe generated files are expected under lib/antd/v1/.
require "antd"
require "antd/grpc_client"
client = Antd::GrpcClient.new # defaults to localhost:50051
# Or custom target:
# client = Antd::GrpcClient.new(target: "my-host:50051")
health = client.health
puts "OK: #{health.ok}, Network: #{health.network}"
result = client.data_put_public("Hello via gRPC!")
puts "Stored at #{result.address}"
data = client.data_get_public(result.address)
puts "Retrieved: #{data}"The GrpcClient raises the same Antd::AntdError hierarchy as the REST
client, translating gRPC status codes to the appropriate error subclass
(an ABORTED whose status details — GRPC::BadStatus#details, the text
the daemon sent — start with Partial upload: becomes
Antd::PartialUploadError, with the chunk counts, retryable and
retention_known parsed from that text — see
Partial uploads; any other
ABORTED, including one that only mentions Partial upload: later in its
text, stays a generic Antd::AntdError).
Note: Wallet operations (address, balance, approve) and payment_mode are available via REST only.
The antd daemon must be running. Start it with:
ant dev start# Default: http://localhost:8082, 300 second timeout
client = Antd::Client.new
# Custom URL
client = Antd::Client.new(base_url: "http://custom-host:9090")
# Custom timeout (seconds)
client = Antd::Client.new(timeout: 30)
# Both
client = Antd::Client.new(base_url: "http://custom-host:9090", timeout: 30)| Method | Description |
|---|---|
health |
Check daemon status |
| Method | Description |
|---|---|
data_put_public(data, payment_mode: :auto) |
Store public data — returns DataPutPublicResult (DataMap stored on-network) |
data_get_public(address) |
Retrieve public data by address |
data_put(data, payment_mode: :auto) |
Store encrypted private data — returns DataPutResult (DataMap returned to caller) |
data_get(data_map) |
Retrieve private data using a caller-held DataMap |
data_cost(data, payment_mode: :auto) |
Estimate storage cost — returns UploadCostEstimate with size, chunks, gas, payment mode |
| Method | Description |
|---|---|
chunk_put(data) |
Store a raw chunk |
chunk_get(address) |
Retrieve a chunk |
| Method | Description |
|---|---|
file_put(path, payment_mode: :auto) |
Upload a file privately — returns FilePutResult (DataMap returned to caller) |
file_get(data_map, dest_path) |
Download a private file using a caller-held DataMap |
file_put_public(path, payment_mode: :auto) |
Upload a file publicly — returns FilePutPublicResult (DataMap stored on-network) |
file_get_public(address, dest_path) |
Download a public file by address |
file_cost(path, is_public, payment_mode: :auto) |
Estimate upload cost — returns UploadCostEstimate with size, chunks, gas, payment mode |
All errors inherit from Antd::AntdError and can be caught by type:
begin
data = client.data_get_public(address)
rescue Antd::NotFoundError => e
puts "Data not found on network"
rescue Antd::PaymentError => e
puts "Insufficient funds"
rescue Antd::AntdError => e
puts "Error #{e.status_code}: #{e.message}"
end| Error Type | HTTP Status | When |
|---|---|---|
BadRequestError |
400 | Invalid parameters |
PaymentError |
402 | Insufficient funds |
NotFoundError |
404 | Resource not found |
AlreadyExistsError |
409 | Resource exists |
ForkError |
409 | Version conflict |
TooLargeError |
413 | Payload too large |
InternalError |
500 | Server error |
NetworkError |
502 | Network unreachable |
PartialUploadError |
502 (code: "PARTIAL_UPLOAD") |
Finalize stored some chunks but not all — subclass of NetworkError |
A finalize_upload / finalize_merkle_upload / finalize_chunk_upload call
can fail after the external signer has paid: some chunks store, others miss
quorum after the daemon's own retries. The daemon reports this as HTTP 502
with code: "PARTIAL_UPLOAD" (gRPC ABORTED), and the SDK raises
Antd::PartialUploadError carrying chunks_stored, chunks_failed,
total_chunks, retryable and retention_known. The on-chain payment
persists and the stored chunks stay on the network; what to do next depends
on what the daemon said about the paid attempt:
retryable(antd >= 0.14.0; impliesretention_known) — the daemon kept the paid attempt under the sameupload_id. Call the same finalize method again with the sameupload_idand payment artefacts to store the remainder against the same payment: no re-prepare, no second signature, no double payment. Bound the loop — a persistent failure raises this error on every call — so cap the attempts and treat achunks_failedthat stops shrinking as stuck. The retained attempt expires with the daemon's pending-upload TTL.retention_known && !retryable— the daemon confirmed nothing was retained (a merkle finalize with deliberately unpaid batches). Re-prepare the same content: already-stored chunks are skipped, so the retry pays only for the remainder.!retention_known— retention is unknown: the error did not say, in a form the SDK could read, whether the paid attempt was kept. The daemon may still hold it (it records the resume handle before it returns the error). Stop automatic recovery, keep theupload_idand the original payment artefacts (tx hashes, or the merkle winner pool hash), and reconcile before re-preparing or paying again. Never pay again on this signal alone. Daemons older than 0.14.0 never sendretryable, so their REST partial uploads read as unknown.
Over REST, retention_known is true only when the body's retryable is a
JSON boolean (true or false); absent, null or any other type reads as
unknown. Over gRPC there is no structured body, so the counts and flags are
parsed from the status text,
Partial upload: <stored>/<total> chunks stored, <failed> failed after retries: <reason> (<hint>).
retention_known is true only when the text starts with that counts
pattern, all three counts parse (each within the daemon's u64 range), and the
text ends with one of the daemon's two hints: (paid attempt retained...)
sets retryable, and (stored chunks persist; re-prepare the same content...)
means the daemon confirmed nothing was retained (daemons older than 0.14.0
write only this one). If the counts cannot be read, all three are 0 and
both flags are false. Readable counts with a missing, truncated or
unrecognised hint keep the counts, but both flags stay false: retention is
unknown, so stop and reconcile rather than re-prepare.
PartialUploadError subclasses NetworkError (the 502 mapping), so existing
rescue Antd::NetworkError blocks keep catching it; rescue the subclass first
to handle it specifically.
MAX_ATTEMPTS = 5
last_failed = nil
attempt = 0
begin
attempt += 1
result = client.finalize_upload(prep.upload_id, tx_hashes)
rescue Antd::PartialUploadError => e
# Not resumable. With retention_known, nothing was retained: re-prepare.
# Without it, retention is unknown: stop, keep upload_id + tx_hashes and
# reconcile before re-preparing or paying again.
raise unless e.retryable
stuck = !last_failed.nil? && e.chunks_failed >= last_failed
raise if attempt >= MAX_ATTEMPTS || stuck # bounded: same payment, same upload_id
last_failed = e.chunks_failed
sleep(2 * attempt)
retry
endSee docs/external-signer-flow.md section 6
for the daemon contract and examples/07_external_signer.rb for a
finalize_with_retry helper.
See the examples/ directory:
01_connect.rb— Health check02_data.rb— Public data put/get with cost estimate03_chunks.rb— Chunk put/get04_files.rb— File upload and download06_private_data.rb— Private data put/get07_external_signer.rb— External-signer prepare/pay/finalize with a bounded partial-upload retry