diff --git a/antd-java/src/main/java/com/autonomi/antd/GrpcAntdClient.java b/antd-java/src/main/java/com/autonomi/antd/GrpcAntdClient.java index c238b305..b84e30c4 100644 --- a/antd-java/src/main/java/com/autonomi/antd/GrpcAntdClient.java +++ b/antd-java/src/main/java/com/autonomi/antd/GrpcAntdClient.java @@ -28,6 +28,14 @@ import antd.v1.Chunks.PutChunkRequest; import antd.v1.Chunks.PutChunkResponse; +import antd.v1.WalletServiceGrpc; +import antd.v1.Wallet.GetWalletAddressRequest; +import antd.v1.Wallet.GetWalletAddressResponse; +import antd.v1.Wallet.GetWalletBalanceRequest; +import antd.v1.Wallet.GetWalletBalanceResponse; +import antd.v1.Wallet.WalletApproveRequest; +import antd.v1.Wallet.WalletApproveResponse; + import antd.v1.FileServiceGrpc; import antd.v1.Files.PutFileRequest; import antd.v1.Files.PutFileResponse; @@ -74,6 +82,7 @@ public class GrpcAntdClient implements AutoCloseable { private final DataServiceGrpc.DataServiceBlockingStub dataStub; private final ChunkServiceGrpc.ChunkServiceBlockingStub chunkStub; private final FileServiceGrpc.FileServiceBlockingStub fileStub; + private final WalletServiceGrpc.WalletServiceBlockingStub walletStub; private final UploadServiceGrpc.UploadServiceBlockingStub uploadStub; public static GrpcAntdClient autoDiscover() { @@ -98,6 +107,7 @@ public GrpcAntdClient(ManagedChannel channel) { this.dataStub = DataServiceGrpc.newBlockingStub(channel); this.chunkStub = ChunkServiceGrpc.newBlockingStub(channel); this.fileStub = FileServiceGrpc.newBlockingStub(channel); + this.walletStub = WalletServiceGrpc.newBlockingStub(channel); this.uploadStub = UploadServiceGrpc.newBlockingStub(channel); } @@ -348,6 +358,42 @@ public UploadCostEstimate fileCost(String path, boolean isPublic) { return fileCost(path, isPublic, PaymentMode.AUTO); } + // Wallet — V2-286 parity with REST AntdClient.walletAddress/Balance/Approve. + // A missing daemon wallet emits gRPC FailedPrecondition which mapException + // surfaces as PaymentException (the established FailedPrecondition→Payment + // convention across all SDKs; semantic is a bit off vs REST's 503 but + // matches every other SDK). + + public WalletAddress walletAddress() { + try { + GetWalletAddressResponse resp = walletStub.getAddress( + GetWalletAddressRequest.getDefaultInstance()); + return new WalletAddress(resp.getAddress()); + } catch (StatusRuntimeException e) { + throw mapException(e); + } + } + + public WalletBalance walletBalance() { + try { + GetWalletBalanceResponse resp = walletStub.getBalance( + GetWalletBalanceRequest.getDefaultInstance()); + return new WalletBalance(resp.getBalance(), resp.getGasBalance()); + } catch (StatusRuntimeException e) { + throw mapException(e); + } + } + + public boolean walletApprove() { + try { + WalletApproveResponse resp = walletStub.approve( + WalletApproveRequest.getDefaultInstance()); + return resp.getApproved(); + } catch (StatusRuntimeException e) { + throw mapException(e); + } + } + // External Signer (Upload + Chunks prepare/finalize) /** diff --git a/antd-java/src/test/java/com/autonomi/antd/GrpcAntdClientTest.java b/antd-java/src/test/java/com/autonomi/antd/GrpcAntdClientTest.java index c680fae5..25005f5e 100644 --- a/antd-java/src/test/java/com/autonomi/antd/GrpcAntdClientTest.java +++ b/antd-java/src/test/java/com/autonomi/antd/GrpcAntdClientTest.java @@ -87,6 +87,7 @@ void setUp() throws Exception { .addService(new MockDataService()) .addService(new MockChunkService()) .addService(new MockFileService()) + .addService(new MockWalletService()) .addService(new MockUploadService()) .build() .start(); @@ -775,4 +776,90 @@ void testFinalizeChunkUploadReturnsAddressAndForwardsBody() { String addr = client.finalizeChunkUpload("upid_chunk_42", tx); assertEquals("addr_for_upid_chunk_42", addr); } + // ========================================================================= + // V2-286 MockWalletService + tests + // ========================================================================= + + static class MockWalletService extends antd.v1.WalletServiceGrpc.WalletServiceImplBase { + @Override + public void getAddress(antd.v1.Wallet.GetWalletAddressRequest req, + io.grpc.stub.StreamObserver resp) { + resp.onNext(antd.v1.Wallet.GetWalletAddressResponse.newBuilder() + .setAddress("0xabc1234567890abcdef1234567890abcdef123456") + .build()); + resp.onCompleted(); + } + + @Override + public void getBalance(antd.v1.Wallet.GetWalletBalanceRequest req, + io.grpc.stub.StreamObserver resp) { + resp.onNext(antd.v1.Wallet.GetWalletBalanceResponse.newBuilder() + .setBalance("1000000000000000000") + .setGasBalance("500000000000000000") + .build()); + resp.onCompleted(); + } + + @Override + public void approve(antd.v1.Wallet.WalletApproveRequest req, + io.grpc.stub.StreamObserver resp) { + resp.onNext(antd.v1.Wallet.WalletApproveResponse.newBuilder() + .setApproved(true) + .build()); + resp.onCompleted(); + } + } + + @Test + void walletAddressReturnsAddress() { + com.autonomi.antd.models.WalletAddress r = client.walletAddress(); + assertEquals("0xabc1234567890abcdef1234567890abcdef123456", r.address()); + } + + @Test + void walletBalanceReturnsBalances() { + com.autonomi.antd.models.WalletBalance r = client.walletBalance(); + assertEquals("1000000000000000000", r.balance()); + assertEquals("500000000000000000", r.gasBalance()); + } + + @Test + void walletApproveReturnsTrue() { + assertTrue(client.walletApprove()); + } + + /** + * Daemon emits gRPC FailedPrecondition for "wallet not configured"; the + * established mapping in GrpcAntdClient.mapException surfaces this as + * PaymentException. (Semantic is a bit off vs REST's 503 but matches every + * SDK.) + */ + @Test + void walletAddressUnconfiguredReturnsPaymentException() throws Exception { + String serverName = InProcessServerBuilder.generateName(); + Server errServer = InProcessServerBuilder.forName(serverName) + .directExecutor() + .addService(new antd.v1.WalletServiceGrpc.WalletServiceImplBase() { + @Override + public void getAddress(antd.v1.Wallet.GetWalletAddressRequest req, + io.grpc.stub.StreamObserver resp) { + resp.onError(io.grpc.Status.FAILED_PRECONDITION + .withDescription("wallet not configured — set AUTONOMI_WALLET_KEY") + .asRuntimeException()); + } + }) + .build() + .start(); + ManagedChannel ch = InProcessChannelBuilder.forName(serverName).directExecutor().build(); + try (GrpcAntdClient c = new GrpcAntdClient(ch)) { + com.autonomi.antd.errors.PaymentException ex = assertThrows( + com.autonomi.antd.errors.PaymentException.class, + c::walletAddress); + assertTrue(ex.getMessage().contains("wallet not configured")); + } finally { + ch.shutdownNow(); + errServer.shutdownNow(); + } + } + }