diff --git a/antd-csharp/Antd.Sdk.Tests/TestRunner.cs b/antd-csharp/Antd.Sdk.Tests/TestRunner.cs
index b19aedc..8ea1f03 100644
--- a/antd-csharp/Antd.Sdk.Tests/TestRunner.cs
+++ b/antd-csharp/Antd.Sdk.Tests/TestRunner.cs
@@ -122,7 +122,7 @@ private async Task TestHealth()
var testData = System.Text.Encoding.UTF8.GetBytes("hello from C# SDK!");
var result = await _client.DataPutPublicAsync(testData);
dataAddr = result.Address;
- Pass("Data put public", $"addr={result.Address[..16]}... cost={result.Cost}");
+ Pass("Data put public", $"addr={result.Address[..16]}... chunks={result.ChunksStored} mode={result.PaymentModeUsed}");
}
catch (Exception ex)
{
diff --git a/antd-csharp/Antd.Sdk.Tests/UnitTests.cs b/antd-csharp/Antd.Sdk.Tests/UnitTests.cs
index e7991ee..929c7eb 100644
--- a/antd-csharp/Antd.Sdk.Tests/UnitTests.cs
+++ b/antd-csharp/Antd.Sdk.Tests/UnitTests.cs
@@ -230,7 +230,6 @@ public async Task DataPutPublicAsync_ReturnsCostAndAddress()
var result = await _client.DataPutPublicAsync(Encoding.UTF8.GetBytes("hello"));
- Assert.Equal("42", result.Cost);
Assert.Equal("abc123def456", result.Address);
}
@@ -254,30 +253,29 @@ public async Task DataGetPublicAsync_ReturnsDecodedBytes()
[Fact]
public async Task DataPutPrivateAsync_ReturnsCostAndDataMap()
{
- _server.RouteOk("POST", "/v1/data/private", new
+ _server.RouteOk("POST", "/v1/data", new
{
cost = "99",
data_map = "map_abc123"
});
_server.Start();
- var result = await _client.DataPutPrivateAsync(Encoding.UTF8.GetBytes("secret"));
+ var result = await _client.DataPutAsync(Encoding.UTF8.GetBytes("secret"));
- Assert.Equal("99", result.Cost);
- Assert.Equal("map_abc123", result.Address);
+ Assert.Equal("map_abc123", result.DataMap);
}
[Fact]
public async Task DataGetPrivateAsync_ReturnsDecodedBytes()
{
var original = Encoding.UTF8.GetBytes("private data content");
- _server.RouteOk("GET", "/v1/data/private", new
+ _server.RouteOk("POST", "/v1/data/get", new
{
data = Convert.ToBase64String(original)
});
_server.Start();
- var result = await _client.DataGetPrivateAsync("some_data_map");
+ var result = await _client.DataGetAsync("some_data_map");
Assert.Equal(original, result);
}
@@ -461,9 +459,9 @@ public async Task ErrorMapping_503_ThrowsServiceUnavailableException()
// ── Files ──
[Fact]
- public async Task FileUploadPublicAsync_ReturnsFileUploadResult()
+ public async Task FileUploadPublicAsync_ReturnsFilePutPublicResult()
{
- _server.RouteOk("POST", "/v1/files/upload/public", new
+ _server.RouteOk("POST", "/v1/files/public", new
{
address = "file_addr_001",
storage_cost_atto = "1000",
@@ -473,7 +471,7 @@ public async Task FileUploadPublicAsync_ReturnsFileUploadResult()
});
_server.Start();
- var result = await _client.FileUploadPublicAsync("/tmp/test.txt");
+ var result = await _client.FilePutPublicAsync("/tmp/test.txt");
Assert.Equal("file_addr_001", result.Address);
Assert.Equal("1000", result.StorageCostAtto);
diff --git a/antd-csharp/Antd.Sdk/AntdGrpcClient.cs b/antd-csharp/Antd.Sdk/AntdGrpcClient.cs
index 98cd6d5..aed07b5 100644
--- a/antd-csharp/Antd.Sdk/AntdGrpcClient.cs
+++ b/antd-csharp/Antd.Sdk/AntdGrpcClient.cs
@@ -22,10 +22,6 @@ public AntdGrpcClient(string target = "http://localhost:50051")
_files = new FileService.FileServiceClient(_channel);
}
- ///
- /// Creates an AntdGrpcClient by reading the daemon.port file written by antd.
- /// Falls back to the default target if the port file is not found.
- ///
public static AntdGrpcClient AutoDiscover()
{
var target = DaemonDiscovery.DiscoverGrpcTarget();
@@ -34,9 +30,15 @@ public static AntdGrpcClient AutoDiscover()
public void Dispose() => _channel.Dispose();
+ public ValueTask DisposeAsync()
+ {
+ _channel.Dispose();
+ return ValueTask.CompletedTask;
+ }
+
private static AntdException Wrap(RpcException ex) => ExceptionMapping.FromGrpcStatus(ex);
- // ── Health ──
+ // Health
public async Task HealthAsync()
{
@@ -51,7 +53,7 @@ public async Task HealthAsync()
}
catch (RpcException)
{
- return new HealthStatus(true, "unknown"); // server responded — it's reachable
+ return new HealthStatus(true, "unknown");
}
catch
{
@@ -59,10 +61,6 @@ public async Task HealthAsync()
}
}
- ///
- /// Convert a gRPC into a typed
- /// .
- ///
internal static HealthStatus HealthStatusFromResp(HealthCheckResponse resp) =>
new(
resp.Status == "ok",
@@ -74,53 +72,65 @@ internal static HealthStatus HealthStatusFromResp(HealthCheckResponse resp) =>
resp.PaymentTokenAddress ?? "",
resp.PaymentVaultAddress ?? "");
- // ── Data ──
+ // Data
- public async Task DataPutPublicAsync(byte[] data, string? paymentMode = null)
+ public async Task DataPutAsync(byte[] data, PaymentMode paymentMode = PaymentMode.Auto)
{
try
{
- var resp = await _data.PutPublicAsync(new PutPublicDataRequest { Data = ByteString.CopyFrom(data) });
- return new PutResult(resp.Cost.AttoTokens, resp.Address);
+ var resp = await _data.PutAsync(new PutDataRequest
+ {
+ Data = ByteString.CopyFrom(data),
+ PaymentMode = paymentMode.ToWire(),
+ });
+ return new DataPutResult(resp.DataMap);
}
catch (RpcException ex) { throw Wrap(ex); }
}
- public async Task DataGetPublicAsync(string address)
+ public async Task DataGetAsync(string dataMap)
{
try
{
- var resp = await _data.GetPublicAsync(new GetPublicDataRequest { Address = address });
+ var resp = await _data.GetAsync(new GetDataRequest { DataMap = dataMap });
return resp.Data.ToByteArray();
}
catch (RpcException ex) { throw Wrap(ex); }
}
- public async Task DataPutPrivateAsync(byte[] data, string? paymentMode = null)
+ public async Task DataPutPublicAsync(byte[] data, PaymentMode paymentMode = PaymentMode.Auto)
{
try
{
- var resp = await _data.PutPrivateAsync(new PutPrivateDataRequest { Data = ByteString.CopyFrom(data) });
- return new PutResult(resp.Cost.AttoTokens, resp.DataMap);
+ var resp = await _data.PutPublicAsync(new PutPublicDataRequest
+ {
+ Data = ByteString.CopyFrom(data),
+ PaymentMode = paymentMode.ToWire(),
+ });
+ return new DataPutPublicResult(resp.Address);
}
catch (RpcException ex) { throw Wrap(ex); }
}
- public async Task DataGetPrivateAsync(string dataMap)
+ public async Task DataGetPublicAsync(string address)
{
try
{
- var resp = await _data.GetPrivateAsync(new GetPrivateDataRequest { DataMap = dataMap });
+ var resp = await _data.GetPublicAsync(new GetPublicDataRequest { Address = address });
return resp.Data.ToByteArray();
}
catch (RpcException ex) { throw Wrap(ex); }
}
- public async Task DataCostAsync(byte[] data)
+ public async Task DataCostAsync(byte[] data, PaymentMode paymentMode = PaymentMode.Auto)
{
try
{
- var resp = await _data.GetCostAsync(new DataCostRequest { Data = ByteString.CopyFrom(data) });
+ var resp = await _data.CostAsync(new DataCostRequest
+ {
+ Data = ByteString.CopyFrom(data),
+ PaymentMode = paymentMode.ToWire(),
+ });
return new UploadCostEstimate(
resp.AttoTokens, resp.FileSize, resp.ChunkCount,
resp.EstimatedGasCostWei, resp.PaymentMode);
@@ -128,7 +138,7 @@ public async Task DataCostAsync(byte[] data)
catch (RpcException ex) { throw Wrap(ex); }
}
- // ── Chunks ──
+ // Chunks
public async Task ChunkPutAsync(byte[] data)
{
@@ -156,35 +166,67 @@ public Task PrepareChunkUploadAsync(byte[] data)
public Task FinalizeChunkUploadAsync(string uploadId, IDictionary txHashes)
=> throw new NotSupportedException("FinalizeChunkUpload is not yet supported via gRPC");
- // ── Files ──
+ // Files
- public async Task FileUploadPublicAsync(string path, string? paymentMode = null)
+ public async Task FilePutAsync(string path, PaymentMode paymentMode = PaymentMode.Auto)
{
try
{
- var resp = await _files.UploadPublicAsync(new UploadFileRequest { Path = path });
- return new FileUploadResult(resp.Address, resp.StorageCostAtto, resp.GasCostWei, resp.ChunksStored, resp.PaymentModeUsed);
+ var resp = await _files.PutAsync(new PutFileRequest
+ {
+ Path = path,
+ PaymentMode = paymentMode.ToWire(),
+ });
+ return new FilePutResult(
+ resp.DataMap, resp.StorageCostAtto, resp.GasCostWei,
+ resp.ChunksStored, resp.PaymentModeUsed);
}
catch (RpcException ex) { throw Wrap(ex); }
}
- public async Task FileDownloadPublicAsync(string address, string destPath)
+ public async Task FileGetAsync(string dataMap, string destPath)
{
try
{
- await _files.DownloadPublicAsync(new DownloadPublicRequest { Address = address, DestPath = destPath });
+ await _files.GetAsync(new GetFileRequest { DataMap = dataMap, DestPath = destPath });
}
catch (RpcException ex) { throw Wrap(ex); }
}
- public async Task FileCostAsync(string path, bool isPublic = true)
+ public async Task FilePutPublicAsync(string path, PaymentMode paymentMode = PaymentMode.Auto)
{
try
{
- var resp = await _files.GetFileCostAsync(new Antd.V1.FileCostRequest
+ var resp = await _files.PutPublicAsync(new PutFileRequest
+ {
+ Path = path,
+ PaymentMode = paymentMode.ToWire(),
+ });
+ return new FilePutPublicResult(
+ resp.Address, resp.StorageCostAtto, resp.GasCostWei,
+ resp.ChunksStored, resp.PaymentModeUsed);
+ }
+ catch (RpcException ex) { throw Wrap(ex); }
+ }
+
+ public async Task FileGetPublicAsync(string address, string destPath)
+ {
+ try
+ {
+ await _files.GetPublicAsync(new GetFilePublicRequest { Address = address, DestPath = destPath });
+ }
+ catch (RpcException ex) { throw Wrap(ex); }
+ }
+
+ public async Task FileCostAsync(string path, bool isPublic = true, PaymentMode paymentMode = PaymentMode.Auto)
+ {
+ try
+ {
+ var resp = await _files.CostAsync(new Antd.V1.FileCostRequest
{
Path = path,
IsPublic = isPublic,
+ PaymentMode = paymentMode.ToWire(),
});
return new UploadCostEstimate(
resp.AttoTokens, resp.FileSize, resp.ChunkCount,
@@ -193,7 +235,7 @@ public async Task FileCostAsync(string path, bool isPublic =
catch (RpcException ex) { throw Wrap(ex); }
}
- // ── Wallet (not yet available via gRPC) ──
+ // Wallet (not yet available via gRPC)
public Task WalletAddressAsync()
=> throw new NotSupportedException("WalletAddress is not yet supported via gRPC");
@@ -204,7 +246,8 @@ public Task WalletBalanceAsync()
public Task WalletApproveAsync()
=> throw new NotSupportedException("WalletApprove is not yet supported via gRPC");
- // ── External Signer (not yet available via gRPC) ──
+
+ // External Signer (Two-Phase Upload) — not yet available via gRPC
public Task PrepareUploadAsync(string path, string? visibility = null)
=> throw new NotSupportedException("PrepareUpload is not yet supported via gRPC");
diff --git a/antd-csharp/Antd.Sdk/AntdRestClient.cs b/antd-csharp/Antd.Sdk/AntdRestClient.cs
index 4d136c0..71ba502 100644
--- a/antd-csharp/Antd.Sdk/AntdRestClient.cs
+++ b/antd-csharp/Antd.Sdk/AntdRestClient.cs
@@ -21,10 +21,6 @@ public AntdRestClient(string baseUrl = "http://localhost:8082", TimeSpan? timeou
_http = new HttpClient { BaseAddress = new Uri(_baseUrl), Timeout = timeout ?? TimeSpan.FromSeconds(300) };
}
- ///
- /// Creates an AntdRestClient by reading the daemon.port file written by antd.
- /// Falls back to the default base URL if the port file is not found.
- ///
public static AntdRestClient AutoDiscover(TimeSpan? timeout = null)
{
var url = DaemonDiscovery.DiscoverDaemonUrl();
@@ -33,7 +29,13 @@ public static AntdRestClient AutoDiscover(TimeSpan? timeout = null)
public void Dispose() => _http.Dispose();
- // ── Helpers ──
+ public ValueTask DisposeAsync()
+ {
+ _http.Dispose();
+ return ValueTask.CompletedTask;
+ }
+
+ // Helpers
private async Task GetJsonAsync(string path)
{
@@ -55,15 +57,6 @@ private async Task PostJsonNoResultAsync(string path, object body)
await EnsureSuccessAsync(resp);
}
- private async Task HeadExistsAsync(string path)
- {
- var req = new HttpRequestMessage(HttpMethod.Head, path);
- var resp = await _http.SendAsync(req);
- if (resp.StatusCode == HttpStatusCode.NotFound) return false;
- await EnsureSuccessAsync(resp);
- return true;
- }
-
private static async Task EnsureSuccessAsync(HttpResponseMessage resp)
{
if (resp.IsSuccessStatusCode) return;
@@ -71,7 +64,7 @@ private static async Task EnsureSuccessAsync(HttpResponseMessage resp)
throw ExceptionMapping.FromHttpStatus(resp.StatusCode, body);
}
- // ── Health ──
+ // Health
public async Task HealthAsync()
{
@@ -89,11 +82,6 @@ public async Task HealthAsync()
}
}
- ///
- /// Convert a parsed into a typed
- /// . Diagnostic fields default to empty / 0
- /// when talking to a pre-0.4.0 daemon that omits them.
- ///
internal static HealthStatus HealthStatusFromDto(HealthResponseDto? dto)
{
if (dto is null) return new HealthStatus(false, "unknown");
@@ -108,49 +96,46 @@ internal static HealthStatus HealthStatusFromDto(HealthResponseDto? dto)
dto.PaymentVaultAddress ?? "");
}
- // ── Data ──
+ // Data
- public async Task DataPutPublicAsync(byte[] data, string? paymentMode = null)
+ public async Task DataPutAsync(byte[] data, PaymentMode paymentMode = PaymentMode.Auto)
{
- object body = paymentMode != null
- ? new { data = Convert.ToBase64String(data), payment_mode = paymentMode }
- : new { data = Convert.ToBase64String(data) };
- var resp = await PostJsonAsync("/v1/data/public", body);
- return new PutResult(resp.Cost, resp.Address);
+ var body = new { data = Convert.ToBase64String(data), payment_mode = paymentMode.ToWire() };
+ var resp = await PostJsonAsync("/v1/data", body);
+ return new DataPutResult(resp.DataMap, resp.ChunksStored, resp.PaymentModeUsed);
}
- public async Task DataGetPublicAsync(string address)
+ public async Task DataGetAsync(string dataMap)
{
- var resp = await GetJsonAsync($"/v1/data/public/{address}");
+ var resp = await PostJsonAsync("/v1/data/get", new { data_map = dataMap });
return Convert.FromBase64String(resp.Data);
}
- public async Task DataPutPrivateAsync(byte[] data, string? paymentMode = null)
+ public async Task DataPutPublicAsync(byte[] data, PaymentMode paymentMode = PaymentMode.Auto)
{
- object body = paymentMode != null
- ? new { data = Convert.ToBase64String(data), payment_mode = paymentMode }
- : new { data = Convert.ToBase64String(data) };
- var resp = await PostJsonAsync("/v1/data/private", body);
- return new PutResult(resp.Cost, resp.DataMap);
+ var body = new { data = Convert.ToBase64String(data), payment_mode = paymentMode.ToWire() };
+ var resp = await PostJsonAsync("/v1/data/public", body);
+ return new DataPutPublicResult(resp.Address, resp.ChunksStored, resp.PaymentModeUsed);
}
- public async Task DataGetPrivateAsync(string dataMap)
+ public async Task DataGetPublicAsync(string address)
{
- var resp = await GetJsonAsync($"/v1/data/private?data_map={Uri.EscapeDataString(dataMap)}");
+ var resp = await GetJsonAsync($"/v1/data/public/{address}");
return Convert.FromBase64String(resp.Data);
}
- public async Task DataCostAsync(byte[] data)
+ public async Task DataCostAsync(byte[] data, PaymentMode paymentMode = PaymentMode.Auto)
{
- var resp = await PostJsonAsync("/v1/data/cost", new { data = Convert.ToBase64String(data) });
+ var body = new { data = Convert.ToBase64String(data), payment_mode = paymentMode.ToWire() };
+ var resp = await PostJsonAsync("/v1/data/cost", body);
return new UploadCostEstimate(resp.Cost, resp.FileSize, resp.ChunkCount, resp.EstimatedGasCostWei, resp.PaymentMode);
}
- // ── Chunks ──
+ // Chunks
public async Task ChunkPutAsync(byte[] data)
{
- var resp = await PostJsonAsync("/v1/chunks", new { data = Convert.ToBase64String(data) });
+ var resp = await PostJsonAsync("/v1/chunks", new { data = Convert.ToBase64String(data) });
return new PutResult(resp.Cost, resp.Address);
}
@@ -160,18 +145,6 @@ public async Task ChunkGetAsync(string address)
return Convert.FromBase64String(resp.Data);
}
- ///
- /// Prepares a single chunk for external-signer publish via
- /// POST /v1/chunks/prepare.
- ///
- /// The daemon quotes the close group for the supplied bytes and returns
- /// either = true with
- /// set (no payment needed), or a
- /// wave-batch payment intent. Mirrors but
- /// routes payment through an external signer instead of the daemon wallet.
- ///
- /// Requires antd >= 0.7.0.
- ///
public async Task PrepareChunkUploadAsync(byte[] data)
{
var resp = await PostJsonAsync("/v1/chunks/prepare",
@@ -191,13 +164,6 @@ public async Task PrepareChunkUploadAsync(byte[] data)
resp.RpcUrl ?? "");
}
- ///
- /// Submits a prepared chunk to the network after the external signer has
- /// paid via POST /v1/chunks/finalize. Returns the hex address of
- /// the stored chunk (matches ).
- ///
- /// Requires antd >= 0.7.0.
- ///
public async Task FinalizeChunkUploadAsync(string uploadId, IDictionary txHashes)
{
var resp = await PostJsonAsync("/v1/chunks/finalize",
@@ -205,30 +171,40 @@ public async Task FinalizeChunkUploadAsync(string uploadId, IDictionary<
return resp.Address ?? "";
}
- // ── Files ──
+ // Files
- public async Task FileUploadPublicAsync(string path, string? paymentMode = null)
+ public async Task FilePutAsync(string path, PaymentMode paymentMode = PaymentMode.Auto)
{
- object body = paymentMode != null
- ? new { path, payment_mode = paymentMode }
- : (object)new { path };
- var resp = await PostJsonAsync("/v1/files/upload/public", body);
- return new FileUploadResult(resp.Address, resp.StorageCostAtto, resp.GasCostWei, resp.ChunksStored, resp.PaymentModeUsed);
+ var body = new { path, payment_mode = paymentMode.ToWire() };
+ var resp = await PostJsonAsync("/v1/files", body);
+ return new FilePutResult(resp.DataMap, resp.StorageCostAtto, resp.GasCostWei, resp.ChunksStored, resp.PaymentModeUsed);
+ }
+
+ public async Task FileGetAsync(string dataMap, string destPath)
+ {
+ await PostJsonNoResultAsync("/v1/files/get", new { data_map = dataMap, dest_path = destPath });
+ }
+
+ public async Task FilePutPublicAsync(string path, PaymentMode paymentMode = PaymentMode.Auto)
+ {
+ var body = new { path, payment_mode = paymentMode.ToWire() };
+ var resp = await PostJsonAsync("/v1/files/public", body);
+ return new FilePutPublicResult(resp.Address, resp.StorageCostAtto, resp.GasCostWei, resp.ChunksStored, resp.PaymentModeUsed);
}
- public async Task FileDownloadPublicAsync(string address, string destPath)
+ public async Task FileGetPublicAsync(string address, string destPath)
{
- await PostJsonNoResultAsync("/v1/files/download/public", new { address, dest_path = destPath });
+ await PostJsonNoResultAsync("/v1/files/public/get", new { address, dest_path = destPath });
}
- public async Task FileCostAsync(string path, bool isPublic = true)
+ public async Task FileCostAsync(string path, bool isPublic = true, PaymentMode paymentMode = PaymentMode.Auto)
{
- var body = new { path, is_public = isPublic };
+ var body = new { path, is_public = isPublic, payment_mode = paymentMode.ToWire() };
var resp = await PostJsonAsync("/v1/files/cost", body);
return new UploadCostEstimate(resp.Cost, resp.FileSize, resp.ChunkCount, resp.EstimatedGasCostWei, resp.PaymentMode);
}
- // ── Wallet ──
+ // Wallet
public async Task WalletAddressAsync()
{
@@ -242,28 +218,14 @@ public async Task WalletBalanceAsync()
return new WalletBalance(resp.Balance, resp.GasBalance);
}
- ///
- /// Approves the wallet to spend tokens on payment contracts (one-time operation).
- ///
public async Task WalletApproveAsync()
{
var resp = await PostJsonAsync("/v1/wallet/approve", new { });
return resp.Approved;
}
- // ── External Signer (Two-Phase Upload) ──
-
- ///
- /// Prepares a file upload for external signing.
- ///
- /// Path to the file to upload.
- ///
- /// Pass "public" to bundle the DataMap chunk into the same
- /// external-signer payment batch — the resulting
- /// is the shareable
- /// retrieval handle. "private" or null preserves the
- /// pre-public daemon wire shape (private-only).
- ///
+ // External Signer (Two-Phase Upload)
+
public async Task PrepareUploadAsync(string path, string? visibility = null)
{
object body = visibility != null
@@ -273,27 +235,9 @@ public async Task PrepareUploadAsync(string path, string? v
return MapPrepareUpload(resp);
}
- ///
- /// Convenience wrapper: prepares a public file upload for external
- /// signing. Equivalent to with
- /// visibility="public".
- ///
- /// Requires antd >= 0.6.1.
- ///
public Task PrepareUploadPublicAsync(string path)
=> PrepareUploadAsync(path, visibility: "public");
- ///
- /// Prepares a data upload for external signing.
- /// Takes raw bytes, base64-encodes them, and POSTs to /v1/data/prepare.
- ///
- /// Raw bytes to upload.
- ///
- /// Pass "public" to request the public flow; note the daemon
- /// currently returns 501 for visibility="public" on the data path
- /// until upstream data_prepare_upload_with_visibility lands. Use
- /// with a file path until then.
- ///
public async Task PrepareDataUploadAsync(byte[] data, string? visibility = null)
{
object body = visibility != null
@@ -303,9 +247,6 @@ public async Task PrepareDataUploadAsync(byte[] data, strin
return MapPrepareUpload(resp);
}
- ///
- /// Finalizes an upload after an external signer has submitted payment transactions.
- ///
public async Task FinalizeUploadAsync(string uploadId, Dictionary txHashes)
{
var resp = await PostJsonAsync("/v1/upload/finalize", new { upload_id = uploadId, tx_hashes = txHashes });
@@ -316,9 +257,6 @@ public async Task FinalizeUploadAsync(string uploadId, Dic
resp.DataMapAddress ?? "");
}
- ///
- /// Finalizes a merkle batch upload by selecting a winner pool.
- ///
public async Task FinalizeMerkleUploadAsync(string uploadId, string winnerPoolHash)
{
var resp = await PostJsonAsync("/v1/upload/finalize",
@@ -345,7 +283,7 @@ private static PrepareUploadResult MapPrepareUpload(PrepareUploadDto resp)
MerklePaymentTimestamp: resp.MerklePaymentTimestamp);
}
- // ── Internal DTOs for JSON deserialization ──
+ // Internal DTOs for JSON deserialization
internal sealed record HealthResponseDto(
[property: JsonPropertyName("status")] string Status,
@@ -358,19 +296,32 @@ internal sealed record HealthResponseDto(
[property: JsonPropertyName("payment_vault_address")] string? PaymentVaultAddress = null);
private sealed record DataPutPublicDto(
- [property: JsonPropertyName("cost")] string Cost,
- [property: JsonPropertyName("address")] string Address);
+ [property: JsonPropertyName("address")] string Address,
+ [property: JsonPropertyName("chunks_stored")] ulong ChunksStored = 0,
+ [property: JsonPropertyName("payment_mode_used")] string PaymentModeUsed = "");
+
+ private sealed record DataPutDto(
+ [property: JsonPropertyName("data_map")] string DataMap,
+ [property: JsonPropertyName("chunks_stored")] ulong ChunksStored = 0,
+ [property: JsonPropertyName("payment_mode_used")] string PaymentModeUsed = "");
+
+ private sealed record FilePutDto(
+ [property: JsonPropertyName("data_map")] string DataMap,
+ [property: JsonPropertyName("storage_cost_atto")] string StorageCostAtto,
+ [property: JsonPropertyName("gas_cost_wei")] string GasCostWei,
+ [property: JsonPropertyName("chunks_stored")] ulong ChunksStored,
+ [property: JsonPropertyName("payment_mode_used")] string PaymentModeUsed);
- private sealed record FileUploadPublicDto(
+ private sealed record FilePutPublicDto(
[property: JsonPropertyName("address")] string Address,
[property: JsonPropertyName("storage_cost_atto")] string StorageCostAtto,
[property: JsonPropertyName("gas_cost_wei")] string GasCostWei,
[property: JsonPropertyName("chunks_stored")] ulong ChunksStored,
[property: JsonPropertyName("payment_mode_used")] string PaymentModeUsed);
- private sealed record DataPutPrivateDto(
+ private sealed record ChunkPutDto(
[property: JsonPropertyName("cost")] string Cost,
- [property: JsonPropertyName("data_map")] string DataMap);
+ [property: JsonPropertyName("address")] string Address);
private sealed record DataGetDto(
[property: JsonPropertyName("data")] string Data);
diff --git a/antd-csharp/Antd.Sdk/IAntdClient.cs b/antd-csharp/Antd.Sdk/IAntdClient.cs
index 65a4c37..7798467 100644
--- a/antd-csharp/Antd.Sdk/IAntdClient.cs
+++ b/antd-csharp/Antd.Sdk/IAntdClient.cs
@@ -1,16 +1,20 @@
namespace Antd.Sdk;
-public interface IAntdClient : IDisposable
+///
+/// Common interface for both REST () and gRPC
+/// () antd clients.
+///
+public interface IAntdClient : IDisposable, IAsyncDisposable
{
// Health
Task HealthAsync();
// Data
- Task DataPutPublicAsync(byte[] data, string? paymentMode = null);
+ Task DataPutPublicAsync(byte[] data, PaymentMode paymentMode = PaymentMode.Auto);
Task DataGetPublicAsync(string address);
- Task DataPutPrivateAsync(byte[] data, string? paymentMode = null);
- Task DataGetPrivateAsync(string dataMap);
- Task DataCostAsync(byte[] data);
+ Task DataPutAsync(byte[] data, PaymentMode paymentMode = PaymentMode.Auto);
+ Task DataGetAsync(string dataMap);
+ Task DataCostAsync(byte[] data, PaymentMode paymentMode = PaymentMode.Auto);
// Chunks
Task ChunkPutAsync(byte[] data);
@@ -19,9 +23,11 @@ public interface IAntdClient : IDisposable
Task FinalizeChunkUploadAsync(string uploadId, IDictionary txHashes);
// Files
- Task FileUploadPublicAsync(string path, string? paymentMode = null);
- Task FileDownloadPublicAsync(string address, string destPath);
- Task FileCostAsync(string path, bool isPublic = true);
+ Task FilePutAsync(string path, PaymentMode paymentMode = PaymentMode.Auto);
+ Task FileGetAsync(string dataMap, string destPath);
+ Task FilePutPublicAsync(string path, PaymentMode paymentMode = PaymentMode.Auto);
+ Task FileGetPublicAsync(string address, string destPath);
+ Task FileCostAsync(string path, bool isPublic = true, PaymentMode paymentMode = PaymentMode.Auto);
// Wallet
Task WalletAddressAsync();
diff --git a/antd-csharp/Antd.Sdk/Models.cs b/antd-csharp/Antd.Sdk/Models.cs
index ac17897..1539955 100644
--- a/antd-csharp/Antd.Sdk/Models.cs
+++ b/antd-csharp/Antd.Sdk/Models.cs
@@ -1,5 +1,31 @@
namespace Antd.Sdk;
+///
+/// Payment-batching strategy for uploads.
+///
+/// - — server picks (merkle for 64+ chunks, single otherwise).
+/// - — force merkle-batch (saves gas, min 2 chunks).
+/// - — force per-chunk payments.
+///
+public enum PaymentMode
+{
+ Auto,
+ Merkle,
+ Single,
+}
+
+public static class PaymentModeExtensions
+{
+ /// Serialize a to the wire string the daemon expects.
+ public static string ToWire(this PaymentMode mode) => mode switch
+ {
+ PaymentMode.Auto => "auto",
+ PaymentMode.Merkle => "merkle",
+ PaymentMode.Single => "single",
+ _ => "auto",
+ };
+}
+
///
/// Health check result from the antd daemon.
///
@@ -19,11 +45,44 @@ public sealed record HealthStatus(
string PaymentTokenAddress = "",
string PaymentVaultAddress = "");
-/// Result of a put/create operation that stores data on the network.
+/// Result of a single-chunk put (used by ChunkPutAsync).
public sealed record PutResult(string Cost, string Address);
-/// Result of a public file or directory upload.
-public sealed record FileUploadResult(
+///
+/// Result of a private data put. The DataMap is returned to the caller;
+/// it is NOT stored on-network. REST populates ChunksStored and PaymentModeUsed;
+/// the gRPC transport currently leaves them empty.
+///
+public sealed record DataPutResult(
+ string DataMap,
+ ulong ChunksStored = 0,
+ string PaymentModeUsed = "");
+
+///
+/// Result of a public data put. The DataMap is stored on-network as an extra
+/// chunk; Address is the shareable retrieval handle.
+///
+public sealed record DataPutPublicResult(
+ string Address,
+ ulong ChunksStored = 0,
+ string PaymentModeUsed = "");
+
+///
+/// Result of a private file upload. The DataMap is returned to the caller;
+/// it is NOT stored on-network.
+///
+public sealed record FilePutResult(
+ string DataMap,
+ string StorageCostAtto,
+ string GasCostWei,
+ ulong ChunksStored,
+ string PaymentModeUsed);
+
+///
+/// Result of a public file upload. The DataMap is stored on-network as an
+/// extra chunk; Address is the shareable retrieval handle.
+///
+public sealed record FilePutPublicResult(
string Address,
string StorageCostAtto,
string GasCostWei,
@@ -58,44 +117,21 @@ public sealed record PrepareUploadResult(
List? PoolCommitments = null,
long? MerklePaymentTimestamp = null);
-///
-/// Result of finalizing an externally-signed wave-batch upload.
-///
-/// is the hex-encoded serialized DataMap and is always
-/// populated. is set only when prepare was called
-/// with visibility="public" — the DataMap chunk was bundled into the
-/// same external-signer payment batch and stored on-network, and the address
-/// is the shareable retrieval handle. Pre-0.6.1 daemons that don't emit this
-/// field leave it as "".
-///
+/// Result of finalizing an externally-signed wave-batch upload.
public sealed record FinalizeUploadResult(
string Address,
long ChunksStored,
string DataMap = "",
string DataMapAddress = "");
-///
-/// Result of finalizing a merkle batch upload.
-///
-/// See for the meaning of
-/// and .
-///
+/// Result of finalizing a merkle batch upload.
public sealed record FinalizeMerkleUploadResult(
string Address,
long ChunksStored,
string DataMap = "",
string DataMapAddress = "");
-///
-/// Result of preparing a single-chunk external-signer publish via
-/// POST /v1/chunks/prepare.
-///
-/// When is true the chunk is already on
-/// the network — only and
-/// are meaningful, and no finalize call is needed. Otherwise the wave-batch
-/// payment fields describe what the external signer must submit before
-/// calling FinalizeChunkUploadAsync.
-///
+/// Result of preparing a single-chunk external-signer publish.
public sealed record PrepareChunkResult(
string Address,
bool AlreadyStored = false,
@@ -107,12 +143,7 @@ public sealed record PrepareChunkResult(
string PaymentTokenAddress = "",
string RpcUrl = "");
-///
-/// Pre-upload cost breakdown returned by DataCostAsync and FileCostAsync.
-///
-/// The server samples up to 5 chunk addresses and extrapolates the storage cost.
-/// Gas is an advisory heuristic, not a live gas-oracle query.
-///
+/// Pre-upload cost breakdown returned by DataCostAsync and FileCostAsync.
public sealed record UploadCostEstimate(
string Cost,
ulong FileSize,
diff --git a/antd-csharp/Examples/Program.cs b/antd-csharp/Examples/Program.cs
index d0a6574..21fee4d 100644
--- a/antd-csharp/Examples/Program.cs
+++ b/antd-csharp/Examples/Program.cs
@@ -65,7 +65,7 @@ static async Task Example02_Data()
// Store public data
var result = await client.DataPutPublicAsync(payload);
Console.WriteLine($"Stored at address: {result.Address}");
- Console.WriteLine($"Actual cost: {result.Cost} atto tokens");
+ Console.WriteLine($"Chunks: {result.ChunksStored}, mode: {result.PaymentModeUsed}");
// Retrieve
var data = await client.DataGetPublicAsync(result.Address);
@@ -116,14 +116,14 @@ static async Task Example04_Files()
Console.WriteLine($"File upload cost estimate: {cost} atto tokens");
// Upload
- var result = await client.FileUploadPublicAsync(srcPath);
+ var result = await client.FilePutPublicAsync(srcPath);
Console.WriteLine($"File uploaded to: {result.Address}");
Console.WriteLine($"Storage cost: {result.StorageCostAtto} atto, gas: {result.GasCostWei} wei");
Console.WriteLine($"Chunks stored: {result.ChunksStored}, payment mode: {result.PaymentModeUsed}");
// Download to new location
var destPath = srcPath + ".downloaded";
- await client.FileDownloadPublicAsync(result.Address, destPath);
+ await client.FileGetPublicAsync(result.Address, destPath);
Console.WriteLine($"Downloaded to: {destPath}");
var content = await File.ReadAllTextAsync(destPath);
@@ -147,13 +147,13 @@ static async Task Example06_PrivateData()
var secretMessage = Encoding.UTF8.GetBytes("This message is encrypted on the network");
// Store private data
- var result = await client.DataPutPrivateAsync(secretMessage);
- var dataMap = result.Address; // for private data, address holds the data map
+ var result = await client.DataPutAsync(secretMessage);
+ var dataMap = result.DataMap;
Console.WriteLine($"Data map: {dataMap}");
- Console.WriteLine($"Cost: {result.Cost} atto tokens");
+ Console.WriteLine($"Chunks: {result.ChunksStored}, mode: {result.PaymentModeUsed}");
// Retrieve and decrypt
- var retrieved = await client.DataGetPrivateAsync(dataMap);
+ var retrieved = await client.DataGetAsync(dataMap);
Console.WriteLine($"Decrypted: {Encoding.UTF8.GetString(retrieved)}");
if (!retrieved.SequenceEqual(secretMessage))
@@ -203,7 +203,7 @@ static async Task Example07_ExternalSigner()
$"chunks_stored={fileFin.ChunksStored}");
var dstPath = srcPath + ".downloaded";
- await client.FileDownloadPublicAsync(fileFin.DataMapAddress, dstPath);
+ await client.FileGetPublicAsync(fileFin.DataMapAddress, dstPath);
var downloaded = await File.ReadAllBytesAsync(dstPath);
if (!downloaded.SequenceEqual(fileContent))
throw new Exception("file round-trip mismatch");