Skip to content
Merged
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
2 changes: 1 addition & 1 deletion antd-csharp/Antd.Sdk.Tests/TestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
18 changes: 8 additions & 10 deletions antd-csharp/Antd.Sdk.Tests/UnitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand All @@ -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);
}
Expand Down Expand Up @@ -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",
Expand All @@ -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);
Expand Down
111 changes: 77 additions & 34 deletions antd-csharp/Antd.Sdk/AntdGrpcClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@ public AntdGrpcClient(string target = "http://localhost:50051")
_files = new FileService.FileServiceClient(_channel);
}

/// <summary>
/// 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.
/// </summary>
public static AntdGrpcClient AutoDiscover()
{
var target = DaemonDiscovery.DiscoverGrpcTarget();
Expand All @@ -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<HealthStatus> HealthAsync()
{
Expand All @@ -51,18 +53,14 @@ public async Task<HealthStatus> HealthAsync()
}
catch (RpcException)
{
return new HealthStatus(true, "unknown"); // server responded — it's reachable
return new HealthStatus(true, "unknown");
}
catch
{
return new HealthStatus(false, "unknown");
}
}

/// <summary>
/// Convert a gRPC <see cref="HealthCheckResponse"/> into a typed
/// <see cref="HealthStatus"/>.
/// </summary>
internal static HealthStatus HealthStatusFromResp(HealthCheckResponse resp) =>
new(
resp.Status == "ok",
Expand All @@ -74,61 +72,73 @@ internal static HealthStatus HealthStatusFromResp(HealthCheckResponse resp) =>
resp.PaymentTokenAddress ?? "",
resp.PaymentVaultAddress ?? "");

// ── Data ──
// Data

public async Task<PutResult> DataPutPublicAsync(byte[] data, string? paymentMode = null)
public async Task<DataPutResult> 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<byte[]> DataGetPublicAsync(string address)
public async Task<byte[]> 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<PutResult> DataPutPrivateAsync(byte[] data, string? paymentMode = null)
public async Task<DataPutPublicResult> 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<byte[]> DataGetPrivateAsync(string dataMap)
public async Task<byte[]> 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<UploadCostEstimate> DataCostAsync(byte[] data)
public async Task<UploadCostEstimate> 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);
}
catch (RpcException ex) { throw Wrap(ex); }
}

// ── Chunks ──
// Chunks

public async Task<PutResult> ChunkPutAsync(byte[] data)
{
Expand Down Expand Up @@ -156,35 +166,67 @@ public Task<PrepareChunkResult> PrepareChunkUploadAsync(byte[] data)
public Task<string> FinalizeChunkUploadAsync(string uploadId, IDictionary<string, string> txHashes)
=> throw new NotSupportedException("FinalizeChunkUpload is not yet supported via gRPC");

// ── Files ──
// Files

public async Task<FileUploadResult> FileUploadPublicAsync(string path, string? paymentMode = null)
public async Task<FilePutResult> 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<UploadCostEstimate> FileCostAsync(string path, bool isPublic = true)
public async Task<FilePutPublicResult> 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<UploadCostEstimate> 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,
Expand All @@ -193,7 +235,7 @@ public async Task<UploadCostEstimate> 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<WalletAddress> WalletAddressAsync()
=> throw new NotSupportedException("WalletAddress is not yet supported via gRPC");
Expand All @@ -204,7 +246,8 @@ public Task<WalletBalance> WalletBalanceAsync()
public Task<bool> 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<PrepareUploadResult> PrepareUploadAsync(string path, string? visibility = null)
=> throw new NotSupportedException("PrepareUpload is not yet supported via gRPC");
Expand Down
Loading