From 37fb60672f56e7b7f5e19b061ce7d959058eb0bc Mon Sep 17 00:00:00 2001
From: ghubstan <36207203+ghubstan@users.noreply.github.com>
Date: Sat, 20 Jun 2020 09:59:06 -0300
Subject: [PATCH 01/21] Add license comment
This is not in scope of current PR, but easy enough to review.
---
.../java/bisq/cli/PasswordCallCredentials.java | 17 +++++++++++++++++
.../java/bisq/core/grpc/GrpcWalletsService.java | 17 +++++++++++++++++
.../bisq/core/grpc/PasswordAuthInterceptor.java | 17 +++++++++++++++++
.../core/grpc/model/AddressBalanceInfo.java | 17 +++++++++++++++++
4 files changed, 68 insertions(+)
diff --git a/cli/src/main/java/bisq/cli/PasswordCallCredentials.java b/cli/src/main/java/bisq/cli/PasswordCallCredentials.java
index 14b451d28f8..a1de5be5564 100644
--- a/cli/src/main/java/bisq/cli/PasswordCallCredentials.java
+++ b/cli/src/main/java/bisq/cli/PasswordCallCredentials.java
@@ -1,3 +1,20 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
package bisq.cli;
import io.grpc.CallCredentials;
diff --git a/core/src/main/java/bisq/core/grpc/GrpcWalletsService.java b/core/src/main/java/bisq/core/grpc/GrpcWalletsService.java
index 0e44e8b329b..f57cb69afb7 100644
--- a/core/src/main/java/bisq/core/grpc/GrpcWalletsService.java
+++ b/core/src/main/java/bisq/core/grpc/GrpcWalletsService.java
@@ -1,3 +1,20 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
package bisq.core.grpc;
import bisq.core.grpc.model.AddressBalanceInfo;
diff --git a/core/src/main/java/bisq/core/grpc/PasswordAuthInterceptor.java b/core/src/main/java/bisq/core/grpc/PasswordAuthInterceptor.java
index 2ab29bcdc95..291c09c5944 100644
--- a/core/src/main/java/bisq/core/grpc/PasswordAuthInterceptor.java
+++ b/core/src/main/java/bisq/core/grpc/PasswordAuthInterceptor.java
@@ -1,3 +1,20 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
package bisq.core.grpc;
import io.grpc.Metadata;
diff --git a/core/src/main/java/bisq/core/grpc/model/AddressBalanceInfo.java b/core/src/main/java/bisq/core/grpc/model/AddressBalanceInfo.java
index e452f999416..dd9ed19f90e 100644
--- a/core/src/main/java/bisq/core/grpc/model/AddressBalanceInfo.java
+++ b/core/src/main/java/bisq/core/grpc/model/AddressBalanceInfo.java
@@ -1,3 +1,20 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
package bisq.core.grpc.model;
import bisq.common.Payload;
From 855ac0f250e5375dd89692fb7d0df8dc0b0c6a4e Mon Sep 17 00:00:00 2001
From: ghubstan <36207203+ghubstan@users.noreply.github.com>
Date: Sat, 20 Jun 2020 10:02:04 -0300
Subject: [PATCH 02/21] Add license comment
---
.../java/bisq/core/grpc/CoreWalletsService.java | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/core/src/main/java/bisq/core/grpc/CoreWalletsService.java b/core/src/main/java/bisq/core/grpc/CoreWalletsService.java
index d7696dce1f3..3eb4cac51ae 100644
--- a/core/src/main/java/bisq/core/grpc/CoreWalletsService.java
+++ b/core/src/main/java/bisq/core/grpc/CoreWalletsService.java
@@ -1,3 +1,20 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
package bisq.core.grpc;
import bisq.core.btc.Balances;
From bfcc693f69d06ba1d2ebb33caa1a71a7f4d83542 Mon Sep 17 00:00:00 2001
From: ghubstan <36207203+ghubstan@users.noreply.github.com>
Date: Sat, 20 Jun 2020 10:03:10 -0300
Subject: [PATCH 03/21] Add license comment
---
.../core/grpc/CorePaymentAccountsService.java | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/core/src/main/java/bisq/core/grpc/CorePaymentAccountsService.java b/core/src/main/java/bisq/core/grpc/CorePaymentAccountsService.java
index db2d3be4a03..c6c2613e1c7 100644
--- a/core/src/main/java/bisq/core/grpc/CorePaymentAccountsService.java
+++ b/core/src/main/java/bisq/core/grpc/CorePaymentAccountsService.java
@@ -1,3 +1,20 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
package bisq.core.grpc;
import bisq.core.account.witness.AccountAgeWitnessService;
From d06807b0e53c2c3054c90e7268b92f4fe1740041 Mon Sep 17 00:00:00 2001
From: ghubstan <36207203+ghubstan@users.noreply.github.com>
Date: Sat, 20 Jun 2020 10:10:39 -0300
Subject: [PATCH 04/21] Wrap Exception from core in gRPC StatusRuntimeException
---
.../core/grpc/GrpcPaymentAccountsService.java | 51 +++++++++++++++----
1 file changed, 41 insertions(+), 10 deletions(-)
diff --git a/core/src/main/java/bisq/core/grpc/GrpcPaymentAccountsService.java b/core/src/main/java/bisq/core/grpc/GrpcPaymentAccountsService.java
index f2a9abf0bbb..f88e1adb8dd 100644
--- a/core/src/main/java/bisq/core/grpc/GrpcPaymentAccountsService.java
+++ b/core/src/main/java/bisq/core/grpc/GrpcPaymentAccountsService.java
@@ -1,3 +1,20 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
package bisq.core.grpc;
import bisq.core.payment.PaymentAccount;
@@ -8,6 +25,8 @@
import bisq.proto.grpc.GetPaymentAccountsRequest;
import bisq.proto.grpc.PaymentAccountsGrpc;
+import io.grpc.Status;
+import io.grpc.StatusRuntimeException;
import io.grpc.stub.StreamObserver;
import javax.inject.Inject;
@@ -27,20 +46,32 @@ public GrpcPaymentAccountsService(CoreApi coreApi) {
@Override
public void createPaymentAccount(CreatePaymentAccountRequest req,
StreamObserver responseObserver) {
- coreApi.createPaymentAccount(req.getAccountName(), req.getAccountNumber(), req.getFiatCurrencyCode());
- var reply = CreatePaymentAccountReply.newBuilder().build();
- responseObserver.onNext(reply);
- responseObserver.onCompleted();
+ try {
+ coreApi.createPaymentAccount(req.getAccountName(), req.getAccountNumber(), req.getFiatCurrencyCode());
+ var reply = CreatePaymentAccountReply.newBuilder().build();
+ responseObserver.onNext(reply);
+ responseObserver.onCompleted();
+ } catch (Exception cause) {
+ var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage()));
+ responseObserver.onError(ex);
+ throw ex;
+ }
}
@Override
public void getPaymentAccounts(GetPaymentAccountsRequest req,
StreamObserver responseObserver) {
- var tradeStatistics = coreApi.getPaymentAccounts().stream()
- .map(PaymentAccount::toProtoMessage)
- .collect(Collectors.toList());
- var reply = GetPaymentAccountsReply.newBuilder().addAllPaymentAccounts(tradeStatistics).build();
- responseObserver.onNext(reply);
- responseObserver.onCompleted();
+ try {
+ var tradeStatistics = coreApi.getPaymentAccounts().stream()
+ .map(PaymentAccount::toProtoMessage)
+ .collect(Collectors.toList());
+ var reply = GetPaymentAccountsReply.newBuilder().addAllPaymentAccounts(tradeStatistics).build();
+ responseObserver.onNext(reply);
+ responseObserver.onCompleted();
+ } catch (Exception cause) {
+ var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage()));
+ responseObserver.onError(ex);
+ throw ex;
+ }
}
}
From 7c073c65f5bf4849bcee1c2ab92acf9136827556 Mon Sep 17 00:00:00 2001
From: ghubstan <36207203+ghubstan@users.noreply.github.com>
Date: Sat, 20 Jun 2020 10:14:36 -0300
Subject: [PATCH 05/21] Revert "Add license comment"
This reverts commit bfcc693f69d06ba1d2ebb33caa1a71a7f4d83542.
This change was reverted because we want unexpected Exceptions
to bubble up for now, until CorePaymentAccountsService.java
throws specific IllegalStateExceptions with user friendly
error messages. (See CoreWalletsService.java for example.)
---
.../core/grpc/CorePaymentAccountsService.java | 17 -----------------
1 file changed, 17 deletions(-)
diff --git a/core/src/main/java/bisq/core/grpc/CorePaymentAccountsService.java b/core/src/main/java/bisq/core/grpc/CorePaymentAccountsService.java
index c6c2613e1c7..db2d3be4a03 100644
--- a/core/src/main/java/bisq/core/grpc/CorePaymentAccountsService.java
+++ b/core/src/main/java/bisq/core/grpc/CorePaymentAccountsService.java
@@ -1,20 +1,3 @@
-/*
- * This file is part of Bisq.
- *
- * Bisq is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or (at
- * your option) any later version.
- *
- * Bisq is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
- * License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with Bisq. If not, see .
- */
-
package bisq.core.grpc;
import bisq.core.account.witness.AccountAgeWitnessService;
From d6ea0ea23692f37508acb56d2dc3d8f7333ebad0 Mon Sep 17 00:00:00 2001
From: ghubstan <36207203+ghubstan@users.noreply.github.com>
Date: Sat, 20 Jun 2020 10:21:57 -0300
Subject: [PATCH 06/21] Re-add license comment
The previous revert was a mistake. It applied to
GrpcPaymentAccountsService, not CorePaymentAccountsService.
---
.../core/grpc/CorePaymentAccountsService.java | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/core/src/main/java/bisq/core/grpc/CorePaymentAccountsService.java b/core/src/main/java/bisq/core/grpc/CorePaymentAccountsService.java
index db2d3be4a03..c6c2613e1c7 100644
--- a/core/src/main/java/bisq/core/grpc/CorePaymentAccountsService.java
+++ b/core/src/main/java/bisq/core/grpc/CorePaymentAccountsService.java
@@ -1,3 +1,20 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
package bisq.core.grpc;
import bisq.core.account.witness.AccountAgeWitnessService;
From 41f1add76b4faf66c2baf3c2c6633809365b41b4 Mon Sep 17 00:00:00 2001
From: ghubstan <36207203+ghubstan@users.noreply.github.com>
Date: Sat, 20 Jun 2020 10:24:14 -0300
Subject: [PATCH 07/21] Remove try catch block
Remove the recently added gRPC StatusRuntimeException wrapping
logic because we want unexpected Exceptions to bubble up for now,
until CorePaymentAccountsService.java throws specific
IllegalStateExceptions with user friendly error messages.
(See CoreWalletsService.java for example.)
---
.../core/grpc/GrpcPaymentAccountsService.java | 34 ++++++-------------
1 file changed, 10 insertions(+), 24 deletions(-)
diff --git a/core/src/main/java/bisq/core/grpc/GrpcPaymentAccountsService.java b/core/src/main/java/bisq/core/grpc/GrpcPaymentAccountsService.java
index f88e1adb8dd..8388b85e3bc 100644
--- a/core/src/main/java/bisq/core/grpc/GrpcPaymentAccountsService.java
+++ b/core/src/main/java/bisq/core/grpc/GrpcPaymentAccountsService.java
@@ -25,8 +25,6 @@
import bisq.proto.grpc.GetPaymentAccountsRequest;
import bisq.proto.grpc.PaymentAccountsGrpc;
-import io.grpc.Status;
-import io.grpc.StatusRuntimeException;
import io.grpc.stub.StreamObserver;
import javax.inject.Inject;
@@ -46,32 +44,20 @@ public GrpcPaymentAccountsService(CoreApi coreApi) {
@Override
public void createPaymentAccount(CreatePaymentAccountRequest req,
StreamObserver responseObserver) {
- try {
- coreApi.createPaymentAccount(req.getAccountName(), req.getAccountNumber(), req.getFiatCurrencyCode());
- var reply = CreatePaymentAccountReply.newBuilder().build();
- responseObserver.onNext(reply);
- responseObserver.onCompleted();
- } catch (Exception cause) {
- var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage()));
- responseObserver.onError(ex);
- throw ex;
- }
+ coreApi.createPaymentAccount(req.getAccountName(), req.getAccountNumber(), req.getFiatCurrencyCode());
+ var reply = CreatePaymentAccountReply.newBuilder().build();
+ responseObserver.onNext(reply);
+ responseObserver.onCompleted();
}
@Override
public void getPaymentAccounts(GetPaymentAccountsRequest req,
StreamObserver responseObserver) {
- try {
- var tradeStatistics = coreApi.getPaymentAccounts().stream()
- .map(PaymentAccount::toProtoMessage)
- .collect(Collectors.toList());
- var reply = GetPaymentAccountsReply.newBuilder().addAllPaymentAccounts(tradeStatistics).build();
- responseObserver.onNext(reply);
- responseObserver.onCompleted();
- } catch (Exception cause) {
- var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage()));
- responseObserver.onError(ex);
- throw ex;
- }
+ var tradeStatistics = coreApi.getPaymentAccounts().stream()
+ .map(PaymentAccount::toProtoMessage)
+ .collect(Collectors.toList());
+ var reply = GetPaymentAccountsReply.newBuilder().addAllPaymentAccounts(tradeStatistics).build();
+ responseObserver.onNext(reply);
+ responseObserver.onCompleted();
}
}
From 88cb90e2093e3563de1eb1e30cad46083245c172 Mon Sep 17 00:00:00 2001
From: ghubstan <36207203+ghubstan@users.noreply.github.com>
Date: Sat, 20 Jun 2020 19:56:28 -0300
Subject: [PATCH 08/21] Add rpc method 'getoffers'
The new method returns current buy or sell offers for a fiat ccy.
These changes need refactoring and polishing before merging, but they're
committed in this state to be safe (don't lose work). Changes include:
* New core.grpc classes
CoreOffersService
GrpcOffersService
model.OfferInfo
* CoreApi -- The new CoreOffersService is injected into CoreApi and
the old getOffers() and placeOffer() impls were moved into the
new CoreOffersService. The getOffers implementation was re-done.
Other changes are just rearranging location of core method calls.
* GrpcServer -- The new GrpcOffersService replaced the old
GetOffersService and PlaceOfferService.
* grpc.proto -- The old GetOffers and PlaceOffer services were combined
into a single Offers service, and the PlaceOffer rpc was renamed
as CreateOffer. These are the only substantive changes; the rest
is just rearranging location of the service defs in the file.
Also created a lighterweight OfferInfo proto message wrapper to
be passed between server & client (client has no access to core's
Offer and OfferPayload).
* OfferInfo -- A new wrapper around the OfferInfo proto message.
* CliMain -- The new GetOffers service stub was added.
Some (maybe too much) number and ccy formatting logic was
copied & modified from core. Some tedius string formatting
was added too (needs to be tidied up).
* License comments were also copied to several classes, and I
made a mistake in reverting changes to the wrong file.
TODO add unit tests
---
cli/src/main/java/bisq/cli/CliMain.java | 91 ++++++++
.../src/main/java/bisq/core/grpc/CoreApi.java | 171 ++++++--------
.../bisq/core/grpc/CoreOffersService.java | 138 ++++++++++++
.../bisq/core/grpc/GrpcOffersService.java | 105 +++++++++
.../main/java/bisq/core/grpc/GrpcServer.java | 53 +----
.../java/bisq/core/grpc/model/OfferInfo.java | 213 ++++++++++++++++++
proto/src/main/proto/grpc.proto | 76 ++++---
7 files changed, 670 insertions(+), 177 deletions(-)
create mode 100644 core/src/main/java/bisq/core/grpc/CoreOffersService.java
create mode 100644 core/src/main/java/bisq/core/grpc/GrpcOffersService.java
create mode 100644 core/src/main/java/bisq/core/grpc/model/OfferInfo.java
diff --git a/cli/src/main/java/bisq/cli/CliMain.java b/cli/src/main/java/bisq/cli/CliMain.java
index a60d41bd2b0..79ca518bc6b 100644
--- a/cli/src/main/java/bisq/cli/CliMain.java
+++ b/cli/src/main/java/bisq/cli/CliMain.java
@@ -22,10 +22,12 @@
import bisq.proto.grpc.GetAddressBalanceRequest;
import bisq.proto.grpc.GetBalanceRequest;
import bisq.proto.grpc.GetFundingAddressesRequest;
+import bisq.proto.grpc.GetOffersRequest;
import bisq.proto.grpc.GetPaymentAccountsRequest;
import bisq.proto.grpc.GetVersionGrpc;
import bisq.proto.grpc.GetVersionRequest;
import bisq.proto.grpc.LockWalletRequest;
+import bisq.proto.grpc.OffersGrpc;
import bisq.proto.grpc.PaymentAccountsGrpc;
import bisq.proto.grpc.RemoveWalletPasswordRequest;
import bisq.proto.grpc.SetWalletPasswordRequest;
@@ -38,14 +40,20 @@
import joptsimple.OptionParser;
import joptsimple.OptionSet;
+import java.text.DateFormat;
import java.text.DecimalFormat;
+import java.text.NumberFormat;
import java.io.IOException;
import java.io.PrintStream;
import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Date;
import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -57,6 +65,8 @@
import static java.lang.System.exit;
import static java.lang.System.out;
import static java.util.Collections.singletonList;
+import static protobuf.OfferPayload.Direction.BUY;
+import static protobuf.OfferPayload.Direction.SELL;
/**
* A command-line client for the Bisq gRPC API.
@@ -65,6 +75,7 @@
@Slf4j
public class CliMain {
+ private static final NumberFormat NUMBER_FORMAT = NumberFormat.getInstance(Locale.US);
private static final BigDecimal SATOSHI_DIVISOR = new BigDecimal(100000000);
private static final DecimalFormat BTC_FORMAT = new DecimalFormat("###,##0.00000000");
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
@@ -72,6 +83,7 @@ public class CliMain {
BTC_FORMAT.format(BigDecimal.valueOf(sats).divide(SATOSHI_DIVISOR));
private enum Method {
+ getoffers,
createpaymentacct,
getpaymentaccts,
getversion,
@@ -151,6 +163,7 @@ public static void run(String[] args) {
}));
var versionService = GetVersionGrpc.newBlockingStub(channel).withCallCredentials(credentials);
+ var offersService = OffersGrpc.newBlockingStub(channel).withCallCredentials(credentials);
var paymentAccountsService = PaymentAccountsGrpc.newBlockingStub(channel).withCallCredentials(credentials);
var walletsService = WalletsGrpc.newBlockingStub(channel).withCallCredentials(credentials);
@@ -185,6 +198,45 @@ public static void run(String[] args) {
out.println(formatTable(reply.getAddressBalanceInfoList()));
return;
}
+ case getoffers: {
+ if (nonOptionArgs.size() < 2)
+ throw new IllegalArgumentException("no buy/sell direction specified");
+
+ var direction = nonOptionArgs.get(1).toUpperCase();
+ if (!direction.equals("BUY") && !direction.equals("SELL"))
+ throw new IllegalArgumentException("no buy/sell direction specified");
+
+ if (nonOptionArgs.size() < 3)
+ throw new IllegalArgumentException("no fiat currency specified");
+
+ var fiatCurrency = nonOptionArgs.get(2).toUpperCase();
+
+ var request = GetOffersRequest.newBuilder()
+ .setDirection(direction)
+ .setFiatCurrencyCode(fiatCurrency)
+ .build();
+ var reply = offersService.getOffers(request);
+
+ // TODO Calculate these format specifiers on the fly?
+ out.println(format("%-8s Price in %s for 1 BTC %s %-23s %-14s %-24s %s",
+ "Buy/Sell", fiatCurrency, "BTC(min - max)", " " + fiatCurrency + "(min - max)",
+ "Payment Method", "Creation Date", "ID"));
+ out.println(reply.getOffersList().stream()
+ .map(o -> format("%-8s %22s %-25s %12s %-14s %-24s %s",
+ o.getDirection().equals(BUY.name()) ? SELL.name() : BUY.name(),
+ formatPrice(o.getPrice()),
+ o.getMinAmount() != o.getAmount() ? formatSatoshis.apply(o.getMinAmount())
+ + " - " + formatSatoshis.apply(o.getAmount())
+ : formatSatoshis.apply(o.getAmount()),
+ o.getMinVolume() != o.getVolume() ? formatVolume(o.getMinVolume())
+ + " - " + formatVolume(o.getVolume())
+ : formatVolume(o.getVolume()),
+ o.getPaymentMethodShortName(),
+ formatDateTime(o.getDate(), true),
+ o.getId()))
+ .collect(Collectors.joining("\n")));
+ return;
+ }
case createpaymentacct: {
if (nonOptionArgs.size() < 2)
throw new IllegalArgumentException("no account name specified");
@@ -295,6 +347,7 @@ private static void printHelp(OptionParser parser, PrintStream stream) {
stream.format("%-22s%-50s%s%n", "getbalance", "", "Get server wallet balance");
stream.format("%-22s%-50s%s%n", "getaddressbalance", "address", "Get server wallet address balance");
stream.format("%-22s%-50s%s%n", "getfundingaddresses", "", "Get BTC funding addresses");
+ stream.format("%-22s%-50s%s%n", "getoffers", "buy | sell, fiat currency code", "Get current offers");
stream.format("%-22s%-50s%s%n", "createpaymentacct", "account name, account number, currency code", "Create PerfectMoney dummy account");
stream.format("%-22s%-50s%s%n", "getpaymentaccts", "", "Get user payment accounts");
stream.format("%-22s%-50s%s%n", "lockwallet", "", "Remove wallet password from memory, locking the wallet");
@@ -317,4 +370,42 @@ private static String formatTable(List addressBalanceInfo) {
info.getNumConfirmations()))
.collect(Collectors.joining("\n"));
}
+
+ // TODO Find a proper home for these formatting methods, with minimum duplication
+ // of the :core and :desktop utils they were copied from.
+
+ // Copied from bisq.core.util.FormattingUtils (pass formatted date as well to client)
+ private static String formatDateTime(long timestamp, boolean useLocaleAndLocalTimezone) {
+ Date date = new Date(timestamp);
+ Locale locale = useLocaleAndLocalTimezone ? Locale.getDefault() : Locale.US;
+ DateFormat dateInstance = DateFormat.getDateInstance(DateFormat.DEFAULT, locale);
+ DateFormat timeInstance = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale);
+ if (!useLocaleAndLocalTimezone) {
+ dateInstance.setTimeZone(TimeZone.getTimeZone("UTC"));
+ timeInstance.setTimeZone(TimeZone.getTimeZone("UTC"));
+ }
+ return formatDateTime(date, dateInstance, timeInstance);
+ }
+
+ // Copied from bisq.core.util.FormattingUtils (pass formatted date as well to client)
+ private static String formatDateTime(Date date, DateFormat dateFormatter, DateFormat timeFormatter) {
+ if (date != null) {
+ return dateFormatter.format(date) + " " + timeFormatter.format(date);
+ } else {
+ return "";
+ }
+ }
+
+ private static String formatPrice(long price) {
+ NUMBER_FORMAT.setMaximumFractionDigits(4);
+ NUMBER_FORMAT.setMinimumFractionDigits(4);
+ NUMBER_FORMAT.setRoundingMode(RoundingMode.UNNECESSARY);
+ return NUMBER_FORMAT.format((double) price / 10000);
+ }
+
+ private static String formatVolume(long volume) {
+ NUMBER_FORMAT.setMaximumFractionDigits(0);
+ NUMBER_FORMAT.setRoundingMode(RoundingMode.UNNECESSARY);
+ return NUMBER_FORMAT.format((double) volume / 10000);
+ }
}
diff --git a/core/src/main/java/bisq/core/grpc/CoreApi.java b/core/src/main/java/bisq/core/grpc/CoreApi.java
index 27d4d42573d..88302a82d2a 100644
--- a/core/src/main/java/bisq/core/grpc/CoreApi.java
+++ b/core/src/main/java/bisq/core/grpc/CoreApi.java
@@ -19,16 +19,12 @@
import bisq.core.grpc.model.AddressBalanceInfo;
import bisq.core.monetary.Price;
-import bisq.core.offer.CreateOfferService;
import bisq.core.offer.Offer;
-import bisq.core.offer.OfferBookService;
import bisq.core.offer.OfferPayload;
-import bisq.core.offer.OpenOfferManager;
import bisq.core.payment.PaymentAccount;
import bisq.core.trade.handlers.TransactionResultHandler;
import bisq.core.trade.statistics.TradeStatistics2;
import bisq.core.trade.statistics.TradeStatisticsManager;
-import bisq.core.user.User;
import bisq.common.app.Version;
@@ -48,35 +44,95 @@
*/
@Slf4j
public class CoreApi {
+
+ private final CoreOffersService coreOffersService;
private final CorePaymentAccountsService paymentAccountsService;
private final CoreWalletsService walletsService;
- private final OfferBookService offerBookService;
private final TradeStatisticsManager tradeStatisticsManager;
- private final CreateOfferService createOfferService;
- private final OpenOfferManager openOfferManager;
- private final User user;
@Inject
- public CoreApi(CorePaymentAccountsService paymentAccountsService,
+ public CoreApi(CoreOffersService coreOffersService,
+ CorePaymentAccountsService paymentAccountsService,
CoreWalletsService walletsService,
- OfferBookService offerBookService,
- TradeStatisticsManager tradeStatisticsManager,
- CreateOfferService createOfferService,
- OpenOfferManager openOfferManager,
- User user) {
+ TradeStatisticsManager tradeStatisticsManager) {
+ this.coreOffersService = coreOffersService;
this.paymentAccountsService = paymentAccountsService;
this.walletsService = walletsService;
- this.offerBookService = offerBookService;
this.tradeStatisticsManager = tradeStatisticsManager;
- this.createOfferService = createOfferService;
- this.openOfferManager = openOfferManager;
- this.user = user;
}
public String getVersion() {
return Version.VERSION;
}
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Offers
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ public List getOffers(String direction, String fiatCurrencyCode) {
+ return coreOffersService.getOffers(direction, fiatCurrencyCode);
+ }
+
+ public void createOffer(String currencyCode,
+ String directionAsString,
+ long priceAsLong,
+ boolean useMarketBasedPrice,
+ double marketPriceMargin,
+ long amountAsLong,
+ long minAmountAsLong,
+ double buyerSecurityDeposit,
+ String paymentAccountId,
+ TransactionResultHandler resultHandler) {
+ coreOffersService.createOffer(currencyCode,
+ directionAsString,
+ priceAsLong,
+ useMarketBasedPrice,
+ marketPriceMargin,
+ amountAsLong,
+ minAmountAsLong,
+ buyerSecurityDeposit,
+ paymentAccountId,
+ resultHandler);
+ }
+
+ public void createOffer(String offerId,
+ String currencyCode,
+ OfferPayload.Direction direction,
+ Price price,
+ boolean useMarketBasedPrice,
+ double marketPriceMargin,
+ Coin amount,
+ Coin minAmount,
+ double buyerSecurityDeposit,
+ PaymentAccount paymentAccount,
+ boolean useSavingsWallet,
+ TransactionResultHandler resultHandler) {
+ coreOffersService.createOffer(offerId,
+ currencyCode,
+ direction,
+ price,
+ useMarketBasedPrice,
+ marketPriceMargin,
+ amount,
+ minAmount,
+ buyerSecurityDeposit,
+ paymentAccount,
+ useSavingsWallet,
+ resultHandler);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // PaymentAccounts
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ public void createPaymentAccount(String accountName, String accountNumber, String fiatCurrencyCode) {
+ paymentAccountsService.createPaymentAccount(accountName, accountNumber, fiatCurrencyCode);
+ }
+
+ public Set getPaymentAccounts() {
+ return paymentAccountsService.getPaymentAccounts();
+ }
+
///////////////////////////////////////////////////////////////////////////////////////////
// Wallets
///////////////////////////////////////////////////////////////////////////////////////////
@@ -120,83 +176,4 @@ public List getTradeStatistics() {
public int getNumConfirmationsForMostRecentTransaction(String addressString) {
return walletsService.getNumConfirmationsForMostRecentTransaction(addressString);
}
-
- public List getOffers() {
- return offerBookService.getOffers();
- }
-
- public void placeOffer(String currencyCode,
- String directionAsString,
- long priceAsLong,
- boolean useMarketBasedPrice,
- double marketPriceMargin,
- long amountAsLong,
- long minAmountAsLong,
- double buyerSecurityDeposit,
- String paymentAccountId,
- TransactionResultHandler resultHandler) {
- String offerId = createOfferService.getRandomOfferId();
- OfferPayload.Direction direction = OfferPayload.Direction.valueOf(directionAsString);
- Price price = Price.valueOf(currencyCode, priceAsLong);
- Coin amount = Coin.valueOf(amountAsLong);
- Coin minAmount = Coin.valueOf(minAmountAsLong);
- PaymentAccount paymentAccount = user.getPaymentAccount(paymentAccountId);
- // We don't support atm funding from external wallet to keep it simple
- boolean useSavingsWallet = true;
-
- placeOffer(offerId,
- currencyCode,
- direction,
- price,
- useMarketBasedPrice,
- marketPriceMargin,
- amount,
- minAmount,
- buyerSecurityDeposit,
- paymentAccount,
- useSavingsWallet,
- resultHandler);
- }
-
- public void placeOffer(String offerId,
- String currencyCode,
- OfferPayload.Direction direction,
- Price price,
- boolean useMarketBasedPrice,
- double marketPriceMargin,
- Coin amount,
- Coin minAmount,
- double buyerSecurityDeposit,
- PaymentAccount paymentAccount,
- boolean useSavingsWallet,
- TransactionResultHandler resultHandler) {
- Offer offer = createOfferService.createAndGetOffer(offerId,
- direction,
- currencyCode,
- amount,
- minAmount,
- price,
- useMarketBasedPrice,
- marketPriceMargin,
- buyerSecurityDeposit,
- paymentAccount);
-
- openOfferManager.placeOffer(offer,
- buyerSecurityDeposit,
- useSavingsWallet,
- resultHandler,
- log::error);
- }
-
- ///////////////////////////////////////////////////////////////////////////////////////////
- // PaymentAccounts
- ///////////////////////////////////////////////////////////////////////////////////////////
-
- public void createPaymentAccount(String accountName, String accountNumber, String fiatCurrencyCode) {
- paymentAccountsService.createPaymentAccount(accountName, accountNumber, fiatCurrencyCode);
- }
-
- public Set getPaymentAccounts() {
- return paymentAccountsService.getPaymentAccounts();
- }
}
diff --git a/core/src/main/java/bisq/core/grpc/CoreOffersService.java b/core/src/main/java/bisq/core/grpc/CoreOffersService.java
new file mode 100644
index 00000000000..95bf4d478e7
--- /dev/null
+++ b/core/src/main/java/bisq/core/grpc/CoreOffersService.java
@@ -0,0 +1,138 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.core.grpc;
+
+import bisq.core.monetary.Price;
+import bisq.core.offer.CreateOfferService;
+import bisq.core.offer.Offer;
+import bisq.core.offer.OfferBookService;
+import bisq.core.offer.OfferPayload;
+import bisq.core.offer.OpenOfferManager;
+import bisq.core.payment.PaymentAccount;
+import bisq.core.trade.handlers.TransactionResultHandler;
+import bisq.core.user.User;
+
+import org.bitcoinj.core.Coin;
+
+import javax.inject.Inject;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import lombok.extern.slf4j.Slf4j;
+
+import static bisq.core.offer.OfferPayload.Direction.BUY;
+
+@Slf4j
+public class CoreOffersService {
+
+ private final CreateOfferService createOfferService;
+ private final OfferBookService offerBookService;
+ private final OpenOfferManager openOfferManager;
+ private final User user;
+
+ @Inject
+ public CoreOffersService(CreateOfferService createOfferService,
+ OfferBookService offerBookService,
+ OpenOfferManager openOfferManager,
+ User user) {
+ this.createOfferService = createOfferService;
+ this.offerBookService = offerBookService;
+ this.openOfferManager = openOfferManager;
+ this.user = user;
+ }
+
+ public List getOffers(String direction, String fiatCurrencyCode) {
+ List offers = offerBookService.getOffers().stream()
+ .filter(o -> !o.getDirection().name().equals(direction)
+ && o.getOfferPayload().getCounterCurrencyCode().equals(fiatCurrencyCode))
+ .collect(Collectors.toList());
+
+ if (direction.equals(BUY.name()))
+ offers.sort(Comparator.comparing(Offer::getPrice));
+ else
+ offers.sort(Comparator.comparing(Offer::getPrice).reversed());
+
+ return offers;
+ }
+
+ public void createOffer(String currencyCode,
+ String directionAsString,
+ long priceAsLong,
+ boolean useMarketBasedPrice,
+ double marketPriceMargin,
+ long amountAsLong,
+ long minAmountAsLong,
+ double buyerSecurityDeposit,
+ String paymentAccountId,
+ TransactionResultHandler resultHandler) {
+ String offerId = createOfferService.getRandomOfferId();
+ OfferPayload.Direction direction = OfferPayload.Direction.valueOf(directionAsString);
+ Price price = Price.valueOf(currencyCode, priceAsLong);
+ Coin amount = Coin.valueOf(amountAsLong);
+ Coin minAmount = Coin.valueOf(minAmountAsLong);
+ PaymentAccount paymentAccount = user.getPaymentAccount(paymentAccountId);
+ // We don't support atm funding from external wallet to keep it simple
+ boolean useSavingsWallet = true;
+
+ //noinspection ConstantConditions
+ createOffer(offerId,
+ currencyCode,
+ direction,
+ price,
+ useMarketBasedPrice,
+ marketPriceMargin,
+ amount,
+ minAmount,
+ buyerSecurityDeposit,
+ paymentAccount,
+ useSavingsWallet,
+ resultHandler);
+ }
+
+ public void createOffer(String offerId,
+ String currencyCode,
+ OfferPayload.Direction direction,
+ Price price,
+ boolean useMarketBasedPrice,
+ double marketPriceMargin,
+ Coin amount,
+ Coin minAmount,
+ double buyerSecurityDeposit,
+ PaymentAccount paymentAccount,
+ boolean useSavingsWallet,
+ TransactionResultHandler resultHandler) {
+ Offer offer = createOfferService.createAndGetOffer(offerId,
+ direction,
+ currencyCode,
+ amount,
+ minAmount,
+ price,
+ useMarketBasedPrice,
+ marketPriceMargin,
+ buyerSecurityDeposit,
+ paymentAccount);
+
+ openOfferManager.placeOffer(offer,
+ buyerSecurityDeposit,
+ useSavingsWallet,
+ resultHandler,
+ log::error);
+ }
+}
diff --git a/core/src/main/java/bisq/core/grpc/GrpcOffersService.java b/core/src/main/java/bisq/core/grpc/GrpcOffersService.java
new file mode 100644
index 00000000000..c0f90a8abb9
--- /dev/null
+++ b/core/src/main/java/bisq/core/grpc/GrpcOffersService.java
@@ -0,0 +1,105 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.core.grpc;
+
+import bisq.core.grpc.model.OfferInfo;
+import bisq.core.trade.handlers.TransactionResultHandler;
+
+import bisq.proto.grpc.CreateOfferReply;
+import bisq.proto.grpc.CreateOfferRequest;
+import bisq.proto.grpc.GetOffersReply;
+import bisq.proto.grpc.GetOffersRequest;
+import bisq.proto.grpc.OffersGrpc;
+
+import io.grpc.stub.StreamObserver;
+
+import javax.inject.Inject;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+class GrpcOffersService extends OffersGrpc.OffersImplBase {
+
+ private final CoreApi coreApi;
+
+ @Inject
+ public GrpcOffersService(CoreApi coreApi) {
+ this.coreApi = coreApi;
+ }
+
+ @Override
+ public void getOffers(GetOffersRequest req,
+ StreamObserver responseObserver) {
+ // The client cannot see bisq.core.Offer or its fromProto method.
+ // We use the lighter weight OfferInfo proto wrapper instead, containing just
+ // enough fields to view and create offers.
+ List result = coreApi.getOffers(req.getDirection(), req.getFiatCurrencyCode())
+ .stream().map(offer -> new OfferInfo.OfferInfoBuilder()
+ .withId(offer.getId())
+ .withDirection(offer.getDirection().name())
+ .withPrice(offer.getPrice().getValue())
+ .withUseMarketBasedPrice(offer.isUseMarketBasedPrice())
+ .withMarketPriceMargin(offer.getMarketPriceMargin())
+ .withAmount(offer.getAmount().value)
+ .withMinAmount(offer.getMinAmount().value)
+ .withVolume(offer.getVolume().getValue())
+ .withMinVolume(offer.getMinVolume().getValue())
+ .withBuyerSecurityDeposit(offer.getBuyerSecurityDeposit().value)
+ .withPaymentAccountId("") // only used when creating offer (?)
+ .withPaymentMethodId(offer.getPaymentMethod().getId())
+ .withPaymentMethodShortName(offer.getPaymentMethod().getShortName())
+ .withBaseCurrencyCode(offer.getOfferPayload().getBaseCurrencyCode())
+ .withCounterCurrencyCode(offer.getOfferPayload().getCounterCurrencyCode())
+ .withDate(offer.getDate().getTime())
+ .build())
+ .collect(Collectors.toList());
+
+ var reply = GetOffersReply.newBuilder()
+ .addAllOffers(
+ result.stream()
+ .map(OfferInfo::toProtoMessage)
+ .collect(Collectors.toList()))
+ .build();
+ responseObserver.onNext(reply);
+ responseObserver.onCompleted();
+ }
+
+ @Override
+ public void createOffer(CreateOfferRequest req,
+ StreamObserver responseObserver) {
+ TransactionResultHandler resultHandler = transaction -> {
+ CreateOfferReply reply = CreateOfferReply.newBuilder().setResult(true).build();
+ responseObserver.onNext(reply);
+ responseObserver.onCompleted();
+ };
+ coreApi.createOffer(
+ req.getCurrencyCode(),
+ req.getDirection(),
+ req.getPrice(),
+ req.getUseMarketBasedPrice(),
+ req.getMarketPriceMargin(),
+ req.getAmount(),
+ req.getMinAmount(),
+ req.getBuyerSecurityDeposit(),
+ req.getPaymentAccountId(),
+ resultHandler);
+ }
+}
diff --git a/core/src/main/java/bisq/core/grpc/GrpcServer.java b/core/src/main/java/bisq/core/grpc/GrpcServer.java
index 6fa6dad9faf..4b5d195a2e1 100644
--- a/core/src/main/java/bisq/core/grpc/GrpcServer.java
+++ b/core/src/main/java/bisq/core/grpc/GrpcServer.java
@@ -17,24 +17,16 @@
package bisq.core.grpc;
-import bisq.core.offer.Offer;
-import bisq.core.trade.handlers.TransactionResultHandler;
import bisq.core.trade.statistics.TradeStatistics2;
import bisq.common.config.Config;
-import bisq.proto.grpc.GetOffersGrpc;
-import bisq.proto.grpc.GetOffersReply;
-import bisq.proto.grpc.GetOffersRequest;
import bisq.proto.grpc.GetTradeStatisticsGrpc;
import bisq.proto.grpc.GetTradeStatisticsReply;
import bisq.proto.grpc.GetTradeStatisticsRequest;
import bisq.proto.grpc.GetVersionGrpc;
import bisq.proto.grpc.GetVersionReply;
import bisq.proto.grpc.GetVersionRequest;
-import bisq.proto.grpc.PlaceOfferGrpc;
-import bisq.proto.grpc.PlaceOfferReply;
-import bisq.proto.grpc.PlaceOfferRequest;
import io.grpc.Server;
import io.grpc.ServerBuilder;
@@ -58,16 +50,16 @@ public class GrpcServer {
@Inject
public GrpcServer(Config config,
CoreApi coreApi,
+ GrpcOffersService offersService,
GrpcPaymentAccountsService paymentAccountsService,
- GrpcWalletsService walletService) {
+ GrpcWalletsService walletsService) {
this.coreApi = coreApi;
this.server = ServerBuilder.forPort(config.apiPort)
.addService(new GetVersionService())
.addService(new GetTradeStatisticsService())
- .addService(new GetOffersService())
- .addService(new PlaceOfferService())
+ .addService(offersService)
.addService(paymentAccountsService)
- .addService(walletService)
+ .addService(walletsService)
.intercept(new PasswordAuthInterceptor(config.apiPassword))
.build();
}
@@ -94,7 +86,6 @@ public void getVersion(GetVersionRequest req, StreamObserver re
}
}
-
class GetTradeStatisticsService extends GetTradeStatisticsGrpc.GetTradeStatisticsImplBase {
@Override
public void getTradeStatistics(GetTradeStatisticsRequest req,
@@ -109,40 +100,4 @@ public void getTradeStatistics(GetTradeStatisticsRequest req,
responseObserver.onCompleted();
}
}
-
- class GetOffersService extends GetOffersGrpc.GetOffersImplBase {
- @Override
- public void getOffers(GetOffersRequest req, StreamObserver responseObserver) {
-
- var tradeStatistics = coreApi.getOffers().stream()
- .map(Offer::toProtoMessage)
- .collect(Collectors.toList());
-
- var reply = GetOffersReply.newBuilder().addAllOffers(tradeStatistics).build();
- responseObserver.onNext(reply);
- responseObserver.onCompleted();
- }
- }
-
- class PlaceOfferService extends PlaceOfferGrpc.PlaceOfferImplBase {
- @Override
- public void placeOffer(PlaceOfferRequest req, StreamObserver responseObserver) {
- TransactionResultHandler resultHandler = transaction -> {
- PlaceOfferReply reply = PlaceOfferReply.newBuilder().setResult(true).build();
- responseObserver.onNext(reply);
- responseObserver.onCompleted();
- };
- coreApi.placeOffer(
- req.getCurrencyCode(),
- req.getDirection(),
- req.getPrice(),
- req.getUseMarketBasedPrice(),
- req.getMarketPriceMargin(),
- req.getAmount(),
- req.getMinAmount(),
- req.getBuyerSecurityDeposit(),
- req.getPaymentAccountId(),
- resultHandler);
- }
- }
}
diff --git a/core/src/main/java/bisq/core/grpc/model/OfferInfo.java b/core/src/main/java/bisq/core/grpc/model/OfferInfo.java
new file mode 100644
index 00000000000..c753004e7b7
--- /dev/null
+++ b/core/src/main/java/bisq/core/grpc/model/OfferInfo.java
@@ -0,0 +1,213 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.core.grpc.model;
+
+import bisq.common.Payload;
+
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+
+@EqualsAndHashCode
+@ToString
+@Getter
+public class OfferInfo implements Payload {
+
+ private final String id;
+ private final String direction;
+ private final long price;
+ private final boolean useMarketBasedPrice;
+ private final double marketPriceMargin;
+ private final long amount;
+ private final long minAmount;
+ private final long volume;
+ private final long minVolume;
+ private final long buyerSecurityDeposit;
+ private final String paymentAccountId; // only used when creating offer
+ private final String paymentMethodId;
+ private final String paymentMethodShortName;
+ // For fiat offer the baseCurrencyCode is BTC and the counterCurrencyCode is the fiat currency
+ // For altcoin offers it is the opposite. baseCurrencyCode is the altcoin and the counterCurrencyCode is BTC.
+ private final String baseCurrencyCode;
+ private final String counterCurrencyCode;
+ private final long date;
+
+ public OfferInfo(OfferInfoBuilder builder) {
+ this.id = builder.id;
+ this.direction = builder.direction;
+ this.price = builder.price;
+ this.useMarketBasedPrice = builder.useMarketBasedPrice;
+ this.marketPriceMargin = builder.marketPriceMargin;
+ this.amount = builder.amount;
+ this.minAmount = builder.minAmount;
+ this.volume = builder.volume;
+ this.minVolume = builder.minVolume;
+ this.buyerSecurityDeposit = builder.buyerSecurityDeposit;
+ this.paymentAccountId = builder.paymentAccountId;
+ this.paymentMethodId = builder.paymentMethodId;
+ this.paymentMethodShortName = builder.paymentMethodShortName;
+ this.baseCurrencyCode = builder.baseCurrencyCode;
+ this.counterCurrencyCode = builder.counterCurrencyCode;
+ this.date = builder.date;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // PROTO BUFFER
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public bisq.proto.grpc.OfferInfo toProtoMessage() {
+ return bisq.proto.grpc.OfferInfo.newBuilder()
+ .setId(id)
+ .setDirection(direction)
+ .setPrice(price)
+ .setUseMarketBasedPrice(useMarketBasedPrice)
+ .setMarketPriceMargin(marketPriceMargin)
+ .setAmount(amount)
+ .setMinAmount(minAmount)
+ .setVolume(volume)
+ .setMinVolume(minVolume)
+ .setBuyerSecurityDeposit(buyerSecurityDeposit)
+ .setPaymentAccountId(paymentAccountId)
+ .setPaymentMethodId(paymentMethodId)
+ .setPaymentMethodShortName(paymentMethodShortName)
+ .setBaseCurrencyCode(baseCurrencyCode)
+ .setCounterCurrencyCode(counterCurrencyCode)
+ .setDate(date)
+ .build();
+ }
+
+ public static OfferInfo fromProto(bisq.proto.grpc.OfferInfo proto) {
+ /*
+ TODO (needed by createoffer)
+ return new OfferInfo(proto.getOfferPayload().getId(),
+ proto.getOfferPayload().getDate());
+ */
+ return null;
+ }
+
+ /**
+ * OfferInfoBuilder helps avoid bungling use of the OfferInfo constructor
+ * argument list, which is large, of all one type, and not wise to overload.
+ */
+ public static class OfferInfoBuilder {
+ private String id;
+ private String direction;
+ private long price;
+ private boolean useMarketBasedPrice;
+ private double marketPriceMargin;
+ private long amount;
+ private long minAmount;
+ private long volume;
+ private long minVolume;
+ private long buyerSecurityDeposit;
+ private String paymentAccountId;
+ private String paymentMethodId;
+ private String paymentMethodShortName;
+ private String baseCurrencyCode;
+ private String counterCurrencyCode;
+ private long date;
+
+ public OfferInfoBuilder() {
+ }
+
+ public OfferInfoBuilder withId(String id) {
+ this.id = id;
+ return this;
+ }
+
+ public OfferInfoBuilder withDirection(String direction) {
+ this.direction = direction;
+ return this;
+ }
+
+ public OfferInfoBuilder withPrice(long price) {
+ this.price = price;
+ return this;
+ }
+
+ public OfferInfoBuilder withUseMarketBasedPrice(boolean useMarketBasedPrice) {
+ this.useMarketBasedPrice = useMarketBasedPrice;
+ return this;
+ }
+
+ public OfferInfoBuilder withMarketPriceMargin(double useMarketBasedPrice) {
+ this.marketPriceMargin = useMarketBasedPrice;
+ return this;
+ }
+
+ public OfferInfoBuilder withAmount(long amount) {
+ this.amount = amount;
+ return this;
+ }
+
+ public OfferInfoBuilder withMinAmount(long minAmount) {
+ this.minAmount = minAmount;
+ return this;
+ }
+
+ public OfferInfoBuilder withVolume(long volume) {
+ this.volume = volume;
+ return this;
+ }
+
+ public OfferInfoBuilder withMinVolume(long minVolume) {
+ this.minVolume = minVolume;
+ return this;
+ }
+
+ public OfferInfoBuilder withBuyerSecurityDeposit(long buyerSecurityDeposit) {
+ this.buyerSecurityDeposit = buyerSecurityDeposit;
+ return this;
+ }
+
+ public OfferInfoBuilder withPaymentAccountId(String paymentAccountId) {
+ this.paymentAccountId = paymentAccountId;
+ return this;
+ }
+
+ public OfferInfoBuilder withPaymentMethodId(String paymentMethodId) {
+ this.paymentMethodId = paymentMethodId;
+ return this;
+ }
+
+ public OfferInfoBuilder withPaymentMethodShortName(String paymentMethodShortName) {
+ this.paymentMethodShortName = paymentMethodShortName;
+ return this;
+ }
+
+ public OfferInfoBuilder withBaseCurrencyCode(String baseCurrencyCode) {
+ this.baseCurrencyCode = baseCurrencyCode;
+ return this;
+ }
+
+ public OfferInfoBuilder withCounterCurrencyCode(String counterCurrencyCode) {
+ this.counterCurrencyCode = counterCurrencyCode;
+ return this;
+ }
+
+ public OfferInfoBuilder withDate(long date) {
+ this.date = date;
+ return this;
+ }
+
+ public OfferInfo build() {
+ return new OfferInfo(this);
+ }
+ }
+}
diff --git a/proto/src/main/proto/grpc.proto b/proto/src/main/proto/grpc.proto
index 7168d04928c..730920b0622 100644
--- a/proto/src/main/proto/grpc.proto
+++ b/proto/src/main/proto/grpc.proto
@@ -40,35 +40,58 @@ message GetVersionReply {
}
///////////////////////////////////////////////////////////////////////////////////////////
-// TradeStatistics
+// Offers
///////////////////////////////////////////////////////////////////////////////////////////
-service GetTradeStatistics {
- rpc GetTradeStatistics (GetTradeStatisticsRequest) returns (GetTradeStatisticsReply) {
+service Offers {
+ rpc GetOffers (GetOffersRequest) returns (GetOffersReply) {
+ }
+ rpc CreateOffer (CreateOfferRequest) returns (CreateOfferReply) {
}
}
-message GetTradeStatisticsRequest {
+message GetOffersRequest {
+ string direction = 1;
+ string fiatCurrencyCode = 2;
}
-message GetTradeStatisticsReply {
- repeated TradeStatistics2 TradeStatistics = 1;
+message GetOffersReply {
+ repeated OfferInfo offers = 1;
}
-///////////////////////////////////////////////////////////////////////////////////////////
-// Offer
-///////////////////////////////////////////////////////////////////////////////////////////
-
-service GetOffers {
- rpc GetOffers (GetOffersRequest) returns (GetOffersReply) {
- }
+message CreateOfferRequest {
+ string currencyCode = 1; // TODO switch order w/ direction field in next PR
+ string direction = 2;
+ uint64 price = 3;
+ bool useMarketBasedPrice = 4;
+ double marketPriceMargin = 5;
+ uint64 amount = 6;
+ uint64 minAmount = 7;
+ double buyerSecurityDeposit = 8;
+ string paymentAccountId = 9;
}
-message GetOffersRequest {
+message CreateOfferReply {
+ bool result = 1;
}
-message GetOffersReply {
- repeated Offer offers = 1;
+message OfferInfo {
+ string id = 1;
+ string direction = 2;
+ uint64 price = 3;
+ bool useMarketBasedPrice = 4;
+ double marketPriceMargin = 5;
+ uint64 amount = 6;
+ uint64 minAmount = 7;
+ uint64 volume = 8;
+ uint64 minVolume = 9;
+ uint64 buyerSecurityDeposit = 10;
+ string paymentAccountId = 11; // only used when creating offer
+ string paymentMethodId = 12;
+ string paymentMethodShortName = 13;
+ string baseCurrencyCode = 14;
+ string counterCurrencyCode = 15;
+ uint64 date = 16;
}
///////////////////////////////////////////////////////////////////////////////////////////
@@ -99,28 +122,19 @@ message GetPaymentAccountsReply {
}
///////////////////////////////////////////////////////////////////////////////////////////
-// PlaceOffer
+// TradeStatistics
///////////////////////////////////////////////////////////////////////////////////////////
-service PlaceOffer {
- rpc PlaceOffer (PlaceOfferRequest) returns (PlaceOfferReply) {
+service GetTradeStatistics {
+ rpc GetTradeStatistics (GetTradeStatisticsRequest) returns (GetTradeStatisticsReply) {
}
}
-message PlaceOfferRequest {
- string currencyCode = 1;
- string direction = 2;
- uint64 price = 3;
- bool useMarketBasedPrice = 4;
- double marketPriceMargin = 5;
- uint64 amount = 6;
- uint64 minAmount = 7;
- double buyerSecurityDeposit = 8;
- string paymentAccountId = 9;
+message GetTradeStatisticsRequest {
}
-message PlaceOfferReply {
- bool result = 1;
+message GetTradeStatisticsReply {
+ repeated TradeStatistics2 TradeStatistics = 1;
}
///////////////////////////////////////////////////////////////////////////////////////////
From 1756258e818ec819dffc511ec71c554ab404fc0b Mon Sep 17 00:00:00 2001
From: ghubstan <36207203+ghubstan@users.noreply.github.com>
Date: Sat, 20 Jun 2020 20:09:18 -0300
Subject: [PATCH 09/21] Do not use protobuf.OfferPayload.Direction in client
---
cli/src/main/java/bisq/cli/CliMain.java | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/cli/src/main/java/bisq/cli/CliMain.java b/cli/src/main/java/bisq/cli/CliMain.java
index 79ca518bc6b..9d0f348e560 100644
--- a/cli/src/main/java/bisq/cli/CliMain.java
+++ b/cli/src/main/java/bisq/cli/CliMain.java
@@ -65,8 +65,6 @@
import static java.lang.System.exit;
import static java.lang.System.out;
import static java.util.Collections.singletonList;
-import static protobuf.OfferPayload.Direction.BUY;
-import static protobuf.OfferPayload.Direction.SELL;
/**
* A command-line client for the Bisq gRPC API.
@@ -223,7 +221,7 @@ public static void run(String[] args) {
"Payment Method", "Creation Date", "ID"));
out.println(reply.getOffersList().stream()
.map(o -> format("%-8s %22s %-25s %12s %-14s %-24s %s",
- o.getDirection().equals(BUY.name()) ? SELL.name() : BUY.name(),
+ o.getDirection().equals("BUY") ? "SELL" : "BUY",
formatPrice(o.getPrice()),
o.getMinAmount() != o.getAmount() ? formatSatoshis.apply(o.getMinAmount())
+ " - " + formatSatoshis.apply(o.getAmount())
From 4778976b6bb78b73537571b2ce8089fe6f427dbf Mon Sep 17 00:00:00 2001
From: ghubstan <36207203+ghubstan@users.noreply.github.com>
Date: Sat, 20 Jun 2020 20:24:49 -0300
Subject: [PATCH 10/21] Fix comments
---
core/src/main/java/bisq/core/grpc/model/OfferInfo.java | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/core/src/main/java/bisq/core/grpc/model/OfferInfo.java b/core/src/main/java/bisq/core/grpc/model/OfferInfo.java
index c753004e7b7..0b2dc5fe0cd 100644
--- a/core/src/main/java/bisq/core/grpc/model/OfferInfo.java
+++ b/core/src/main/java/bisq/core/grpc/model/OfferInfo.java
@@ -94,16 +94,18 @@ public bisq.proto.grpc.OfferInfo toProtoMessage() {
public static OfferInfo fromProto(bisq.proto.grpc.OfferInfo proto) {
/*
- TODO (needed by createoffer)
+ TODO (will be needed by the createoffer method)
return new OfferInfo(proto.getOfferPayload().getId(),
proto.getOfferPayload().getDate());
*/
return null;
}
- /**
- * OfferInfoBuilder helps avoid bungling use of the OfferInfo constructor
- * argument list, which is large, of all one type, and not wise to overload.
+ /*
+ * OfferInfoBuilder helps avoid bungling use of a large OfferInfo constructor
+ * argument list. If consecutive argument values of the same type are not
+ * ordered correctly, the compiler won't complain but the resulting bugs could
+ * be hard to find and fix.
*/
public static class OfferInfoBuilder {
private String id;
From b25abf1b6bf14aa3332fccb8e7b312ba43425673 Mon Sep 17 00:00:00 2001
From: ghubstan <36207203+ghubstan@users.noreply.github.com>
Date: Sun, 21 Jun 2020 19:46:37 -0300
Subject: [PATCH 11/21] Refactor CLI output table formatting
This change moves logic for formatting BTC balances, dates and
tables out of CliMain. Two new output formatting classes were
added: CurrencyFormat and TableFormat.
---
cli/src/main/java/bisq/cli/CliMain.java | 96 ++-------------
.../main/java/bisq/cli/CurrencyFormat.java | 47 +++++++
cli/src/main/java/bisq/cli/TableFormat.java | 116 ++++++++++++++++++
3 files changed, 170 insertions(+), 89 deletions(-)
create mode 100644 cli/src/main/java/bisq/cli/CurrencyFormat.java
create mode 100644 cli/src/main/java/bisq/cli/TableFormat.java
diff --git a/cli/src/main/java/bisq/cli/CliMain.java b/cli/src/main/java/bisq/cli/CliMain.java
index 9d0f348e560..f768430b624 100644
--- a/cli/src/main/java/bisq/cli/CliMain.java
+++ b/cli/src/main/java/bisq/cli/CliMain.java
@@ -17,7 +17,6 @@
package bisq.cli;
-import bisq.proto.grpc.AddressBalanceInfo;
import bisq.proto.grpc.CreatePaymentAccountRequest;
import bisq.proto.grpc.GetAddressBalanceRequest;
import bisq.proto.grpc.GetBalanceRequest;
@@ -40,26 +39,18 @@
import joptsimple.OptionParser;
import joptsimple.OptionSet;
-import java.text.DateFormat;
-import java.text.DecimalFormat;
-import java.text.NumberFormat;
-
import java.io.IOException;
import java.io.PrintStream;
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-
-import java.util.Date;
import java.util.List;
-import java.util.Locale;
-import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
-import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
+import static bisq.cli.CurrencyFormat.formatSatoshis;
+import static bisq.cli.TableFormat.formatAddressBalanceTbl;
+import static bisq.cli.TableFormat.formatOfferTable;
import static java.lang.String.format;
import static java.lang.System.err;
import static java.lang.System.exit;
@@ -73,13 +64,6 @@
@Slf4j
public class CliMain {
- private static final NumberFormat NUMBER_FORMAT = NumberFormat.getInstance(Locale.US);
- private static final BigDecimal SATOSHI_DIVISOR = new BigDecimal(100000000);
- private static final DecimalFormat BTC_FORMAT = new DecimalFormat("###,##0.00000000");
- @SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
- private static final Function formatSatoshis = (sats) ->
- BTC_FORMAT.format(BigDecimal.valueOf(sats).divide(SATOSHI_DIVISOR));
-
private enum Method {
getoffers,
createpaymentacct,
@@ -176,7 +160,7 @@ public static void run(String[] args) {
case getbalance: {
var request = GetBalanceRequest.newBuilder().build();
var reply = walletsService.getBalance(request);
- var btcBalance = formatSatoshis.apply(reply.getBalance());
+ var btcBalance = formatSatoshis(reply.getBalance());
out.println(btcBalance);
return;
}
@@ -187,13 +171,13 @@ public static void run(String[] args) {
var request = GetAddressBalanceRequest.newBuilder()
.setAddress(nonOptionArgs.get(1)).build();
var reply = walletsService.getAddressBalance(request);
- out.println(formatTable(singletonList(reply.getAddressBalanceInfo())));
+ out.println(formatAddressBalanceTbl(singletonList(reply.getAddressBalanceInfo())));
return;
}
case getfundingaddresses: {
var request = GetFundingAddressesRequest.newBuilder().build();
var reply = walletsService.getFundingAddresses(request);
- out.println(formatTable(reply.getAddressBalanceInfoList()));
+ out.println(formatAddressBalanceTbl(reply.getAddressBalanceInfoList()));
return;
}
case getoffers: {
@@ -214,25 +198,7 @@ public static void run(String[] args) {
.setFiatCurrencyCode(fiatCurrency)
.build();
var reply = offersService.getOffers(request);
-
- // TODO Calculate these format specifiers on the fly?
- out.println(format("%-8s Price in %s for 1 BTC %s %-23s %-14s %-24s %s",
- "Buy/Sell", fiatCurrency, "BTC(min - max)", " " + fiatCurrency + "(min - max)",
- "Payment Method", "Creation Date", "ID"));
- out.println(reply.getOffersList().stream()
- .map(o -> format("%-8s %22s %-25s %12s %-14s %-24s %s",
- o.getDirection().equals("BUY") ? "SELL" : "BUY",
- formatPrice(o.getPrice()),
- o.getMinAmount() != o.getAmount() ? formatSatoshis.apply(o.getMinAmount())
- + " - " + formatSatoshis.apply(o.getAmount())
- : formatSatoshis.apply(o.getAmount()),
- o.getMinVolume() != o.getVolume() ? formatVolume(o.getMinVolume())
- + " - " + formatVolume(o.getVolume())
- : formatVolume(o.getVolume()),
- o.getPaymentMethodShortName(),
- formatDateTime(o.getDate(), true),
- o.getId()))
- .collect(Collectors.joining("\n")));
+ out.println(formatOfferTable(reply.getOffersList(), fiatCurrency));
return;
}
case createpaymentacct: {
@@ -358,52 +324,4 @@ private static void printHelp(OptionParser parser, PrintStream stream) {
ex.printStackTrace(stream);
}
}
-
- private static String formatTable(List addressBalanceInfo) {
- return format("%-35s %13s %s%n", "Address", "Balance", "Confirmations")
- + addressBalanceInfo.stream()
- .map(info -> format("%-35s %13s %14d",
- info.getAddress(),
- formatSatoshis.apply(info.getBalance()),
- info.getNumConfirmations()))
- .collect(Collectors.joining("\n"));
- }
-
- // TODO Find a proper home for these formatting methods, with minimum duplication
- // of the :core and :desktop utils they were copied from.
-
- // Copied from bisq.core.util.FormattingUtils (pass formatted date as well to client)
- private static String formatDateTime(long timestamp, boolean useLocaleAndLocalTimezone) {
- Date date = new Date(timestamp);
- Locale locale = useLocaleAndLocalTimezone ? Locale.getDefault() : Locale.US;
- DateFormat dateInstance = DateFormat.getDateInstance(DateFormat.DEFAULT, locale);
- DateFormat timeInstance = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale);
- if (!useLocaleAndLocalTimezone) {
- dateInstance.setTimeZone(TimeZone.getTimeZone("UTC"));
- timeInstance.setTimeZone(TimeZone.getTimeZone("UTC"));
- }
- return formatDateTime(date, dateInstance, timeInstance);
- }
-
- // Copied from bisq.core.util.FormattingUtils (pass formatted date as well to client)
- private static String formatDateTime(Date date, DateFormat dateFormatter, DateFormat timeFormatter) {
- if (date != null) {
- return dateFormatter.format(date) + " " + timeFormatter.format(date);
- } else {
- return "";
- }
- }
-
- private static String formatPrice(long price) {
- NUMBER_FORMAT.setMaximumFractionDigits(4);
- NUMBER_FORMAT.setMinimumFractionDigits(4);
- NUMBER_FORMAT.setRoundingMode(RoundingMode.UNNECESSARY);
- return NUMBER_FORMAT.format((double) price / 10000);
- }
-
- private static String formatVolume(long volume) {
- NUMBER_FORMAT.setMaximumFractionDigits(0);
- NUMBER_FORMAT.setRoundingMode(RoundingMode.UNNECESSARY);
- return NUMBER_FORMAT.format((double) volume / 10000);
- }
}
diff --git a/cli/src/main/java/bisq/cli/CurrencyFormat.java b/cli/src/main/java/bisq/cli/CurrencyFormat.java
new file mode 100644
index 00000000000..859dc4e8e8f
--- /dev/null
+++ b/cli/src/main/java/bisq/cli/CurrencyFormat.java
@@ -0,0 +1,47 @@
+package bisq.cli;
+
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
+import java.util.Locale;
+
+class CurrencyFormat {
+
+ private static final NumberFormat NUMBER_FORMAT = NumberFormat.getInstance(Locale.US);
+
+ static final BigDecimal SATOSHI_DIVISOR = new BigDecimal(100000000);
+ static final DecimalFormat BTC_FORMAT = new DecimalFormat("###,##0.00000000");
+
+ @SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
+ static final String formatSatoshis(long sats) {
+ return BTC_FORMAT.format(BigDecimal.valueOf(sats).divide(SATOSHI_DIVISOR));
+ }
+
+ static String formatAmountRange(long minAmount, long amount) {
+ return minAmount != amount
+ ? formatSatoshis(minAmount) + " - " + formatSatoshis(amount)
+ : formatSatoshis(amount);
+ }
+
+ static String formatVolumeRange(long minVolume, long volume) {
+ return minVolume != volume
+ ? formatOfferVolume(minVolume) + " - " + formatOfferVolume(volume)
+ : formatOfferVolume(volume);
+ }
+
+ static String formatOfferPrice(long price) {
+ NUMBER_FORMAT.setMaximumFractionDigits(4);
+ NUMBER_FORMAT.setMinimumFractionDigits(4);
+ NUMBER_FORMAT.setRoundingMode(RoundingMode.UNNECESSARY);
+ return NUMBER_FORMAT.format((double) price / 10000);
+ }
+
+ static String formatOfferVolume(long volume) {
+ NUMBER_FORMAT.setMaximumFractionDigits(0);
+ NUMBER_FORMAT.setRoundingMode(RoundingMode.UNNECESSARY);
+ return NUMBER_FORMAT.format((double) volume / 10000);
+ }
+}
diff --git a/cli/src/main/java/bisq/cli/TableFormat.java b/cli/src/main/java/bisq/cli/TableFormat.java
new file mode 100644
index 00000000000..bee9e0c73a2
--- /dev/null
+++ b/cli/src/main/java/bisq/cli/TableFormat.java
@@ -0,0 +1,116 @@
+package bisq.cli;
+
+import bisq.proto.grpc.AddressBalanceInfo;
+import bisq.proto.grpc.OfferInfo;
+
+import java.text.DateFormat;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+import static bisq.cli.CurrencyFormat.formatAmountRange;
+import static bisq.cli.CurrencyFormat.formatOfferPrice;
+import static bisq.cli.CurrencyFormat.formatSatoshis;
+import static bisq.cli.CurrencyFormat.formatVolumeRange;
+import static com.google.common.base.Strings.padEnd;
+import static java.lang.String.format;
+import static java.text.DateFormat.DEFAULT;
+import static java.text.DateFormat.getDateInstance;
+import static java.text.DateFormat.getTimeInstance;
+import static java.util.Collections.max;
+import static java.util.Comparator.comparing;
+import static java.util.TimeZone.getTimeZone;
+
+class TableFormat {
+
+ // For inserting 2 spaces between column headers.
+ private static final String COL_HEADER_DELIMITER = " ";
+
+ // Table column header format specs, right padded with two spaces. In some cases
+ // such as COL_HEADER_CREATION_DATE, COL_HEADER_VOLUME and COL_HEADER_UUID, the
+ // expected max data string length is accounted for. In others, the column header length
+ // are expected to be greater than any column value length.
+ private static final String COL_HEADER_AMOUNT = padEnd("BTC(min - max)", 24, ' ');
+ private static final String COL_HEADER_CREATION_DATE = padEnd("Creation Date", 24, ' ');
+ private static final String COL_HEADER_DIRECTION = "Buy/Sell";
+ private static final String COL_HEADER_PAYMENT_METHOD = "Payment Method";
+ private static final String COL_HEADER_PRICE = "Price in %-3s for 1 BTC";
+ private static final String COL_HEADER_VOLUME = padEnd("%-3s(min - max)", 15, ' ');
+ private static final String COL_HEADER_UUID = padEnd("ID", 52, ' ');
+
+ static String formatAddressBalanceTbl(List addressBalanceInfo) {
+ return format("%-35s %13s %s%n", "Address", "Balance", "Confirmations")
+ + addressBalanceInfo.stream()
+ .map(info -> format("%-35s %13s %14d",
+ info.getAddress(),
+ formatSatoshis(info.getBalance()),
+ info.getNumConfirmations()))
+ .collect(Collectors.joining("\n"));
+ }
+
+ static String formatOfferTable(List offerInfo, String fiatCurrency) {
+
+ // Some column values might be longer than header, so we need to calculated them.
+ int paymentMethodColWidth = getLengthOfLongestColumn(
+ COL_HEADER_PAYMENT_METHOD.length(),
+ offerInfo.stream()
+ .map(o -> o.getPaymentMethodShortName())
+ .collect(Collectors.toList()));
+
+ String headersFormat = COL_HEADER_DIRECTION + COL_HEADER_DELIMITER
+ + COL_HEADER_PRICE + COL_HEADER_DELIMITER // includes %s -> fiatCurrency
+ + COL_HEADER_AMOUNT + COL_HEADER_DELIMITER
+ + COL_HEADER_VOLUME + COL_HEADER_DELIMITER // includes %s -> fiatCurrency
+ + padEnd(COL_HEADER_PAYMENT_METHOD, paymentMethodColWidth, ' ') + COL_HEADER_DELIMITER
+ + COL_HEADER_CREATION_DATE + COL_HEADER_DELIMITER
+ + COL_HEADER_UUID.trim() + "%n";
+ String headerLine = format(headersFormat, fiatCurrency, fiatCurrency);
+
+ String colDataFormat = "%-" + (COL_HEADER_DIRECTION.length() + COL_HEADER_DELIMITER.length()) + "s" // left
+ + "%" + (COL_HEADER_PRICE.length() - 1) + "s" // rt justify to end of hdr
+ + " %-" + (COL_HEADER_AMOUNT.length() - 1) + "s" // left justify
+ + " %" + COL_HEADER_VOLUME.length() + "s" // right justify
+ + " %-" + paymentMethodColWidth + "s" // left justify
+ + " %-" + (COL_HEADER_CREATION_DATE.length()) + "s" // left justify
+ + " %-" + COL_HEADER_UUID.length() + "s";
+ return headerLine
+ + offerInfo.stream()
+ .map(o -> format(colDataFormat,
+ o.getDirection().equals("BUY") ? "SELL" : "BUY",
+ formatOfferPrice(o.getPrice()),
+ formatAmountRange(o.getMinAmount(), o.getAmount()),
+ formatVolumeRange(o.getMinVolume(), o.getVolume()),
+ o.getPaymentMethodShortName(),
+ formatDateTime(o.getDate(), true),
+ o.getId()))
+ .collect(Collectors.joining("\n"));
+ }
+
+ // Return length of the longest string value, or the header.len, whichever is greater.
+ private static int getLengthOfLongestColumn(int headerLength, List strings) {
+ int longest = max(strings, comparing(s -> s.length())).length();
+ return longest > headerLength ? longest : headerLength;
+ }
+
+ private static String formatDateTime(long timestamp, boolean useLocaleAndLocalTimezone) {
+ Date date = new Date(timestamp);
+ Locale locale = useLocaleAndLocalTimezone ? Locale.getDefault() : Locale.US;
+ DateFormat dateInstance = getDateInstance(DEFAULT, locale);
+ DateFormat timeInstance = getTimeInstance(DEFAULT, locale);
+ if (!useLocaleAndLocalTimezone) {
+ dateInstance.setTimeZone(getTimeZone("UTC"));
+ timeInstance.setTimeZone(getTimeZone("UTC"));
+ }
+ return formatDateTime(date, dateInstance, timeInstance);
+ }
+
+ private static String formatDateTime(Date date, DateFormat dateFormatter, DateFormat timeFormatter) {
+ if (date != null) {
+ return dateFormatter.format(date) + " " + timeFormatter.format(date);
+ } else {
+ return "";
+ }
+ }
+}
From a48af7c05276f8a219994a776d9c4f4a30b97fb9 Mon Sep 17 00:00:00 2001
From: ghubstan <36207203+ghubstan@users.noreply.github.com>
Date: Mon, 22 Jun 2020 14:43:59 -0300
Subject: [PATCH 12/21] Add 'getoffers' unit tests
---
cli/test.sh | 29 +++++++++++++++++++++++++++++
1 file changed, 29 insertions(+)
diff --git a/cli/test.sh b/cli/test.sh
index eaa64c9ebc8..c44c85abf44 100755
--- a/cli/test.sh
+++ b/cli/test.sh
@@ -176,6 +176,35 @@
[ "$status" -eq 0 ]
}
+@test "test getoffers missing direction argument" {
+ run ./bisq-cli --password=xyz getoffers
+ [ "$status" -eq 1 ]
+ echo "actual output: $output" >&2
+ [ "$output" = "Error: no buy/sell direction specified" ]
+}
+
+@test "test getoffers missing ccy argument" {
+ run ./bisq-cli --password=xyz getoffers buy
+ [ "$status" -eq 1 ]
+ echo "actual output: $output" >&2
+ [ "$output" = "Error: no fiat currency specified" ]
+}
+
+@test "test getoffers buy eur check return status" {
+ run ./bisq-cli --password=xyz getoffers buy eur
+ [ "$status" -eq 0 ]
+}
+
+@test "test getoffers buy eur check return status" {
+ run ./bisq-cli --password=xyz getoffers buy eur
+ [ "$status" -eq 0 ]
+}
+
+@test "test getoffers sell gbp check return status" {
+ run ./bisq-cli --password=xyz getoffers sell gbp
+ [ "$status" -eq 0 ]
+}
+
@test "test help displayed on stderr if no options or arguments" {
run ./bisq-cli
[ "$status" -eq 1 ]
From 61285a760280eb42d00ef703f019a97ce806b0f9 Mon Sep 17 00:00:00 2001
From: ghubstan <36207203+ghubstan@users.noreply.github.com>
Date: Mon, 22 Jun 2020 15:08:46 -0300
Subject: [PATCH 13/21] Do not change case of input params in client
This commit is for a change requested in PR 4308:
https://github.com/bisq-network/bisq/pull/4308#pullrequestreview-435055483
".toUpperCase() seems misplaced here. It would soon get repetive.
Whether the underlying logic differentiates between capitalizations
is a low-level implementation detail and would do better at the
lowest practical level."
---
cli/src/main/java/bisq/cli/CliMain.java | 8 ++++----
core/src/main/java/bisq/core/grpc/CoreOffersService.java | 4 ++--
.../java/bisq/core/grpc/CorePaymentAccountsService.java | 2 +-
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/cli/src/main/java/bisq/cli/CliMain.java b/cli/src/main/java/bisq/cli/CliMain.java
index f768430b624..8e5d275c761 100644
--- a/cli/src/main/java/bisq/cli/CliMain.java
+++ b/cli/src/main/java/bisq/cli/CliMain.java
@@ -184,14 +184,14 @@ public static void run(String[] args) {
if (nonOptionArgs.size() < 2)
throw new IllegalArgumentException("no buy/sell direction specified");
- var direction = nonOptionArgs.get(1).toUpperCase();
- if (!direction.equals("BUY") && !direction.equals("SELL"))
+ var direction = nonOptionArgs.get(1);
+ if (!direction.equalsIgnoreCase("BUY") && !direction.equalsIgnoreCase("SELL"))
throw new IllegalArgumentException("no buy/sell direction specified");
if (nonOptionArgs.size() < 3)
throw new IllegalArgumentException("no fiat currency specified");
- var fiatCurrency = nonOptionArgs.get(2).toUpperCase();
+ var fiatCurrency = nonOptionArgs.get(2);
var request = GetOffersRequest.newBuilder()
.setDirection(direction)
@@ -215,7 +215,7 @@ public static void run(String[] args) {
if (nonOptionArgs.size() < 4)
throw new IllegalArgumentException("no fiat currency specified");
- var fiatCurrencyCode = nonOptionArgs.get(3).toUpperCase();
+ var fiatCurrencyCode = nonOptionArgs.get(3);
var request = CreatePaymentAccountRequest.newBuilder()
.setAccountName(accountName)
diff --git a/core/src/main/java/bisq/core/grpc/CoreOffersService.java b/core/src/main/java/bisq/core/grpc/CoreOffersService.java
index 95bf4d478e7..3ac365bd888 100644
--- a/core/src/main/java/bisq/core/grpc/CoreOffersService.java
+++ b/core/src/main/java/bisq/core/grpc/CoreOffersService.java
@@ -60,8 +60,8 @@ public CoreOffersService(CreateOfferService createOfferService,
public List getOffers(String direction, String fiatCurrencyCode) {
List offers = offerBookService.getOffers().stream()
- .filter(o -> !o.getDirection().name().equals(direction)
- && o.getOfferPayload().getCounterCurrencyCode().equals(fiatCurrencyCode))
+ .filter(o -> !o.getDirection().name().equalsIgnoreCase(direction)
+ && o.getOfferPayload().getCounterCurrencyCode().equalsIgnoreCase(fiatCurrencyCode))
.collect(Collectors.toList());
if (direction.equals(BUY.name()))
diff --git a/core/src/main/java/bisq/core/grpc/CorePaymentAccountsService.java b/core/src/main/java/bisq/core/grpc/CorePaymentAccountsService.java
index c6c2613e1c7..9a7183d21ab 100644
--- a/core/src/main/java/bisq/core/grpc/CorePaymentAccountsService.java
+++ b/core/src/main/java/bisq/core/grpc/CorePaymentAccountsService.java
@@ -58,7 +58,7 @@ public void createPaymentAccount(String accountName, String accountNumber, Strin
paymentAccount.init();
paymentAccount.setAccountName(accountName);
((PerfectMoneyAccount) paymentAccount).setAccountNr(accountNumber);
- paymentAccount.setSingleTradeCurrency(new FiatCurrency(fiatCurrencyCode));
+ paymentAccount.setSingleTradeCurrency(new FiatCurrency(fiatCurrencyCode.toUpperCase()));
user.addPaymentAccount(paymentAccount);
// Don't do this on mainnet until thoroughly tested.
From 0d9bdefa006843fb1f8b555672377ad0d85f493c Mon Sep 17 00:00:00 2001
From: ghubstan <36207203+ghubstan@users.noreply.github.com>
Date: Mon, 22 Jun 2020 15:23:27 -0300
Subject: [PATCH 14/21] Add 'getoffers' smoke test
---
.../java/bisq/cli/GetOffersSmokeTest.java | 39 +++++++++++++++++++
1 file changed, 39 insertions(+)
create mode 100644 cli/src/test/java/bisq/cli/GetOffersSmokeTest.java
diff --git a/cli/src/test/java/bisq/cli/GetOffersSmokeTest.java b/cli/src/test/java/bisq/cli/GetOffersSmokeTest.java
new file mode 100644
index 00000000000..49f849e0cc7
--- /dev/null
+++ b/cli/src/test/java/bisq/cli/GetOffersSmokeTest.java
@@ -0,0 +1,39 @@
+package bisq.cli;
+
+import static java.lang.System.out;
+
+/**
+ Smoke tests for getoffers method. Useful for examining the format of the console output.
+
+ Prerequisites:
+
+ - Run `./bisq-daemon --apiPassword=xyz --appDataDir=$TESTDIR`
+
+ This can be run on mainnet.
+ */
+public class GetOffersSmokeTest {
+
+ public static void main(String[] args) {
+
+ out.println(">>> getoffers buy usd");
+ CliMain.main(new String[]{"--password=xyz", "getoffers", "buy", "usd"});
+ out.println(">>> getoffers sell usd");
+ CliMain.main(new String[]{"--password=xyz", "getoffers", "sell", "usd"});
+
+ out.println(">>> getoffers buy eur");
+ CliMain.main(new String[]{"--password=xyz", "getoffers", "buy", "eur"});
+ out.println(">>> getoffers sell eur");
+ CliMain.main(new String[]{"--password=xyz", "getoffers", "sell", "eur"});
+
+ out.println(">>> getoffers buy gbp");
+ CliMain.main(new String[]{"--password=xyz", "getoffers", "buy", "gbp"});
+ out.println(">>> getoffers sell gbp");
+ CliMain.main(new String[]{"--password=xyz", "getoffers", "sell", "gbp"});
+
+ out.println(">>> getoffers buy brl");
+ CliMain.main(new String[]{"--password=xyz", "getoffers", "buy", "brl"});
+ out.println(">>> getoffers sell brl");
+ CliMain.main(new String[]{"--password=xyz", "getoffers", "sell", "brl"});
+ }
+
+}
From 8dcfa50bde222b01b78bcc88e685109f1657d63d Mon Sep 17 00:00:00 2001
From: ghubstan <36207203+ghubstan@users.noreply.github.com>
Date: Mon, 22 Jun 2020 17:49:46 -0300
Subject: [PATCH 15/21] Define reusable headers from balance-info tbl
---
cli/src/main/java/bisq/cli/TableFormat.java | 36 +++++++++++++++++++--
1 file changed, 33 insertions(+), 3 deletions(-)
diff --git a/cli/src/main/java/bisq/cli/TableFormat.java b/cli/src/main/java/bisq/cli/TableFormat.java
index bee9e0c73a2..4803a06a361 100644
--- a/cli/src/main/java/bisq/cli/TableFormat.java
+++ b/cli/src/main/java/bisq/cli/TableFormat.java
@@ -15,6 +15,7 @@
import static bisq.cli.CurrencyFormat.formatSatoshis;
import static bisq.cli.CurrencyFormat.formatVolumeRange;
import static com.google.common.base.Strings.padEnd;
+import static com.google.common.base.Strings.padStart;
import static java.lang.String.format;
import static java.text.DateFormat.DEFAULT;
import static java.text.DateFormat.getDateInstance;
@@ -32,18 +33,27 @@ class TableFormat {
// such as COL_HEADER_CREATION_DATE, COL_HEADER_VOLUME and COL_HEADER_UUID, the
// expected max data string length is accounted for. In others, the column header length
// are expected to be greater than any column value length.
+ private static final String COL_HEADER_ADDRESS = padEnd("Address", 34, ' ');
private static final String COL_HEADER_AMOUNT = padEnd("BTC(min - max)", 24, ' ');
+ private static final String COL_HEADER_BALANCE = padStart("Balance", 12, ' ');
+ private static final String COL_HEADER_CONFIRMATIONS = "Confirmations";
private static final String COL_HEADER_CREATION_DATE = padEnd("Creation Date", 24, ' ');
- private static final String COL_HEADER_DIRECTION = "Buy/Sell";
+ private static final String COL_HEADER_DIRECTION = "Buy/Sell"; // TODO "Take Offer to
private static final String COL_HEADER_PAYMENT_METHOD = "Payment Method";
private static final String COL_HEADER_PRICE = "Price in %-3s for 1 BTC";
private static final String COL_HEADER_VOLUME = padEnd("%-3s(min - max)", 15, ' ');
private static final String COL_HEADER_UUID = padEnd("ID", 52, ' ');
static String formatAddressBalanceTbl(List addressBalanceInfo) {
- return format("%-35s %13s %s%n", "Address", "Balance", "Confirmations")
+ String headerLine = (COL_HEADER_ADDRESS + COL_HEADER_DELIMITER
+ + COL_HEADER_BALANCE + COL_HEADER_DELIMITER
+ + COL_HEADER_CONFIRMATIONS + COL_HEADER_DELIMITER + "\n");
+ String colDataFormat = "%-" + COL_HEADER_ADDRESS.length() + "s" // left justify
+ + " %" + COL_HEADER_BALANCE.length() + "s" // right justify
+ + " %" + COL_HEADER_CONFIRMATIONS.length() + "d"; // right justify
+ return headerLine
+ addressBalanceInfo.stream()
- .map(info -> format("%-35s %13s %14d",
+ .map(info -> format(colDataFormat,
info.getAddress(),
formatSatoshis(info.getBalance()),
info.getNumConfirmations()))
@@ -88,6 +98,26 @@ static String formatOfferTable(List offerInfo, String fiatCurrency) {
.collect(Collectors.joining("\n"));
}
+ static String formatPaymentAcctTbl() {
+ /*
+ case getpaymentaccts: {
+ var request = GetPaymentAccountsRequest.newBuilder().build();
+ var reply = paymentAccountsService.getPaymentAccounts(request);
+ var columnFormatSpec = "%-41s %-25s %-14s %s";
+ out.println(format(columnFormatSpec, "ID", "Name", "Currency", "Payment Method"));
+ out.println(reply.getPaymentAccountsList().stream()
+ .map(a -> format(columnFormatSpec,
+ a.getId(),
+ a.getAccountName(),
+ a.getSelectedTradeCurrency().getCode(),
+ a.getPaymentMethod().getId()))
+ .collect(Collectors.joining("\n")));
+ return;
+ }
+ */
+ return "";
+ }
+
// Return length of the longest string value, or the header.len, whichever is greater.
private static int getLengthOfLongestColumn(int headerLength, List strings) {
int longest = max(strings, comparing(s -> s.length())).length();
From 52529a912eeae18c78dae9ed00e8c73d6357917f Mon Sep 17 00:00:00 2001
From: ghubstan <36207203+ghubstan@users.noreply.github.com>
Date: Mon, 22 Jun 2020 18:20:09 -0300
Subject: [PATCH 16/21] Move getpaymentaccts tbl formatting to TableFormat
Created a new TableFormat.formatPaymentAcctTbl method.
Also:
* Defined new "Currency" and "Name" column headers in TableFormat.
* Changed syntax of stream().map() calls in some TableFormat methods.
* Fixed verbose return statement in TableFormat.getLengthOfLongestColumn.
This commit is out of scope for the getoffers PR (4329), but is
included as part of the migration of all console tbl formatting
from the client into TableFormat.
---
cli/src/main/java/bisq/cli/CliMain.java | 12 +----
cli/src/main/java/bisq/cli/TableFormat.java | 55 +++++++++++++--------
2 files changed, 36 insertions(+), 31 deletions(-)
diff --git a/cli/src/main/java/bisq/cli/CliMain.java b/cli/src/main/java/bisq/cli/CliMain.java
index 8e5d275c761..cb99de79e3b 100644
--- a/cli/src/main/java/bisq/cli/CliMain.java
+++ b/cli/src/main/java/bisq/cli/CliMain.java
@@ -44,13 +44,13 @@
import java.util.List;
import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import static bisq.cli.CurrencyFormat.formatSatoshis;
import static bisq.cli.TableFormat.formatAddressBalanceTbl;
import static bisq.cli.TableFormat.formatOfferTable;
+import static bisq.cli.TableFormat.formatPaymentAcctTbl;
import static java.lang.String.format;
import static java.lang.System.err;
import static java.lang.System.exit;
@@ -228,15 +228,7 @@ public static void run(String[] args) {
case getpaymentaccts: {
var request = GetPaymentAccountsRequest.newBuilder().build();
var reply = paymentAccountsService.getPaymentAccounts(request);
- var columnFormatSpec = "%-41s %-25s %-14s %s";
- out.println(format(columnFormatSpec, "ID", "Name", "Currency", "Payment Method"));
- out.println(reply.getPaymentAccountsList().stream()
- .map(a -> format(columnFormatSpec,
- a.getId(),
- a.getAccountName(),
- a.getSelectedTradeCurrency().getCode(),
- a.getPaymentMethod().getId()))
- .collect(Collectors.joining("\n")));
+ out.println(formatPaymentAcctTbl(reply.getPaymentAccountsList()));
return;
}
case lockwallet: {
diff --git a/cli/src/main/java/bisq/cli/TableFormat.java b/cli/src/main/java/bisq/cli/TableFormat.java
index 4803a06a361..bb1ef4e3b2a 100644
--- a/cli/src/main/java/bisq/cli/TableFormat.java
+++ b/cli/src/main/java/bisq/cli/TableFormat.java
@@ -3,6 +3,8 @@
import bisq.proto.grpc.AddressBalanceInfo;
import bisq.proto.grpc.OfferInfo;
+import protobuf.PaymentAccount;
+
import java.text.DateFormat;
import java.util.Date;
@@ -38,7 +40,9 @@ class TableFormat {
private static final String COL_HEADER_BALANCE = padStart("Balance", 12, ' ');
private static final String COL_HEADER_CONFIRMATIONS = "Confirmations";
private static final String COL_HEADER_CREATION_DATE = padEnd("Creation Date", 24, ' ');
+ private static final String COL_HEADER_CURRENCY = "Currency";
private static final String COL_HEADER_DIRECTION = "Buy/Sell"; // TODO "Take Offer to
+ private static final String COL_HEADER_NAME = "Name";
private static final String COL_HEADER_PAYMENT_METHOD = "Payment Method";
private static final String COL_HEADER_PRICE = "Price in %-3s for 1 BTC";
private static final String COL_HEADER_VOLUME = padEnd("%-3s(min - max)", 15, ' ');
@@ -66,7 +70,7 @@ static String formatOfferTable(List offerInfo, String fiatCurrency) {
int paymentMethodColWidth = getLengthOfLongestColumn(
COL_HEADER_PAYMENT_METHOD.length(),
offerInfo.stream()
- .map(o -> o.getPaymentMethodShortName())
+ .map(OfferInfo::getPaymentMethodShortName)
.collect(Collectors.toList()));
String headersFormat = COL_HEADER_DIRECTION + COL_HEADER_DELIMITER
@@ -98,30 +102,39 @@ static String formatOfferTable(List offerInfo, String fiatCurrency) {
.collect(Collectors.joining("\n"));
}
- static String formatPaymentAcctTbl() {
- /*
- case getpaymentaccts: {
- var request = GetPaymentAccountsRequest.newBuilder().build();
- var reply = paymentAccountsService.getPaymentAccounts(request);
- var columnFormatSpec = "%-41s %-25s %-14s %s";
- out.println(format(columnFormatSpec, "ID", "Name", "Currency", "Payment Method"));
- out.println(reply.getPaymentAccountsList().stream()
- .map(a -> format(columnFormatSpec,
- a.getId(),
- a.getAccountName(),
- a.getSelectedTradeCurrency().getCode(),
- a.getPaymentMethod().getId()))
- .collect(Collectors.joining("\n")));
- return;
- }
- */
- return "";
+ static String formatPaymentAcctTbl(List paymentAccounts) {
+ // Some column values might be longer than header, so we need to calculated them.
+ int nameColWidth = getLengthOfLongestColumn(
+ COL_HEADER_NAME.length(),
+ paymentAccounts.stream().map(PaymentAccount::getAccountName)
+ .collect(Collectors.toList()));
+ int paymentMethodColWidth = getLengthOfLongestColumn(
+ COL_HEADER_PAYMENT_METHOD.length(),
+ paymentAccounts.stream().map(a -> a.getPaymentMethod().getId())
+ .collect(Collectors.toList()));
+
+ String headerLine = padEnd(COL_HEADER_NAME, nameColWidth, ' ') + COL_HEADER_DELIMITER
+ + COL_HEADER_CURRENCY + COL_HEADER_DELIMITER
+ + padEnd(COL_HEADER_PAYMENT_METHOD, paymentMethodColWidth, ' ') + COL_HEADER_DELIMITER
+ + COL_HEADER_UUID + COL_HEADER_DELIMITER + "\n";
+ String colDataFormat = "%-" + nameColWidth + "s" // left justify
+ + " %" + COL_HEADER_CURRENCY.length() + "s" // right justify
+ + " %-" + paymentMethodColWidth + "s" // left justify
+ + " %-" + COL_HEADER_UUID.length() + "s"; // left justify
+ return headerLine
+ + paymentAccounts.stream()
+ .map(a -> format(colDataFormat,
+ a.getAccountName(),
+ a.getSelectedTradeCurrency().getCode(),
+ a.getPaymentMethod().getId(),
+ a.getId()))
+ .collect(Collectors.joining("\n"));
}
// Return length of the longest string value, or the header.len, whichever is greater.
private static int getLengthOfLongestColumn(int headerLength, List strings) {
- int longest = max(strings, comparing(s -> s.length())).length();
- return longest > headerLength ? longest : headerLength;
+ int longest = max(strings, comparing(String::length)).length();
+ return Math.max(longest, headerLength);
}
private static String formatDateTime(long timestamp, boolean useLocaleAndLocalTimezone) {
From 9691f350825cbe33e2a4ab17b51d1032f4e9efc1 Mon Sep 17 00:00:00 2001
From: ghubstan <36207203+ghubstan@users.noreply.github.com>
Date: Tue, 23 Jun 2020 12:42:56 -0300
Subject: [PATCH 17/21] Check param count only, not param order correctness
This change simplifies client 'createpaymentacct' method parameter
validation. It no longer assumes parameter ordering is correct, and
only verifies the string parameter count is correct.
A unit test was also added to cli/test.sh
This commit is in response to the requested change in PR 4308.
https://github.com/bisq-network/bisq/pull/4308#pullrequestreview-435052357
---
cli/src/main/java/bisq/cli/CliMain.java | 13 +++----------
cli/test.sh | 7 +++++++
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/cli/src/main/java/bisq/cli/CliMain.java b/cli/src/main/java/bisq/cli/CliMain.java
index cb99de79e3b..074af31d19b 100644
--- a/cli/src/main/java/bisq/cli/CliMain.java
+++ b/cli/src/main/java/bisq/cli/CliMain.java
@@ -202,19 +202,12 @@ public static void run(String[] args) {
return;
}
case createpaymentacct: {
- if (nonOptionArgs.size() < 2)
- throw new IllegalArgumentException("no account name specified");
+ if (nonOptionArgs.size() < 4)
+ throw new IllegalArgumentException(
+ "incorrect parameter count, expecting account name, account number, currency code");
var accountName = nonOptionArgs.get(1);
-
- if (nonOptionArgs.size() < 3)
- throw new IllegalArgumentException("no account number specified");
-
var accountNumber = nonOptionArgs.get(2);
-
- if (nonOptionArgs.size() < 4)
- throw new IllegalArgumentException("no fiat currency specified");
-
var fiatCurrencyCode = nonOptionArgs.get(3);
var request = CreatePaymentAccountRequest.newBuilder()
diff --git a/cli/test.sh b/cli/test.sh
index c44c85abf44..d21ab501d55 100755
--- a/cli/test.sh
+++ b/cli/test.sh
@@ -166,6 +166,13 @@
[ "$output" = "Error: address bogus not found in wallet" ]
}
+@test "test createpaymentacct PerfectMoneyDummy (missing nbr, ccy params)" {
+ run ./bisq-cli --password=xyz createpaymentacct PerfectMoneyDummy
+ [ "$status" -eq 1 ]
+ echo "actual output: $output" >&2
+ [ "$output" = "Error: incorrect parameter count, expecting account name, account number, currency code" ]
+}
+
@test "test createpaymentacct PerfectMoneyDummy 0123456789 USD" {
run ./bisq-cli --password=xyz createpaymentacct PerfectMoneyDummy 0123456789 USD
[ "$status" -eq 0 ]
From e1fddfacf8867ed139f0e594f2fb4adafe27e8c4 Mon Sep 17 00:00:00 2001
From: ghubstan <36207203+ghubstan@users.noreply.github.com>
Date: Tue, 23 Jun 2020 13:11:36 -0300
Subject: [PATCH 18/21] Remove duplication in wallets availability checks
This change adds a new 'verifyWalletsAreAvailable' method to the client,
which eliminates this duplicated statement:
throw new IllegalStateException("wallet is not yet available");
The commit is in response to a requested change in PR 4312:
https://github.com/bisq-network/bisq/pull/4312#pullrequestreview-435659314
---
.../java/bisq/core/grpc/CoreWalletsService.java | 17 +++++++++--------
1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/core/src/main/java/bisq/core/grpc/CoreWalletsService.java b/core/src/main/java/bisq/core/grpc/CoreWalletsService.java
index 3eb4cac51ae..1d3719558b7 100644
--- a/core/src/main/java/bisq/core/grpc/CoreWalletsService.java
+++ b/core/src/main/java/bisq/core/grpc/CoreWalletsService.java
@@ -72,9 +72,7 @@ public CoreWalletsService(Balances balances,
}
public long getAvailableBalance() {
- if (!walletsManager.areWalletsAvailable())
- throw new IllegalStateException("wallet is not yet available");
-
+ verifyWalletsAreAvailable();
verifyEncryptedWalletIsUnlocked();
var balance = balances.getAvailableBalance().get();
@@ -96,9 +94,7 @@ public AddressBalanceInfo getAddressBalanceInfo(String addressString) {
}
public List getFundingAddresses() {
- if (!walletsManager.areWalletsAvailable())
- throw new IllegalStateException("wallet is not yet available");
-
+ verifyWalletsAreAvailable();
verifyEncryptedWalletIsUnlocked();
// Create a new funding address if none exists.
@@ -140,8 +136,7 @@ public int getNumConfirmationsForMostRecentTransaction(String addressString) {
}
public void setWalletPassword(String password, String newPassword) {
- if (!walletsManager.areWalletsAvailable())
- throw new IllegalStateException("wallet is not yet available");
+ verifyWalletsAreAvailable();
KeyCrypterScrypt keyCrypterScrypt = getKeyCrypterScrypt();
@@ -230,6 +225,12 @@ public void removeWalletPassword(String password) {
walletsManager.backupWallets();
}
+ // Throws a RuntimeException if wallets are not available (encrypted or not).
+ private void verifyWalletsAreAvailable() {
+ if (!walletsManager.areWalletsAvailable())
+ throw new IllegalStateException("wallet is not yet available");
+ }
+
// Throws a RuntimeException if wallets are not available or not encrypted.
private void verifyWalletIsAvailableAndEncrypted() {
if (!walletsManager.areWalletsAvailable())
From 69792098c63093bf80926dbcf2355fecfdfa4b5a Mon Sep 17 00:00:00 2001
From: ghubstan <36207203+ghubstan@users.noreply.github.com>
Date: Tue, 23 Jun 2020 15:29:32 -0300
Subject: [PATCH 19/21] Check param count only, not param order correctness
This change simplifies client 'getoffers' method parameter
validation. It no longer assumes parameter ordering is correct, nor
validates the direction parameter value. The client only verifies
the correct number of string parameters are present.
---
cli/src/main/java/bisq/cli/CliMain.java | 10 ++--------
cli/test.sh | 9 +--------
2 files changed, 3 insertions(+), 16 deletions(-)
diff --git a/cli/src/main/java/bisq/cli/CliMain.java b/cli/src/main/java/bisq/cli/CliMain.java
index 074af31d19b..37dbe3f52d6 100644
--- a/cli/src/main/java/bisq/cli/CliMain.java
+++ b/cli/src/main/java/bisq/cli/CliMain.java
@@ -181,16 +181,10 @@ public static void run(String[] args) {
return;
}
case getoffers: {
- if (nonOptionArgs.size() < 2)
- throw new IllegalArgumentException("no buy/sell direction specified");
-
- var direction = nonOptionArgs.get(1);
- if (!direction.equalsIgnoreCase("BUY") && !direction.equalsIgnoreCase("SELL"))
- throw new IllegalArgumentException("no buy/sell direction specified");
-
if (nonOptionArgs.size() < 3)
- throw new IllegalArgumentException("no fiat currency specified");
+ throw new IllegalArgumentException("incorrect parameter count, expecting direction (buy|sell), currency code");
+ var direction = nonOptionArgs.get(1);
var fiatCurrency = nonOptionArgs.get(2);
var request = GetOffersRequest.newBuilder()
diff --git a/cli/test.sh b/cli/test.sh
index d21ab501d55..54af77c717b 100755
--- a/cli/test.sh
+++ b/cli/test.sh
@@ -187,14 +187,7 @@
run ./bisq-cli --password=xyz getoffers
[ "$status" -eq 1 ]
echo "actual output: $output" >&2
- [ "$output" = "Error: no buy/sell direction specified" ]
-}
-
-@test "test getoffers missing ccy argument" {
- run ./bisq-cli --password=xyz getoffers buy
- [ "$status" -eq 1 ]
- echo "actual output: $output" >&2
- [ "$output" = "Error: no fiat currency specified" ]
+ [ "$output" = "Error: incorrect parameter count, expecting direction (buy|sell), currency code" ]
}
@test "test getoffers buy eur check return status" {
From 51d82b1dff4764bf136a6a9049ebdd054ad63095 Mon Sep 17 00:00:00 2001
From: ghubstan <36207203+ghubstan@users.noreply.github.com>
Date: Tue, 23 Jun 2020 17:15:39 -0300
Subject: [PATCH 20/21] Fix offer list filter bug due to direction flip
Respect the direction parmeter; do not give it meaning it does not
have. If the user passes a 'buy' parameter, return buy offers. Do
not misinterpret the param's intent. The direction parameter's value
does not imply "buy=I'm a buyer, show me sell offers" or
"sell=I'm a seller, show me buy offers".
I got mixed up by looking at the UI. If I want to sell BTC, I click
the SELL tab to view buy offers (maker as buyer). If I want to buy
BTC, I click the BUY tab to view sell offers (maker as seller).
This change also fixes an offer list sorting bug.
The commit is in response to a requested changes in PR 4329:
https://github.com/bisq-network/bisq/pull/4329#pullrequestreview-436033502
---
cli/src/main/java/bisq/cli/TableFormat.java | 2 +-
.../java/bisq/core/grpc/CoreOffersService.java | 15 ++++++++++-----
2 files changed, 11 insertions(+), 6 deletions(-)
diff --git a/cli/src/main/java/bisq/cli/TableFormat.java b/cli/src/main/java/bisq/cli/TableFormat.java
index bb1ef4e3b2a..ca74f057236 100644
--- a/cli/src/main/java/bisq/cli/TableFormat.java
+++ b/cli/src/main/java/bisq/cli/TableFormat.java
@@ -92,7 +92,7 @@ static String formatOfferTable(List offerInfo, String fiatCurrency) {
return headerLine
+ offerInfo.stream()
.map(o -> format(colDataFormat,
- o.getDirection().equals("BUY") ? "SELL" : "BUY",
+ o.getDirection(),
formatOfferPrice(o.getPrice()),
formatAmountRange(o.getMinAmount(), o.getAmount()),
formatVolumeRange(o.getMinVolume(), o.getVolume()),
diff --git a/core/src/main/java/bisq/core/grpc/CoreOffersService.java b/core/src/main/java/bisq/core/grpc/CoreOffersService.java
index 3ac365bd888..0325bcf4ff3 100644
--- a/core/src/main/java/bisq/core/grpc/CoreOffersService.java
+++ b/core/src/main/java/bisq/core/grpc/CoreOffersService.java
@@ -60,14 +60,19 @@ public CoreOffersService(CreateOfferService createOfferService,
public List getOffers(String direction, String fiatCurrencyCode) {
List offers = offerBookService.getOffers().stream()
- .filter(o -> !o.getDirection().name().equalsIgnoreCase(direction)
- && o.getOfferPayload().getCounterCurrencyCode().equalsIgnoreCase(fiatCurrencyCode))
+ .filter(o -> {
+ var offerOfWantedDirection = o.getDirection().name().equalsIgnoreCase(direction);
+ var offerInWantedCurrency = o.getOfferPayload().getCounterCurrencyCode().equalsIgnoreCase(fiatCurrencyCode);
+ return offerOfWantedDirection && offerInWantedCurrency;
+ })
.collect(Collectors.toList());
- if (direction.equals(BUY.name()))
- offers.sort(Comparator.comparing(Offer::getPrice));
- else
+ // A buyer probably wants to see sell orders in price ascending order.
+ // A seller probably wants to see buy orders in price descending order.
+ if (direction.equalsIgnoreCase(BUY.name()))
offers.sort(Comparator.comparing(Offer::getPrice).reversed());
+ else
+ offers.sort(Comparator.comparing(Offer::getPrice));
return offers;
}
From f820897e5bc45dbb8bb824f84f024d3f4fcb5ad0 Mon Sep 17 00:00:00 2001
From: ghubstan <36207203+ghubstan@users.noreply.github.com>
Date: Wed, 24 Jun 2020 13:27:43 -0300
Subject: [PATCH 21/21] Format dates ISO 8601 in UTC
We display all dates in UTC, using the "yyyy-MM-dd'T'HH:mm:ss'Z'" format.
---
cli/src/main/java/bisq/cli/TableFormat.java | 35 ++++++---------------
1 file changed, 10 insertions(+), 25 deletions(-)
diff --git a/cli/src/main/java/bisq/cli/TableFormat.java b/cli/src/main/java/bisq/cli/TableFormat.java
index ca74f057236..84d0f5820de 100644
--- a/cli/src/main/java/bisq/cli/TableFormat.java
+++ b/cli/src/main/java/bisq/cli/TableFormat.java
@@ -5,11 +5,11 @@
import protobuf.PaymentAccount;
-import java.text.DateFormat;
+import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
-import java.util.Locale;
+import java.util.TimeZone;
import java.util.stream.Collectors;
import static bisq.cli.CurrencyFormat.formatAmountRange;
@@ -19,15 +19,15 @@
import static com.google.common.base.Strings.padEnd;
import static com.google.common.base.Strings.padStart;
import static java.lang.String.format;
-import static java.text.DateFormat.DEFAULT;
-import static java.text.DateFormat.getDateInstance;
-import static java.text.DateFormat.getTimeInstance;
import static java.util.Collections.max;
import static java.util.Comparator.comparing;
import static java.util.TimeZone.getTimeZone;
class TableFormat {
+ private static final TimeZone TZ_UTC = getTimeZone("UTC");
+ private static final SimpleDateFormat DATE_FORMAT_ISO_8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+
// For inserting 2 spaces between column headers.
private static final String COL_HEADER_DELIMITER = " ";
@@ -39,7 +39,7 @@ class TableFormat {
private static final String COL_HEADER_AMOUNT = padEnd("BTC(min - max)", 24, ' ');
private static final String COL_HEADER_BALANCE = padStart("Balance", 12, ' ');
private static final String COL_HEADER_CONFIRMATIONS = "Confirmations";
- private static final String COL_HEADER_CREATION_DATE = padEnd("Creation Date", 24, ' ');
+ private static final String COL_HEADER_CREATION_DATE = padEnd("Creation Date (UTC)", 20, ' ');
private static final String COL_HEADER_CURRENCY = "Currency";
private static final String COL_HEADER_DIRECTION = "Buy/Sell"; // TODO "Take Offer to
private static final String COL_HEADER_NAME = "Name";
@@ -97,7 +97,7 @@ static String formatOfferTable(List offerInfo, String fiatCurrency) {
formatAmountRange(o.getMinAmount(), o.getAmount()),
formatVolumeRange(o.getMinVolume(), o.getVolume()),
o.getPaymentMethodShortName(),
- formatDateTime(o.getDate(), true),
+ formatTimestamp(o.getDate()),
o.getId()))
.collect(Collectors.joining("\n"));
}
@@ -137,23 +137,8 @@ private static int getLengthOfLongestColumn(int headerLength, List strin
return Math.max(longest, headerLength);
}
- private static String formatDateTime(long timestamp, boolean useLocaleAndLocalTimezone) {
- Date date = new Date(timestamp);
- Locale locale = useLocaleAndLocalTimezone ? Locale.getDefault() : Locale.US;
- DateFormat dateInstance = getDateInstance(DEFAULT, locale);
- DateFormat timeInstance = getTimeInstance(DEFAULT, locale);
- if (!useLocaleAndLocalTimezone) {
- dateInstance.setTimeZone(getTimeZone("UTC"));
- timeInstance.setTimeZone(getTimeZone("UTC"));
- }
- return formatDateTime(date, dateInstance, timeInstance);
- }
-
- private static String formatDateTime(Date date, DateFormat dateFormatter, DateFormat timeFormatter) {
- if (date != null) {
- return dateFormatter.format(date) + " " + timeFormatter.format(date);
- } else {
- return "";
- }
+ private static String formatTimestamp(long timestamp) {
+ DATE_FORMAT_ISO_8601.setTimeZone(TZ_UTC);
+ return DATE_FORMAT_ISO_8601.format(new Date(timestamp));
}
}