From a067ba12286b2db0ad6abd7c836102b48e8074df Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Thu, 14 Jan 2021 10:19:39 -0300 Subject: [PATCH 01/10] Add new CoreHelpService and method help docs Adds all the gRPC server boilerplate, and a simple help service that serves method help in man page format. Help text is maintained in text files located in core/src/main/resources/help. Only some of the method help text files are defined in this change, more to be added. --- core/src/main/java/bisq/core/api/CoreApi.java | 13 ++- .../java/bisq/core/api/CoreHelpService.java | 103 ++++++++++++++++++ .../main/resources/help/createoffer-help.txt | 64 +++++++++++ .../help/getfundingaddresses-help.txt | 17 +++ .../resources/help/getpaymentaccts-help.txt | 17 +++ .../resources/help/getpaymentmethods-help.txt | 17 +++ .../main/resources/help/gettxfeerate-help.txt | 17 +++ .../help/getunusedbsqaddress-help.txt | 17 +++ .../main/resources/help/getversion-help.txt | 17 +++ .../main/resources/help/lockwallet-help.txt | 17 +++ .../main/resources/help/takeoffer-help.txt | 35 ++++++ .../resources/help/unsettxfeerate-help.txt | 17 +++ .../bisq/daemon/grpc/GrpcHelpService.java | 56 ++++++++++ .../java/bisq/daemon/grpc/GrpcServer.java | 2 + proto/src/main/proto/grpc.proto | 17 +++ 15 files changed, 425 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/bisq/core/api/CoreHelpService.java create mode 100644 core/src/main/resources/help/createoffer-help.txt create mode 100644 core/src/main/resources/help/getfundingaddresses-help.txt create mode 100644 core/src/main/resources/help/getpaymentaccts-help.txt create mode 100644 core/src/main/resources/help/getpaymentmethods-help.txt create mode 100644 core/src/main/resources/help/gettxfeerate-help.txt create mode 100644 core/src/main/resources/help/getunusedbsqaddress-help.txt create mode 100644 core/src/main/resources/help/getversion-help.txt create mode 100644 core/src/main/resources/help/lockwallet-help.txt create mode 100644 core/src/main/resources/help/takeoffer-help.txt create mode 100644 core/src/main/resources/help/unsettxfeerate-help.txt create mode 100644 daemon/src/main/java/bisq/daemon/grpc/GrpcHelpService.java diff --git a/core/src/main/java/bisq/core/api/CoreApi.java b/core/src/main/java/bisq/core/api/CoreApi.java index bbd12b7ccbd..bfc814c67a6 100644 --- a/core/src/main/java/bisq/core/api/CoreApi.java +++ b/core/src/main/java/bisq/core/api/CoreApi.java @@ -62,6 +62,7 @@ public class CoreApi { @Getter private final Config config; private final CoreDisputeAgentsService coreDisputeAgentsService; + private final CoreHelpService coreHelpService; private final CoreOffersService coreOffersService; private final CorePaymentAccountsService paymentAccountsService; private final CorePriceService corePriceService; @@ -72,7 +73,7 @@ public class CoreApi { @Inject public CoreApi(Config config, CoreDisputeAgentsService coreDisputeAgentsService, - CoreOffersService coreOffersService, + CoreHelpService coreHelpService, CoreOffersService coreOffersService, CorePaymentAccountsService paymentAccountsService, CorePriceService corePriceService, CoreTradesService coreTradesService, @@ -80,6 +81,7 @@ public CoreApi(Config config, TradeStatisticsManager tradeStatisticsManager) { this.config = config; this.coreDisputeAgentsService = coreDisputeAgentsService; + this.coreHelpService = coreHelpService; this.coreOffersService = coreOffersService; this.paymentAccountsService = paymentAccountsService; this.coreTradesService = coreTradesService; @@ -101,6 +103,15 @@ public void registerDisputeAgent(String disputeAgentType, String registrationKey coreDisputeAgentsService.registerDisputeAgent(disputeAgentType, registrationKey); } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Help + /////////////////////////////////////////////////////////////////////////////////////////// + + public String getMethodHelp(String methodName) { + return coreHelpService.getMethodHelp(methodName); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Offers /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/api/CoreHelpService.java b/core/src/main/java/bisq/core/api/CoreHelpService.java new file mode 100644 index 00000000000..a294dcad627 --- /dev/null +++ b/core/src/main/java/bisq/core/api/CoreHelpService.java @@ -0,0 +1,103 @@ +/* + * 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.api; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +import lombok.extern.slf4j.Slf4j; + +import static java.io.File.separator; +import static java.lang.String.format; +import static java.lang.System.out; + +@Singleton +@Slf4j +class CoreHelpService { + + @Inject + public CoreHelpService() { + } + + public String getMethodHelp(String methodName) { + switch (methodName) { + case "createoffer": + case "getfundingaddresses": + case "getpaymentaccts": + case "getpaymentmethods": + case "gettxfeerate": + case "getunusedbsqaddress": + case "getversion": + case "lockwallet": + case "takeoffer": + case "unsettxfeerate": + return getHelpText(methodName); + default: + throw new IllegalStateException("no help found for " + methodName); + } + } + + private String getHelpText(String methodName) { + String resourceFile = "/help" + separator + methodName + "-" + "help.txt"; + try { + return readHelpFile(resourceFile); + } catch (NullPointerException ex) { + log.error("", ex); + throw new IllegalStateException(format("could not find %s help doc", methodName)); + } catch (IOException ex) { + log.error("", ex); + throw new IllegalStateException(format("could not read %s help doc", methodName)); + } + } + + private String readHelpFile(String resourceFile) throws NullPointerException, IOException { + // The deployed text file is in the core.jar file, so use + // Class.getResourceAsStream to read it. + InputStream is = getClass().getResourceAsStream(resourceFile); + BufferedReader br = new BufferedReader(new InputStreamReader(is)); + String line; + StringBuilder builder = new StringBuilder(); + while ((line = br.readLine()) != null) + builder.append(line).append("\n"); + + return builder.toString(); + } + + // Main method for devs to view help text without running the server. + @SuppressWarnings("CommentedOutCode") + public static void main(String[] args) { + CoreHelpService coreHelpService = new CoreHelpService(); + // out.println(coreHelpService.getMethodHelp("getversion")); + // out.println(coreHelpService.getMethodHelp("getfundingaddresses")); + // out.println(coreHelpService.getMethodHelp("getfundingaddresses")); + // out.println(coreHelpService.getMethodHelp("getunusedbsqaddress")); + // out.println(coreHelpService.getMethodHelp("unsettxfeerate")); + // out.println(coreHelpService.getMethodHelp("getpaymentmethods")); + // out.println(coreHelpService.getMethodHelp("getpaymentaccts")); + // out.println(coreHelpService.getMethodHelp("lockwallet")); + // out.println(coreHelpService.getMethodHelp("gettxfeerate")); + out.println(coreHelpService.getMethodHelp("createoffer")); + // out.println(coreHelpService.getMethodHelp("takeoffer")); + // out.println(coreHelpService.getMethodHelp("garbage")); + } +} diff --git a/core/src/main/resources/help/createoffer-help.txt b/core/src/main/resources/help/createoffer-help.txt new file mode 100644 index 00000000000..536e2596b65 --- /dev/null +++ b/core/src/main/resources/help/createoffer-help.txt @@ -0,0 +1,64 @@ +createoffer + +NAME +---- +createoffer - create offer to buy or sell BTC + +SYNOPSIS +-------- +createoffer + --payment-account= + --direction= + --currency-code= + --market-price-margin= | --fixed-price= + --amount= + --min-amount= + --security-deposit= + [--fee-currency=] + +DESCRIPTION +----------- +Create and place an offer to buy or sell BTC using a fiat account. + +OPTIONS +------- +--payment-account + The ID of the fiat payment account used to send or receive funds during the trade. + +--direction + The direction of the trade (BUY or SELL). + +--currency-code + The three letter code for the fiat used to buy or sell BTC, e.g., EUR, USD, BRL, ... + +--market-price-margin + The % above or below market BTC price, e.g., 1.00 (1%). + If --market-price-margin is not present, --fixed-price must be. + +--fixed-price + The fixed BTC price in fiat used to buy or sell BTC, e.g., 34000 (USD). + If --fixed-price is not present, --market-price-margin must be. + +--amount + The amount of BTC to buy or sell, e.g., 0.125. + +--min-amount + The minimum amount of BTC to buy or sell, e.g., 0.006. + If --min-amount is not present, it defaults to the --amount value. + +--security-deposit + The percentage of the BTC amount being traded for the security deposit, e.g., 60.0 (60%). + +--fee-currency + The wallet currency used to pay the Bisq trade maker fee (BSQ|BTC). Default is BTC + +EXAMPLES +-------- +To create a BUY 0.125 BTC with EUR offer + at the current market price, + using a payment account with ID 7413d263-225a-4f1b-837a-1e3094dc0d77, + putting up a 30 percent security deposit, + and paying the Bisq maker trading fee in BSQ: +$ ./bisq-cli --password=xyz --port=9998 createoffer --payment-account=7413d263-225a-4f1b-837a-1e3094dc0d77 --direction=buy --currency-code=eur --amount=0.125 --market-price-margin=0.00 --security-deposit=30.0 --fee-currency=bsq + + (TODO another 3 examples: selling @ mkt price, buying a fixed price, selling at fixed price...) diff --git a/core/src/main/resources/help/getfundingaddresses-help.txt b/core/src/main/resources/help/getfundingaddresses-help.txt new file mode 100644 index 00000000000..0d3e7ea6292 --- /dev/null +++ b/core/src/main/resources/help/getfundingaddresses-help.txt @@ -0,0 +1,17 @@ +getfundingaddresses + +NAME +---- +getfundingaddresses - list BTC receiving address + +SYNOPSIS +-------- +getfundingaddresses + +DESCRIPTION +----------- +Returns a list of receiving BTC addresses. + +EXAMPLES +-------- +$ ./bisq-cli --password=xyz --port=9998 getfundingaddresses diff --git a/core/src/main/resources/help/getpaymentaccts-help.txt b/core/src/main/resources/help/getpaymentaccts-help.txt new file mode 100644 index 00000000000..f12b5bd4534 --- /dev/null +++ b/core/src/main/resources/help/getpaymentaccts-help.txt @@ -0,0 +1,17 @@ +getpaymentaccts + +NAME +---- +getpaymentaccts - list user payment accounts + +SYNOPSIS +-------- +getpaymentaccts + +DESCRIPTION +----------- +Returns the list of user payment accounts. + +EXAMPLES +-------- +$ ./bisq-cli --password=xyz --port=9998 getpaymentaccts diff --git a/core/src/main/resources/help/getpaymentmethods-help.txt b/core/src/main/resources/help/getpaymentmethods-help.txt new file mode 100644 index 00000000000..b7f860548df --- /dev/null +++ b/core/src/main/resources/help/getpaymentmethods-help.txt @@ -0,0 +1,17 @@ +getpaymentmethods + +NAME +---- +getpaymentmethods - list fiat payment methods + +SYNOPSIS +-------- +getpaymentmethods + +DESCRIPTION +----------- +Returns a list of currently supported fiat payment method IDs. + +EXAMPLES +-------- +$ ./bisq-cli --password=xyz --port=9998 getpaymentmethods diff --git a/core/src/main/resources/help/gettxfeerate-help.txt b/core/src/main/resources/help/gettxfeerate-help.txt new file mode 100644 index 00000000000..3582d5dcf09 --- /dev/null +++ b/core/src/main/resources/help/gettxfeerate-help.txt @@ -0,0 +1,17 @@ +gettxfeerate + +NAME +---- +gettxfeerate - get transaction fee rate + +SYNOPSIS +-------- +gettxfeerate + +DESCRIPTION +----------- +Returns the most recent bitcoin network transaction fee the Bisq server could find. + +EXAMPLES +-------- +$ ./bisq-cli --password=xyz --port=9998 gettxfeerate diff --git a/core/src/main/resources/help/getunusedbsqaddress-help.txt b/core/src/main/resources/help/getunusedbsqaddress-help.txt new file mode 100644 index 00000000000..308ad01ba7b --- /dev/null +++ b/core/src/main/resources/help/getunusedbsqaddress-help.txt @@ -0,0 +1,17 @@ +getunusedbsqaddress + +NAME +---- +getunusedbsqaddress - get BSQ receiving address + +SYNOPSIS +-------- +getunusedbsqaddress + +DESCRIPTION +----------- +Returns an unused BSQ receiving address. + +EXAMPLES +-------- +$ ./bisq-cli --password=xyz --port=9998 getunusedbsqaddress diff --git a/core/src/main/resources/help/getversion-help.txt b/core/src/main/resources/help/getversion-help.txt new file mode 100644 index 00000000000..ce3b801db4b --- /dev/null +++ b/core/src/main/resources/help/getversion-help.txt @@ -0,0 +1,17 @@ +getversion + +NAME +---- +getversion - get server version + +SYNOPSIS +-------- +getversion + +DESCRIPTION +----------- +Returns the Bisq server version. + +EXAMPLES +-------- +$ ./bisq-cli --password=xyz --port=9998 getversion diff --git a/core/src/main/resources/help/lockwallet-help.txt b/core/src/main/resources/help/lockwallet-help.txt new file mode 100644 index 00000000000..6639dcef5ea --- /dev/null +++ b/core/src/main/resources/help/lockwallet-help.txt @@ -0,0 +1,17 @@ +lockwallet + +NAME +---- +lockwallet - lock Bisq wallet + +SYNOPSIS +-------- +lockwallet + +DESCRIPTION +----------- +Locks an unlocked wallet before an unlockwallet timeout expires. + +EXAMPLES +-------- +$ ./bisq-cli --password=xyz --port=9998 lockwallet diff --git a/core/src/main/resources/help/takeoffer-help.txt b/core/src/main/resources/help/takeoffer-help.txt new file mode 100644 index 00000000000..89ab21fc9c0 --- /dev/null +++ b/core/src/main/resources/help/takeoffer-help.txt @@ -0,0 +1,35 @@ +takeoffer + +NAME +---- +takeoffer - take an offer to buy or sell BTC + +SYNOPSIS +-------- +takeoffer + --offer-id= + --payment-account= + --fee-currency= + +DESCRIPTION +----------- +Take an existing offer using a matching payment method. The Bisq trade fee can be paid in BSQ or BTC. + +OPTIONS +------- +--offer-id + The ID of the buy or sell offer to take. + +--payment-account + The ID of the fiat payment account used to send or receive funds during the trade. + The payment account's payment method must match that of the offer. + +--fee-currency + The wallet currency used to pay the Bisq trade taker fee (BSQ|BTC). Default is BTC + +EXAMPLES +-------- +To take an offer with ID 83e8b2e2-51b6-4f39-a748-3ebd29c22aea + using a payment account with ID fe20cdbd-22be-4b8a-a4b6-d2608ff09d6e, + and paying the Bisq trading fee in BSQ: +$ ./bisq-cli --password=xyz --port=9998 takeoffer -offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea -payment-account=fe20cdbd-22be-4b8a-a4b6-d2608ff09d6e -fee-currency=bsq diff --git a/core/src/main/resources/help/unsettxfeerate-help.txt b/core/src/main/resources/help/unsettxfeerate-help.txt new file mode 100644 index 00000000000..36767e6f1f0 --- /dev/null +++ b/core/src/main/resources/help/unsettxfeerate-help.txt @@ -0,0 +1,17 @@ +unsettxfeerate + +NAME +---- +unsettxfeerate - unset transaction fee rate preference + +SYNOPSIS +-------- +unsettxfeerate + +DESCRIPTION +----------- +Unsets (removes) the transaction fee rate user preference. + +EXAMPLES +-------- +$ ./bisq-cli --password=xyz --port=9998 unsettxfeerate diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcHelpService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcHelpService.java new file mode 100644 index 00000000000..1a62ed6f9f6 --- /dev/null +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcHelpService.java @@ -0,0 +1,56 @@ +/* + * 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.daemon.grpc; + +import bisq.core.api.CoreApi; + +import bisq.proto.grpc.GetMethodHelpReply; +import bisq.proto.grpc.GetMethodHelpRequest; +import bisq.proto.grpc.HelpGrpc; + +import io.grpc.stub.StreamObserver; + +import javax.inject.Inject; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +class GrpcHelpService extends HelpGrpc.HelpImplBase { + + private final CoreApi coreApi; + private final GrpcExceptionHandler exceptionHandler; + + @Inject + public GrpcHelpService(CoreApi coreApi, GrpcExceptionHandler exceptionHandler) { + this.coreApi = coreApi; + this.exceptionHandler = exceptionHandler; + } + + @Override + public void getMethodHelp(GetMethodHelpRequest req, + StreamObserver responseObserver) { + try { + String helpText = coreApi.getMethodHelp(req.getMethodName()); + var reply = GetMethodHelpReply.newBuilder().setMethodHelp(helpText).build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } catch (Throwable cause) { + exceptionHandler.handleException(cause, responseObserver); + } + } +} diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcServer.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcServer.java index 589645d77dd..8e50a98aa1f 100644 --- a/daemon/src/main/java/bisq/daemon/grpc/GrpcServer.java +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcServer.java @@ -50,6 +50,7 @@ public GrpcServer(CoreContext coreContext, Config config, PasswordAuthInterceptor passwordAuthInterceptor, GrpcDisputeAgentsService disputeAgentsService, + GrpcHelpService helpService, GrpcOffersService offersService, GrpcPaymentAccountsService paymentAccountsService, GrpcPriceService priceService, @@ -60,6 +61,7 @@ public GrpcServer(CoreContext coreContext, this.server = ServerBuilder.forPort(config.apiPort) .executor(UserThread.getExecutor()) .addService(disputeAgentsService) + .addService(helpService) .addService(offersService) .addService(paymentAccountsService) .addService(priceService) diff --git a/proto/src/main/proto/grpc.proto b/proto/src/main/proto/grpc.proto index 3ac2ea4bf61..085c3f90741 100644 --- a/proto/src/main/proto/grpc.proto +++ b/proto/src/main/proto/grpc.proto @@ -40,6 +40,23 @@ message RegisterDisputeAgentRequest { message RegisterDisputeAgentReply { } +/////////////////////////////////////////////////////////////////////////////////////////// +// Help +/////////////////////////////////////////////////////////////////////////////////////////// + +service Help { + rpc GetMethodHelp (GetMethodHelpRequest) returns (GetMethodHelpReply) { + } +} + +message GetMethodHelpRequest { + string methodName = 1; +} + +message GetMethodHelpReply { + string methodHelp = 1; +} + /////////////////////////////////////////////////////////////////////////////////////////// // Offers /////////////////////////////////////////////////////////////////////////////////////////// From 49a3b46960df6de01d6d94d026d7fde692823d15 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Thu, 14 Jan 2021 10:22:14 -0300 Subject: [PATCH 02/10] Add CoreHelpService gRPC stubs and test case --- apitest/scripts/mainnet-test.sh | 26 ++++---- .../apitest/method/GetMethodHelpTest.java | 65 +++++++++++++++++++ .../java/bisq/apitest/method/MethodTest.java | 10 +++ .../bisq/apitest/scenario/StartupTest.java | 8 +++ cli/src/main/java/bisq/cli/GrpcStubs.java | 3 + 5 files changed, 99 insertions(+), 13 deletions(-) create mode 100644 apitest/src/test/java/bisq/apitest/method/GetMethodHelpTest.java diff --git a/apitest/scripts/mainnet-test.sh b/apitest/scripts/mainnet-test.sh index 48fe4023bf1..d643ee716e0 100755 --- a/apitest/scripts/mainnet-test.sh +++ b/apitest/scripts/mainnet-test.sh @@ -27,7 +27,7 @@ run ./bisq-cli --bogus getversion [ "$status" -eq 1 ] echo "actual output: $output" >&2 - [ "$output" = "Error: bogus is not a recognized option" ] + [ "$output" = "Error: missing required 'password' option" ] } @test "test missing required password option error" { @@ -61,7 +61,7 @@ } @test "test setwalletpassword \"a b c\"" { - run ./bisq-cli --password=xyz setwalletpassword "a b c" + run ./bisq-cli --password=xyz setwalletpassword --wallet-password="a b c" [ "$status" -eq 0 ] echo "actual output: $output" >&2 [ "$output" = "wallet encrypted" ] @@ -76,7 +76,7 @@ } @test "test unlockwallet without timeout arg" { - run ./bisq-cli --password=xyz unlockwallet "a b c" + run ./bisq-cli --password=xyz unlockwallet --wallet-password="a b c" [ "$status" -eq 1 ] echo "actual output: $output" >&2 [ "$output" = "Error: no unlock timeout specified" ] @@ -84,7 +84,7 @@ @test "test unlockwallet \"a b c\" 8" { - run ./bisq-cli --password=xyz unlockwallet "a b c" 8 + run ./bisq-cli --password=xyz unlockwallet --wallet-password="a b c" --timeout=8 [ "$status" -eq 0 ] echo "actual output: $output" >&2 [ "$output" = "wallet unlocked" ] @@ -97,7 +97,7 @@ } @test "test unlockwallet \"a b c\" 6" { - run ./bisq-cli --password=xyz unlockwallet "a b c" 6 + run ./bisq-cli --password=xyz unlockwallet --wallet-password="a b c" --timeout=6 [ "$status" -eq 0 ] echo "actual output: $output" >&2 [ "$output" = "wallet unlocked" ] @@ -111,14 +111,14 @@ } @test "test setwalletpassword incorrect old pwd error" { - run ./bisq-cli --password=xyz setwalletpassword "z z z" "d e f" + run ./bisq-cli --password=xyz setwalletpassword --wallet-password="z z z" --new-wallet-password="d e f" [ "$status" -eq 1 ] echo "actual output: $output" >&2 [ "$output" = "Error: incorrect old password" ] } @test "test setwalletpassword oldpwd newpwd" { - run ./bisq-cli --password=xyz setwalletpassword "a b c" "d e f" + run ./bisq-cli --password=xyz setwalletpassword --wallet-password="a b c" --new-wallet-password="d e f" [ "$status" -eq 0 ] echo "actual output: $output" >&2 [ "$output" = "wallet encrypted with new password" ] @@ -133,7 +133,7 @@ } @test "test removewalletpassword" { - run ./bisq-cli --password=xyz removewalletpassword "d e f" + run ./bisq-cli --password=xyz removewalletpassword --wallet-password="d e f" [ "$status" -eq 0 ] echo "actual output: $output" >&2 [ "$output" = "wallet decrypted" ] @@ -163,7 +163,7 @@ } @test "test getaddressbalance bogus address argument" { - run ./bisq-cli --password=xyz getaddressbalance bogus + run ./bisq-cli --password=xyz getaddressbalance --address=bogus [ "$status" -eq 1 ] echo "actual output: $output" >&2 [ "$output" = "Error: address bogus not found in wallet" ] @@ -183,21 +183,21 @@ run ./bisq-cli --password=xyz getoffers [ "$status" -eq 1 ] echo "actual output: $output" >&2 - [ "$output" = "Error: incorrect parameter count, expecting direction (buy|sell), currency code" ] + [ "$output" = "Error: no direction (buy|sell) specified" ] } @test "test getoffers sell eur check return status" { - run ./bisq-cli --password=xyz getoffers sell eur + run ./bisq-cli --password=xyz getoffers --direction=sell --currency-code=eur [ "$status" -eq 0 ] } @test "test getoffers buy eur check return status" { - run ./bisq-cli --password=xyz getoffers buy eur + run ./bisq-cli --password=xyz getoffers --direction=buy --currency-code=eur [ "$status" -eq 0 ] } @test "test getoffers sell gbp check return status" { - run ./bisq-cli --password=xyz getoffers sell gbp + run ./bisq-cli --password=xyz getoffers --direction=sell --currency-code=gbp [ "$status" -eq 0 ] } diff --git a/apitest/src/test/java/bisq/apitest/method/GetMethodHelpTest.java b/apitest/src/test/java/bisq/apitest/method/GetMethodHelpTest.java new file mode 100644 index 00000000000..99e7b4873f2 --- /dev/null +++ b/apitest/src/test/java/bisq/apitest/method/GetMethodHelpTest.java @@ -0,0 +1,65 @@ +/* + * 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.apitest.method; + +import bisq.proto.grpc.GetMethodHelpRequest; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +import static bisq.apitest.config.BisqAppConfig.alicedaemon; +import static bisq.cli.Method.createoffer; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation; + +@Disabled +@Slf4j +@TestMethodOrder(OrderAnnotation.class) +public class GetMethodHelpTest extends MethodTest { + + @BeforeAll + public static void setUp() { + try { + setUpScaffold(alicedaemon); + } catch (Exception ex) { + fail(ex); + } + } + + @Test + @Order(1) + public void testGetCreateOfferHelp() { + var help = grpcStubs(alicedaemon).helpService + .getMethodHelp(GetMethodHelpRequest.newBuilder() + .setMethodName(createoffer.name()).build()) + .getMethodHelp(); + assertNotNull(help); + } + + @AfterAll + public static void tearDown() { + tearDownScaffold(); + } +} diff --git a/apitest/src/test/java/bisq/apitest/method/MethodTest.java b/apitest/src/test/java/bisq/apitest/method/MethodTest.java index f4fd94b517a..26fa8c422cb 100644 --- a/apitest/src/test/java/bisq/apitest/method/MethodTest.java +++ b/apitest/src/test/java/bisq/apitest/method/MethodTest.java @@ -35,6 +35,7 @@ import bisq.proto.grpc.GetAddressBalanceRequest; import bisq.proto.grpc.GetBalancesRequest; import bisq.proto.grpc.GetFundingAddressesRequest; +import bisq.proto.grpc.GetMethodHelpRequest; import bisq.proto.grpc.GetMyOfferRequest; import bisq.proto.grpc.GetOfferRequest; import bisq.proto.grpc.GetPaymentAccountFormRequest; @@ -271,6 +272,10 @@ protected final WithdrawFundsRequest createWithdrawFundsRequest(String tradeId, .build(); } + protected final GetMethodHelpRequest createGetMethodHelpRequest(String methodName) { + return GetMethodHelpRequest.newBuilder().setMethodName(methodName).build(); + } + // Convenience methods for calling frequently used & thoroughly tested gRPC services. protected final BalancesInfo getBalances(BisqAppConfig bisqAppConfig, String currencyCode) { return grpcStubs(bisqAppConfig).walletsService.getBalances( @@ -490,6 +495,11 @@ public bisq.core.payment.PaymentAccount createDummyF2FAccount(BisqAppConfig bisq return f2FAccount; } + protected final String getMethodHelp(BisqAppConfig bisqAppConfig, String methodName) { + var req = createGetMethodHelpRequest(methodName); + return grpcStubs(bisqAppConfig).helpService.getMethodHelp(req).getMethodHelp(); + } + // Static conveniences for test methods and test case fixture setups. protected static RegisterDisputeAgentRequest createRegisterDisputeAgentRequest(String disputeAgentType) { diff --git a/apitest/src/test/java/bisq/apitest/scenario/StartupTest.java b/apitest/src/test/java/bisq/apitest/scenario/StartupTest.java index 5c017e8e4f8..3010a6568da 100644 --- a/apitest/src/test/java/bisq/apitest/scenario/StartupTest.java +++ b/apitest/src/test/java/bisq/apitest/scenario/StartupTest.java @@ -38,6 +38,7 @@ import bisq.apitest.method.CallRateMeteringInterceptorTest; +import bisq.apitest.method.GetMethodHelpTest; import bisq.apitest.method.GetVersionTest; import bisq.apitest.method.MethodTest; import bisq.apitest.method.RegisterDisputeAgentsTest; @@ -92,6 +93,13 @@ public void testRegisterDisputeAgents() { test.testRegisterRefundAgent(); } + @Test + @Order(4) + public void testGetCreateOfferHelp() { + GetMethodHelpTest test = new GetMethodHelpTest(); + test.testGetCreateOfferHelp(); + } + @AfterAll public static void tearDown() { tearDownScaffold(); diff --git a/cli/src/main/java/bisq/cli/GrpcStubs.java b/cli/src/main/java/bisq/cli/GrpcStubs.java index 2db33fcbaa9..2094eb743c4 100644 --- a/cli/src/main/java/bisq/cli/GrpcStubs.java +++ b/cli/src/main/java/bisq/cli/GrpcStubs.java @@ -19,6 +19,7 @@ import bisq.proto.grpc.DisputeAgentsGrpc; import bisq.proto.grpc.GetVersionGrpc; +import bisq.proto.grpc.HelpGrpc; import bisq.proto.grpc.OffersGrpc; import bisq.proto.grpc.PaymentAccountsGrpc; import bisq.proto.grpc.PriceGrpc; @@ -33,6 +34,7 @@ public class GrpcStubs { public final DisputeAgentsGrpc.DisputeAgentsBlockingStub disputeAgentsService; + public final HelpGrpc.HelpBlockingStub helpService; public final GetVersionGrpc.GetVersionBlockingStub versionService; public final OffersGrpc.OffersBlockingStub offersService; public final PaymentAccountsGrpc.PaymentAccountsBlockingStub paymentAccountsService; @@ -53,6 +55,7 @@ public GrpcStubs(String apiHost, int apiPort, String apiPassword) { })); this.disputeAgentsService = DisputeAgentsGrpc.newBlockingStub(channel).withCallCredentials(credentials); + this.helpService = HelpGrpc.newBlockingStub(channel).withCallCredentials(credentials); this.versionService = GetVersionGrpc.newBlockingStub(channel).withCallCredentials(credentials); this.offersService = OffersGrpc.newBlockingStub(channel).withCallCredentials(credentials); this.paymentAccountsService = PaymentAccountsGrpc.newBlockingStub(channel).withCallCredentials(credentials); From 37ad73d4f423f749b0fd03eea277e9422f84f083 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Thu, 14 Jan 2021 10:23:38 -0300 Subject: [PATCH 03/10] Add posix-style api method option parsers --- .../cli/opts/AbstractMethodOptionParser.java | 59 ++++++++ .../main/java/bisq/cli/opts/ArgumentList.java | 123 +++++++++++++++ .../cli/opts/CancelOfferOptionParser.java | 52 +++++++ .../cli/opts/CreateOfferOptionParser.java | 140 ++++++++++++++++++ .../opts/CreatePaymentAcctOptionParser.java | 63 ++++++++ .../opts/GetAddressBalanceOptionParser.java | 52 +++++++ .../bisq/cli/opts/GetBalanceOptionParser.java | 43 ++++++ .../bisq/cli/opts/GetOfferOptionParser.java | 52 +++++++ .../bisq/cli/opts/GetOffersOptionParser.java | 64 ++++++++ .../opts/GetPaymentAcctFormOptionParser.java | 53 +++++++ .../bisq/cli/opts/GetTradeOptionParser.java | 62 ++++++++ .../cli/opts/GetTransactionOptionParser.java | 52 +++++++ .../main/java/bisq/cli/opts/MethodOpts.java | 26 ++++ cli/src/main/java/bisq/cli/opts/OptLabel.java | 51 +++++++ .../RegisterDisputeAgentOptionParser.java | 64 ++++++++ .../RemoveWalletPasswordOptionParser.java | 52 +++++++ .../bisq/cli/opts/SendBsqOptionParser.java | 73 +++++++++ .../bisq/cli/opts/SendBtcOptionParser.java | 82 ++++++++++ .../cli/opts/SetTxFeeRateOptionParser.java | 53 +++++++ .../opts/SetWalletPasswordOptionParser.java | 61 ++++++++ .../cli/opts/SimpleMethodOptionParser.java | 30 ++++ .../bisq/cli/opts/TakeOfferOptionParser.java | 73 +++++++++ .../cli/opts/UnlockWalletOptionParser.java | 65 ++++++++ .../cli/opts/WithdrawFundsOptionParser.java | 70 +++++++++ 24 files changed, 1515 insertions(+) create mode 100644 cli/src/main/java/bisq/cli/opts/AbstractMethodOptionParser.java create mode 100644 cli/src/main/java/bisq/cli/opts/ArgumentList.java create mode 100644 cli/src/main/java/bisq/cli/opts/CancelOfferOptionParser.java create mode 100644 cli/src/main/java/bisq/cli/opts/CreateOfferOptionParser.java create mode 100644 cli/src/main/java/bisq/cli/opts/CreatePaymentAcctOptionParser.java create mode 100644 cli/src/main/java/bisq/cli/opts/GetAddressBalanceOptionParser.java create mode 100644 cli/src/main/java/bisq/cli/opts/GetBalanceOptionParser.java create mode 100644 cli/src/main/java/bisq/cli/opts/GetOfferOptionParser.java create mode 100644 cli/src/main/java/bisq/cli/opts/GetOffersOptionParser.java create mode 100644 cli/src/main/java/bisq/cli/opts/GetPaymentAcctFormOptionParser.java create mode 100644 cli/src/main/java/bisq/cli/opts/GetTradeOptionParser.java create mode 100644 cli/src/main/java/bisq/cli/opts/GetTransactionOptionParser.java create mode 100644 cli/src/main/java/bisq/cli/opts/MethodOpts.java create mode 100644 cli/src/main/java/bisq/cli/opts/OptLabel.java create mode 100644 cli/src/main/java/bisq/cli/opts/RegisterDisputeAgentOptionParser.java create mode 100644 cli/src/main/java/bisq/cli/opts/RemoveWalletPasswordOptionParser.java create mode 100644 cli/src/main/java/bisq/cli/opts/SendBsqOptionParser.java create mode 100644 cli/src/main/java/bisq/cli/opts/SendBtcOptionParser.java create mode 100644 cli/src/main/java/bisq/cli/opts/SetTxFeeRateOptionParser.java create mode 100644 cli/src/main/java/bisq/cli/opts/SetWalletPasswordOptionParser.java create mode 100644 cli/src/main/java/bisq/cli/opts/SimpleMethodOptionParser.java create mode 100644 cli/src/main/java/bisq/cli/opts/TakeOfferOptionParser.java create mode 100644 cli/src/main/java/bisq/cli/opts/UnlockWalletOptionParser.java create mode 100644 cli/src/main/java/bisq/cli/opts/WithdrawFundsOptionParser.java diff --git a/cli/src/main/java/bisq/cli/opts/AbstractMethodOptionParser.java b/cli/src/main/java/bisq/cli/opts/AbstractMethodOptionParser.java new file mode 100644 index 00000000000..4c495c06e5e --- /dev/null +++ b/cli/src/main/java/bisq/cli/opts/AbstractMethodOptionParser.java @@ -0,0 +1,59 @@ +/* + * 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.opts; + +import joptsimple.OptionParser; +import joptsimple.OptionSet; +import joptsimple.OptionSpec; + +import java.util.List; + +import lombok.Getter; + +import static bisq.cli.opts.OptLabel.OPT_HELP; + +abstract class AbstractMethodOptionParser implements MethodOpts { + + // The full command line args passed to CliMain.main(String[] args). + // CLI and Method level arguments are derived from args by an ArgumentList(args). + protected final String[] args; + + protected final OptionParser parser = new OptionParser(); + + // The help option for a specific api method, e.g., takeoffer -help. + protected final OptionSpec helpOpt = parser.accepts(OPT_HELP, "Print method help").forHelp(); + + @Getter + protected OptionSet options; + @Getter + protected List nonOptionArguments; + + protected AbstractMethodOptionParser(String[] args) { + this.args = args; + } + + public AbstractMethodOptionParser parse() { + options = parser.parse(new ArgumentList(args).getMethodArguments()); + nonOptionArguments = (List) options.nonOptionArguments(); + return this; + } + + public boolean isForHelp() { + return options.has(helpOpt); + } +} diff --git a/cli/src/main/java/bisq/cli/opts/ArgumentList.java b/cli/src/main/java/bisq/cli/opts/ArgumentList.java new file mode 100644 index 00000000000..3b52fb34a90 --- /dev/null +++ b/cli/src/main/java/bisq/cli/opts/ArgumentList.java @@ -0,0 +1,123 @@ +/* + * 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.opts; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; + +/** + * Wrapper for an array of command line arguments. + * + * Used to extract CLI connection and authentication arguments, or method arguments + * before parsing CLI or method opts + * + */ +public class ArgumentList { + + private final Predicate isCliOpt = (o) -> + o.startsWith("--password") || o.startsWith("-password") + || o.startsWith("--port") || o.startsWith("-port") + || o.startsWith("--host") || o.startsWith("-host"); + + + // The method name is the only positional opt in a command (easy to identify). + // If the positional argument does not match a Method, or there are more than one + // positional arguments, the joptsimple parser or CLI will fail as expected. + private final Predicate isMethodNameOpt = (o) -> !o.startsWith("-"); + + private final Predicate isHelpOpt = (o) -> o.startsWith("--help") || o.startsWith("-help"); + + private final String[] arguments; + private int currentIndex; + + public ArgumentList(String... arguments) { + this.arguments = arguments.clone(); + } + + /** + * Returns only the CLI connection & authentication, and method name args + * (--password, --host, --port, --help, method name) contained in the original + * String[] args; excludes the method specific arguments. + * + * If String[] args contains both a method name (the only positional opt) and a help + * argument (--help, -help), it is assumed the user wants method help, not CLI help, + * and the help argument is not included in the returned String[]. + */ + public String[] getCLIArguments() { + currentIndex = 0; + Optional methodNameArgument = Optional.empty(); + Optional helpArgument = Optional.empty(); + List prunedArguments = new ArrayList<>(); + + while (hasMore()) { + String arg = peek(); + if (isMethodNameOpt.test(arg)) { + methodNameArgument = Optional.of(arg); + prunedArguments.add(arg); + } + + if (isCliOpt.test(arg)) + prunedArguments.add(arg); + + if (isHelpOpt.test(arg)) + helpArgument = Optional.of(arg); + + next(); + } + + // Include the saved CLI help argument if the positional method name argument + // was not found. + if (!methodNameArgument.isPresent() && helpArgument.isPresent()) + prunedArguments.add(helpArgument.get()); + + return prunedArguments.toArray(new String[0]); + } + + /** + * Returns only the method args contained in the original String[] args; excludes the + * CLI connection & authentication opts (--password, --host, --port), plus the + * positional method name arg. + */ + public String[] getMethodArguments() { + List prunedArguments = new ArrayList<>(); + currentIndex = 0; + while (hasMore()) { + String arg = peek(); + if (!isCliOpt.test(arg) && !isMethodNameOpt.test(arg)) { + prunedArguments.add(arg); + } + next(); + } + return prunedArguments.toArray(new String[0]); + } + + + boolean hasMore() { + return currentIndex < arguments.length; + } + + String next() { + return arguments[currentIndex++]; + } + + String peek() { + return arguments[currentIndex]; + } +} diff --git a/cli/src/main/java/bisq/cli/opts/CancelOfferOptionParser.java b/cli/src/main/java/bisq/cli/opts/CancelOfferOptionParser.java new file mode 100644 index 00000000000..24ebc744211 --- /dev/null +++ b/cli/src/main/java/bisq/cli/opts/CancelOfferOptionParser.java @@ -0,0 +1,52 @@ +/* + * 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.opts; + + +import joptsimple.OptionSpec; + +import static bisq.cli.opts.OptLabel.OPT_OFFER_ID; +import static joptsimple.internal.Strings.EMPTY; + +public class CancelOfferOptionParser extends AbstractMethodOptionParser implements MethodOpts { + + final OptionSpec offerIdOpt = parser.accepts(OPT_OFFER_ID, "id of offer to cancel") + .withRequiredArg() + .defaultsTo(EMPTY); + + public CancelOfferOptionParser(String[] args) { + super(args); + } + + public CancelOfferOptionParser parse() { + super.parse(); + + // Short circuit opt validation if user just wants help. + if (options.has(helpOpt)) + return this; + + if (!options.has(offerIdOpt)) + throw new IllegalArgumentException("no offer id specified"); + + return this; + } + + public String getOfferId() { + return options.valueOf(offerIdOpt); + } +} diff --git a/cli/src/main/java/bisq/cli/opts/CreateOfferOptionParser.java b/cli/src/main/java/bisq/cli/opts/CreateOfferOptionParser.java new file mode 100644 index 00000000000..4cb6a24a4cc --- /dev/null +++ b/cli/src/main/java/bisq/cli/opts/CreateOfferOptionParser.java @@ -0,0 +1,140 @@ +/* + * 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.opts; + + +import joptsimple.OptionSpec; + +import java.math.BigDecimal; + +import static bisq.cli.opts.OptLabel.*; +import static joptsimple.internal.Strings.EMPTY; + +public class CreateOfferOptionParser extends AbstractMethodOptionParser implements MethodOpts { + + final OptionSpec paymentAccountIdOpt = parser.accepts(OPT_PAYMENT_ACCOUNT, + "id of payment account used for offer") + .withRequiredArg() + .defaultsTo(EMPTY); + + final OptionSpec directionOpt = parser.accepts(OPT_DIRECTION, "offer direction (buy|sell)") + .withRequiredArg() + .defaultsTo(EMPTY); + + final OptionSpec currencyCodeOpt = parser.accepts(OPT_CURRENCY_CODE, "currency code (eur|usd|...)") + .withRequiredArg() + .defaultsTo(EMPTY); + + final OptionSpec amountOpt = parser.accepts(OPT_AMOUNT, "amount of btc to buy or sell") + .withRequiredArg() + .defaultsTo(EMPTY); + + final OptionSpec minAmountOpt = parser.accepts(OPT_MIN_AMOUNT, "minimum amount of btc to buy or sell") + .withOptionalArg() + .defaultsTo(EMPTY); + + final OptionSpec mktPriceMarginOpt = parser.accepts(OPT_MKT_PRICE_MARGIN, "market btc price margin (%)") + .withOptionalArg() + .defaultsTo(EMPTY); + + final OptionSpec fixedPriceOpt = parser.accepts(OPT_FIXED_PRICE, "fixed btc price") + .withOptionalArg() + .defaultsTo(EMPTY); + + final OptionSpec securityDepositOpt = parser.accepts(OPT_SECURITY_DEPOSIT, "maker security deposit (%)") + .withRequiredArg() + .defaultsTo(EMPTY); + + final OptionSpec makerFeeCurrencyCodeOpt = parser.accepts(OPT_FEE_CURRENCY, "maker fee currency code (bsq|btc)") + .withOptionalArg() + .defaultsTo("btc"); + + public CreateOfferOptionParser(String[] args) { + super(args); + } + + public CreateOfferOptionParser parse() { + super.parse(); + + // Short circuit opt validation if user just wants help. + if (options.has(helpOpt)) + return this; + + if (!options.has(paymentAccountIdOpt)) + throw new IllegalArgumentException("no payment account id specified"); + + if (!options.has(directionOpt)) + throw new IllegalArgumentException("no direction (buy|sell) specified"); + + if (!options.has(amountOpt)) + throw new IllegalArgumentException("no btc amount specified"); + + if (!options.has(mktPriceMarginOpt) && !options.has(fixedPriceOpt)) + throw new IllegalArgumentException("no market price margin or fixed price specified"); + + if (!options.has(securityDepositOpt)) + throw new IllegalArgumentException("no security deposit specified"); + + return this; + } + + public String getPaymentAccountId() { + return options.valueOf(paymentAccountIdOpt); + } + + public String getDirection() { + return options.valueOf(directionOpt); + } + + public String getCurrencyCode() { + return options.valueOf(currencyCodeOpt); + } + + public String getAmount() { + return options.valueOf(amountOpt); + } + + public String getMinAmount() { + return options.has(minAmountOpt) ? options.valueOf(minAmountOpt) : getAmount(); + } + + public boolean isUsingMktPriceMargin() { + return options.has(mktPriceMarginOpt); + } + + @SuppressWarnings("unused") + public String getMktPriceMargin() { + return isUsingMktPriceMargin() ? options.valueOf(mktPriceMarginOpt) : ""; + } + + public BigDecimal getMktPriceMarginAsBigDecimal() { + return isUsingMktPriceMargin() ? new BigDecimal(options.valueOf(mktPriceMarginOpt)) : BigDecimal.ZERO; + } + + public String getFixedPrice() { + return options.has(fixedPriceOpt) ? options.valueOf(fixedPriceOpt) : ""; + } + + public String getSecurityDeposit() { + return options.valueOf(securityDepositOpt); + } + + public String getMakerFeeCurrencyCode() { + return options.has(makerFeeCurrencyCodeOpt) ? options.valueOf(makerFeeCurrencyCodeOpt) : "btc"; + } +} diff --git a/cli/src/main/java/bisq/cli/opts/CreatePaymentAcctOptionParser.java b/cli/src/main/java/bisq/cli/opts/CreatePaymentAcctOptionParser.java new file mode 100644 index 00000000000..21fb01bcec5 --- /dev/null +++ b/cli/src/main/java/bisq/cli/opts/CreatePaymentAcctOptionParser.java @@ -0,0 +1,63 @@ +/* + * 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.opts; + + +import joptsimple.OptionSpec; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import static bisq.cli.opts.OptLabel.OPT_PAYMENT_ACCOUNT_FORM; +import static java.lang.String.format; +import static joptsimple.internal.Strings.EMPTY; + +public class CreatePaymentAcctOptionParser extends AbstractMethodOptionParser implements MethodOpts { + + final OptionSpec paymentAcctFormPathOpt = parser.accepts(OPT_PAYMENT_ACCOUNT_FORM, + "path to json payment account form") + .withRequiredArg() + .defaultsTo(EMPTY); + + public CreatePaymentAcctOptionParser(String[] args) { + super(args); + } + + public CreatePaymentAcctOptionParser parse() { + super.parse(); + + // Short circuit opt validation if user just wants help. + if (options.has(helpOpt)) + return this; + + if (!options.has(paymentAcctFormPathOpt)) + throw new IllegalArgumentException("no path to json payment account form specified"); + + Path path = Paths.get(options.valueOf(paymentAcctFormPathOpt)); + if (!path.toFile().exists()) + throw new IllegalStateException( + format("json payment account form '%s' could not be found", + path.toString())); + + return this; + } + + public Path getPaymentAcctForm() { + return Paths.get(options.valueOf(paymentAcctFormPathOpt)); + } +} diff --git a/cli/src/main/java/bisq/cli/opts/GetAddressBalanceOptionParser.java b/cli/src/main/java/bisq/cli/opts/GetAddressBalanceOptionParser.java new file mode 100644 index 00000000000..80537ffc89d --- /dev/null +++ b/cli/src/main/java/bisq/cli/opts/GetAddressBalanceOptionParser.java @@ -0,0 +1,52 @@ +/* + * 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.opts; + + +import joptsimple.OptionSpec; + +import static bisq.cli.opts.OptLabel.OPT_ADDRESS; +import static joptsimple.internal.Strings.EMPTY; + +public class GetAddressBalanceOptionParser extends AbstractMethodOptionParser implements MethodOpts { + + final OptionSpec addressOpt = parser.accepts(OPT_ADDRESS, "wallet btc address") + .withRequiredArg() + .defaultsTo(EMPTY); + + public GetAddressBalanceOptionParser(String[] args) { + super(args); + } + + public GetAddressBalanceOptionParser parse() { + super.parse(); + + // Short circuit opt validation if user just wants help. + if (options.has(helpOpt)) + return this; + + if (!options.has(addressOpt)) + throw new IllegalArgumentException("no address specified"); + + return this; + } + + public String getAddress() { + return options.valueOf(addressOpt); + } +} diff --git a/cli/src/main/java/bisq/cli/opts/GetBalanceOptionParser.java b/cli/src/main/java/bisq/cli/opts/GetBalanceOptionParser.java new file mode 100644 index 00000000000..206e590c3d2 --- /dev/null +++ b/cli/src/main/java/bisq/cli/opts/GetBalanceOptionParser.java @@ -0,0 +1,43 @@ +/* + * 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.opts; + + +import joptsimple.OptionSpec; + +import static bisq.cli.opts.OptLabel.OPT_CURRENCY_CODE; +import static joptsimple.internal.Strings.EMPTY; + +public class GetBalanceOptionParser extends AbstractMethodOptionParser implements MethodOpts { + + final OptionSpec currencyCodeOpt = parser.accepts(OPT_CURRENCY_CODE, "wallet currency code (bsq|btc)") + .withOptionalArg() + .defaultsTo(EMPTY); + + public GetBalanceOptionParser(String[] args) { + super(args); + } + + public GetBalanceOptionParser parse() { + return (GetBalanceOptionParser) super.parse(); + } + + public String getCurrencyCode() { + return options.has(currencyCodeOpt) ? options.valueOf(currencyCodeOpt) : ""; + } +} diff --git a/cli/src/main/java/bisq/cli/opts/GetOfferOptionParser.java b/cli/src/main/java/bisq/cli/opts/GetOfferOptionParser.java new file mode 100644 index 00000000000..600e7672c45 --- /dev/null +++ b/cli/src/main/java/bisq/cli/opts/GetOfferOptionParser.java @@ -0,0 +1,52 @@ +/* + * 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.opts; + + +import joptsimple.OptionSpec; + +import static bisq.cli.opts.OptLabel.OPT_OFFER_ID; +import static joptsimple.internal.Strings.EMPTY; + +public class GetOfferOptionParser extends AbstractMethodOptionParser implements MethodOpts { + + final OptionSpec offerIdOpt = parser.accepts(OPT_OFFER_ID, "id of offer to get") + .withRequiredArg() + .defaultsTo(EMPTY); + + public GetOfferOptionParser(String[] args) { + super(args); + } + + public GetOfferOptionParser parse() { + super.parse(); + + // Short circuit opt validation if user just wants help. + if (options.has(helpOpt)) + return this; + + if (!options.has(offerIdOpt)) + throw new IllegalArgumentException("no offer id specified"); + + return this; + } + + public String getOfferId() { + return options.valueOf(offerIdOpt); + } +} diff --git a/cli/src/main/java/bisq/cli/opts/GetOffersOptionParser.java b/cli/src/main/java/bisq/cli/opts/GetOffersOptionParser.java new file mode 100644 index 00000000000..29360886f87 --- /dev/null +++ b/cli/src/main/java/bisq/cli/opts/GetOffersOptionParser.java @@ -0,0 +1,64 @@ +/* + * 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.opts; + + +import joptsimple.OptionSpec; + +import static bisq.cli.opts.OptLabel.OPT_CURRENCY_CODE; +import static bisq.cli.opts.OptLabel.OPT_DIRECTION; +import static joptsimple.internal.Strings.EMPTY; + +public class GetOffersOptionParser extends AbstractMethodOptionParser implements MethodOpts { + + final OptionSpec directionOpt = parser.accepts(OPT_DIRECTION, "offer direction (buy|sell)") + .withRequiredArg() + .defaultsTo(EMPTY); + + final OptionSpec currencyCodeOpt = parser.accepts(OPT_CURRENCY_CODE, "currency code (eur|usd|...)") + .withRequiredArg() + .defaultsTo(EMPTY); + + public GetOffersOptionParser(String[] args) { + super(args); + } + + public GetOffersOptionParser parse() { + super.parse(); + + // Short circuit opt validation if user just wants help. + if (options.has(helpOpt)) + return this; + + if (!options.has(directionOpt)) + throw new IllegalArgumentException("no direction (buy|sell) specified"); + + if (!options.has(currencyCodeOpt)) + throw new IllegalArgumentException("no currency code specified"); + + return this; + } + + public String getDirection() { + return options.valueOf(directionOpt); + } + + public String getCurrencyCode() { + return options.valueOf(currencyCodeOpt); + } +} diff --git a/cli/src/main/java/bisq/cli/opts/GetPaymentAcctFormOptionParser.java b/cli/src/main/java/bisq/cli/opts/GetPaymentAcctFormOptionParser.java new file mode 100644 index 00000000000..ef5bd5b454c --- /dev/null +++ b/cli/src/main/java/bisq/cli/opts/GetPaymentAcctFormOptionParser.java @@ -0,0 +1,53 @@ +/* + * 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.opts; + + +import joptsimple.OptionSpec; + +import static bisq.cli.opts.OptLabel.OPT_PAYMENT_METHOD_ID; +import static joptsimple.internal.Strings.EMPTY; + +public class GetPaymentAcctFormOptionParser extends AbstractMethodOptionParser implements MethodOpts { + + final OptionSpec paymentMethodIdOpt = parser.accepts(OPT_PAYMENT_METHOD_ID, + "id of payment method type used by a payment account") + .withRequiredArg() + .defaultsTo(EMPTY); + + public GetPaymentAcctFormOptionParser(String[] args) { + super(args); + } + + public GetPaymentAcctFormOptionParser parse() { + super.parse(); + + // Short circuit opt validation if user just wants help. + if (options.has(helpOpt)) + return this; + + if (!options.has(paymentMethodIdOpt)) + throw new IllegalArgumentException("no payment method id specified"); + + return this; + } + + public String getPaymentMethodId() { + return options.valueOf(paymentMethodIdOpt); + } +} diff --git a/cli/src/main/java/bisq/cli/opts/GetTradeOptionParser.java b/cli/src/main/java/bisq/cli/opts/GetTradeOptionParser.java new file mode 100644 index 00000000000..d1edf181063 --- /dev/null +++ b/cli/src/main/java/bisq/cli/opts/GetTradeOptionParser.java @@ -0,0 +1,62 @@ +/* + * 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.opts; + + +import joptsimple.OptionSpec; + +import static bisq.cli.opts.OptLabel.OPT_SHOW_CONTRACT; +import static bisq.cli.opts.OptLabel.OPT_TRADE_ID; +import static joptsimple.internal.Strings.EMPTY; + +public class GetTradeOptionParser extends AbstractMethodOptionParser implements MethodOpts { + + final OptionSpec tradeIdOpt = parser.accepts(OPT_TRADE_ID, "id of trade to get") + .withRequiredArg() + .defaultsTo(EMPTY); + + final OptionSpec showContractOpt = parser.accepts(OPT_SHOW_CONTRACT, "show trade's json contract") + .withOptionalArg() + .ofType(boolean.class) + .defaultsTo(Boolean.FALSE); + + public GetTradeOptionParser(String[] args) { + super(args); + } + + public GetTradeOptionParser parse() { + super.parse(); + + // Short circuit opt validation if user just wants help. + if (options.has(helpOpt)) + return this; + + if (!options.has(tradeIdOpt)) + throw new IllegalArgumentException("no trade id specified"); + + return this; + } + + public String getTradeId() { + return options.valueOf(tradeIdOpt); + } + + public boolean getShowContract() { + return options.has(showContractOpt) ? options.valueOf(showContractOpt) : false; + } +} diff --git a/cli/src/main/java/bisq/cli/opts/GetTransactionOptionParser.java b/cli/src/main/java/bisq/cli/opts/GetTransactionOptionParser.java new file mode 100644 index 00000000000..d4266eb9ff7 --- /dev/null +++ b/cli/src/main/java/bisq/cli/opts/GetTransactionOptionParser.java @@ -0,0 +1,52 @@ +/* + * 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.opts; + + +import joptsimple.OptionSpec; + +import static bisq.cli.opts.OptLabel.OPT_TRANSACTION_ID; +import static joptsimple.internal.Strings.EMPTY; + +public class GetTransactionOptionParser extends AbstractMethodOptionParser implements MethodOpts { + + final OptionSpec txIdOpt = parser.accepts(OPT_TRANSACTION_ID, "id of transaction") + .withRequiredArg() + .defaultsTo(EMPTY); + + public GetTransactionOptionParser(String[] args) { + super(args); + } + + public GetTransactionOptionParser parse() { + super.parse(); + + // Short circuit opt validation if user just wants help. + if (options.has(helpOpt)) + return this; + + if (!options.has(txIdOpt)) + throw new IllegalArgumentException("no tx id specified"); + + return this; + } + + public String getTxId() { + return options.valueOf(txIdOpt); + } +} diff --git a/cli/src/main/java/bisq/cli/opts/MethodOpts.java b/cli/src/main/java/bisq/cli/opts/MethodOpts.java new file mode 100644 index 00000000000..da639857522 --- /dev/null +++ b/cli/src/main/java/bisq/cli/opts/MethodOpts.java @@ -0,0 +1,26 @@ +/* + * 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.opts; + +public interface MethodOpts { + + MethodOpts parse(); + + boolean isForHelp(); + +} diff --git a/cli/src/main/java/bisq/cli/opts/OptLabel.java b/cli/src/main/java/bisq/cli/opts/OptLabel.java new file mode 100644 index 00000000000..5cbd068ce53 --- /dev/null +++ b/cli/src/main/java/bisq/cli/opts/OptLabel.java @@ -0,0 +1,51 @@ +/* + * 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.opts; + +/** + * CLI opt label definitions. + */ +public class OptLabel { + public final static String OPT_ADDRESS = "address"; + public final static String OPT_AMOUNT = "amount"; + public final static String OPT_CURRENCY_CODE = "currency-code"; + public final static String OPT_DIRECTION = "direction"; + public final static String OPT_DISPUTE_AGENT_TYPE = "dispute-agent-type"; + public final static String OPT_FEE_CURRENCY = "fee-currency"; + public final static String OPT_FIXED_PRICE = "fixed-price"; + public final static String OPT_HELP = "help"; + public final static String OPT_HOST = "host"; + public final static String OPT_MEMO = "memo"; + public final static String OPT_MKT_PRICE_MARGIN = "market-price-margin"; + public final static String OPT_MIN_AMOUNT = "min-amount"; + public final static String OPT_OFFER_ID = "offer-id"; + public final static String OPT_PASSWORD = "password"; + public final static String OPT_PAYMENT_ACCOUNT = "payment-account"; + public final static String OPT_PAYMENT_ACCOUNT_FORM = "payment-account-form"; + public final static String OPT_PAYMENT_METHOD_ID = "payment-method-id"; + public final static String OPT_PORT = "port"; + public final static String OPT_REGISTRATION_KEY = "registration-key"; + public final static String OPT_SECURITY_DEPOSIT = "security-deposit"; + public final static String OPT_SHOW_CONTRACT = "show-contract"; + public final static String OPT_TRADE_ID = "trade-id"; + public final static String OPT_TIMEOUT = "timeout"; + public final static String OPT_TRANSACTION_ID = "transaction-id"; + public final static String OPT_TX_FEE_RATE = "tx-fee-rate"; + public final static String OPT_WALLET_PASSWORD = "wallet-password"; + public final static String OPT_NEW_WALLET_PASSWORD = "new-wallet-password"; +} diff --git a/cli/src/main/java/bisq/cli/opts/RegisterDisputeAgentOptionParser.java b/cli/src/main/java/bisq/cli/opts/RegisterDisputeAgentOptionParser.java new file mode 100644 index 00000000000..428555a3493 --- /dev/null +++ b/cli/src/main/java/bisq/cli/opts/RegisterDisputeAgentOptionParser.java @@ -0,0 +1,64 @@ +/* + * 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.opts; + + +import joptsimple.OptionSpec; + +import static bisq.cli.opts.OptLabel.OPT_DISPUTE_AGENT_TYPE; +import static bisq.cli.opts.OptLabel.OPT_REGISTRATION_KEY; +import static joptsimple.internal.Strings.EMPTY; + +public class RegisterDisputeAgentOptionParser extends AbstractMethodOptionParser implements MethodOpts { + + final OptionSpec disputeAgentTypeOpt = parser.accepts(OPT_DISPUTE_AGENT_TYPE, "dispute agent type") + .withRequiredArg() + .defaultsTo(EMPTY); + + final OptionSpec registrationKeyOpt = parser.accepts(OPT_REGISTRATION_KEY, "registration key") + .withRequiredArg() + .defaultsTo(EMPTY); + + public RegisterDisputeAgentOptionParser(String[] args) { + super(args); + } + + public RegisterDisputeAgentOptionParser parse() { + super.parse(); + + // Short circuit opt validation if user just wants help. + if (options.has(helpOpt)) + return this; + + if (!options.has(disputeAgentTypeOpt)) + throw new IllegalArgumentException("no dispute agent type specified"); + + if (!options.has(registrationKeyOpt)) + throw new IllegalArgumentException("no registration key specified"); + + return this; + } + + public String getDisputeAgentType() { + return options.valueOf(disputeAgentTypeOpt); + } + + public String getRegistrationKey() { + return options.valueOf(registrationKeyOpt); + } +} diff --git a/cli/src/main/java/bisq/cli/opts/RemoveWalletPasswordOptionParser.java b/cli/src/main/java/bisq/cli/opts/RemoveWalletPasswordOptionParser.java new file mode 100644 index 00000000000..5b9a3915941 --- /dev/null +++ b/cli/src/main/java/bisq/cli/opts/RemoveWalletPasswordOptionParser.java @@ -0,0 +1,52 @@ +/* + * 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.opts; + + +import joptsimple.OptionSpec; + +import static bisq.cli.opts.OptLabel.OPT_WALLET_PASSWORD; +import static joptsimple.internal.Strings.EMPTY; + +public class RemoveWalletPasswordOptionParser extends AbstractMethodOptionParser implements MethodOpts { + + final OptionSpec passwordOpt = parser.accepts(OPT_WALLET_PASSWORD, "bisq wallet password") + .withRequiredArg() + .defaultsTo(EMPTY); + + public RemoveWalletPasswordOptionParser(String[] args) { + super(args); + } + + public RemoveWalletPasswordOptionParser parse() { + super.parse(); + + // Short circuit opt validation if user just wants help. + if (options.has(helpOpt)) + return this; + + if (!options.has(passwordOpt)) + throw new IllegalArgumentException("no password specified"); + + return this; + } + + public String getPassword() { + return options.valueOf(passwordOpt); + } +} diff --git a/cli/src/main/java/bisq/cli/opts/SendBsqOptionParser.java b/cli/src/main/java/bisq/cli/opts/SendBsqOptionParser.java new file mode 100644 index 00000000000..3bffce785c4 --- /dev/null +++ b/cli/src/main/java/bisq/cli/opts/SendBsqOptionParser.java @@ -0,0 +1,73 @@ +/* + * 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.opts; + + +import joptsimple.OptionSpec; + +import static bisq.cli.opts.OptLabel.OPT_ADDRESS; +import static bisq.cli.opts.OptLabel.OPT_AMOUNT; +import static bisq.cli.opts.OptLabel.OPT_TX_FEE_RATE; +import static joptsimple.internal.Strings.EMPTY; + +public class SendBsqOptionParser extends AbstractMethodOptionParser implements MethodOpts { + + final OptionSpec addressOpt = parser.accepts(OPT_ADDRESS, "destination bsq address") + .withRequiredArg() + .defaultsTo(EMPTY); + + final OptionSpec amountOpt = parser.accepts(OPT_AMOUNT, "amount of bsq to send") + .withRequiredArg() + .defaultsTo(EMPTY); + + final OptionSpec feeRateOpt = parser.accepts(OPT_TX_FEE_RATE, "optional tx fee rate (sats/byte)") + .withOptionalArg() + .defaultsTo(EMPTY); + + public SendBsqOptionParser(String[] args) { + super(args); + } + + public SendBsqOptionParser parse() { + super.parse(); + + // Short circuit opt validation if user just wants help. + if (options.has(helpOpt)) + return this; + + if (!options.has(addressOpt)) + throw new IllegalArgumentException("no bsq address specified"); + + if (!options.has(amountOpt)) + throw new IllegalArgumentException("no bsq amount specified"); + + return this; + } + + public String getAddress() { + return options.valueOf(addressOpt); + } + + public String getAmount() { + return options.valueOf(amountOpt); + } + + public String getFeeRate() { + return options.has(feeRateOpt) ? options.valueOf(feeRateOpt) : ""; + } +} diff --git a/cli/src/main/java/bisq/cli/opts/SendBtcOptionParser.java b/cli/src/main/java/bisq/cli/opts/SendBtcOptionParser.java new file mode 100644 index 00000000000..8c3f9762019 --- /dev/null +++ b/cli/src/main/java/bisq/cli/opts/SendBtcOptionParser.java @@ -0,0 +1,82 @@ +/* + * 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.opts; + + +import joptsimple.OptionSpec; + +import static bisq.cli.opts.OptLabel.OPT_ADDRESS; +import static bisq.cli.opts.OptLabel.OPT_AMOUNT; +import static bisq.cli.opts.OptLabel.OPT_MEMO; +import static bisq.cli.opts.OptLabel.OPT_TX_FEE_RATE; +import static joptsimple.internal.Strings.EMPTY; + +public class SendBtcOptionParser extends AbstractMethodOptionParser implements MethodOpts { + + final OptionSpec addressOpt = parser.accepts(OPT_ADDRESS, "destination btc address") + .withRequiredArg() + .defaultsTo(EMPTY); + + final OptionSpec amountOpt = parser.accepts(OPT_AMOUNT, "amount of btc to send") + .withRequiredArg() + .defaultsTo(EMPTY); + + final OptionSpec feeRateOpt = parser.accepts(OPT_TX_FEE_RATE, "optional tx fee rate (sats/byte)") + .withOptionalArg() + .defaultsTo(EMPTY); + + final OptionSpec memoOpt = parser.accepts(OPT_MEMO, "optional tx memo") + .withOptionalArg() + .defaultsTo(EMPTY); + + public SendBtcOptionParser(String[] args) { + super(args); + } + + public SendBtcOptionParser parse() { + super.parse(); + + // Short circuit opt validation if user just wants help. + if (options.has(helpOpt)) + return this; + + if (!options.has(addressOpt)) + throw new IllegalArgumentException("no btc address specified"); + + if (!options.has(amountOpt)) + throw new IllegalArgumentException("no btc amount specified"); + + return this; + } + + public String getAddress() { + return options.valueOf(addressOpt); + } + + public String getAmount() { + return options.valueOf(amountOpt); + } + + public String getFeeRate() { + return options.has(feeRateOpt) ? options.valueOf(feeRateOpt) : ""; + } + + public String getMemo() { + return options.has(memoOpt) ? options.valueOf(memoOpt) : ""; + } +} diff --git a/cli/src/main/java/bisq/cli/opts/SetTxFeeRateOptionParser.java b/cli/src/main/java/bisq/cli/opts/SetTxFeeRateOptionParser.java new file mode 100644 index 00000000000..9d4b5e71b3e --- /dev/null +++ b/cli/src/main/java/bisq/cli/opts/SetTxFeeRateOptionParser.java @@ -0,0 +1,53 @@ +/* + * 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.opts; + + +import joptsimple.OptionSpec; + +import static bisq.cli.opts.OptLabel.OPT_TX_FEE_RATE; +import static joptsimple.internal.Strings.EMPTY; + +public class SetTxFeeRateOptionParser extends AbstractMethodOptionParser implements MethodOpts { + + final OptionSpec feeRateOpt = parser.accepts(OPT_TX_FEE_RATE, + "tx fee rate preference (sats/byte)") + .withRequiredArg() + .defaultsTo(EMPTY); + + public SetTxFeeRateOptionParser(String[] args) { + super(args); + } + + public SetTxFeeRateOptionParser parse() { + super.parse(); + + // Short circuit opt validation if user just wants help. + if (options.has(helpOpt)) + return this; + + if (!options.has(feeRateOpt)) + throw new IllegalArgumentException("no tx fee rate specified"); + + return this; + } + + public String getFeeRate() { + return options.valueOf(feeRateOpt); + } +} diff --git a/cli/src/main/java/bisq/cli/opts/SetWalletPasswordOptionParser.java b/cli/src/main/java/bisq/cli/opts/SetWalletPasswordOptionParser.java new file mode 100644 index 00000000000..d55b1bf33b4 --- /dev/null +++ b/cli/src/main/java/bisq/cli/opts/SetWalletPasswordOptionParser.java @@ -0,0 +1,61 @@ +/* + * 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.opts; + + +import joptsimple.OptionSpec; + +import static bisq.cli.opts.OptLabel.OPT_NEW_WALLET_PASSWORD; +import static bisq.cli.opts.OptLabel.OPT_WALLET_PASSWORD; +import static joptsimple.internal.Strings.EMPTY; + +public class SetWalletPasswordOptionParser extends AbstractMethodOptionParser implements MethodOpts { + + final OptionSpec passwordOpt = parser.accepts(OPT_WALLET_PASSWORD, "bisq wallet password") + .withRequiredArg() + .defaultsTo(EMPTY); + + final OptionSpec newPasswordOpt = parser.accepts(OPT_NEW_WALLET_PASSWORD, "new bisq wallet password") + .withOptionalArg() + .defaultsTo(EMPTY); + + public SetWalletPasswordOptionParser(String[] args) { + super(args); + } + + public SetWalletPasswordOptionParser parse() { + super.parse(); + + // Short circuit opt validation if user just wants help. + if (options.has(helpOpt)) + return this; + + if (!options.has(passwordOpt)) + throw new IllegalArgumentException("no password specified"); + + return this; + } + + public String getPassword() { + return options.valueOf(passwordOpt); + } + + public String getNewPassword() { + return options.has(newPasswordOpt) ? options.valueOf(newPasswordOpt) : ""; + } +} diff --git a/cli/src/main/java/bisq/cli/opts/SimpleMethodOptionParser.java b/cli/src/main/java/bisq/cli/opts/SimpleMethodOptionParser.java new file mode 100644 index 00000000000..a0e396d63af --- /dev/null +++ b/cli/src/main/java/bisq/cli/opts/SimpleMethodOptionParser.java @@ -0,0 +1,30 @@ +/* + * 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.opts; + + +public class SimpleMethodOptionParser extends AbstractMethodOptionParser implements MethodOpts { + + public SimpleMethodOptionParser(String[] args) { + super(args); + } + + public SimpleMethodOptionParser parse() { + return (SimpleMethodOptionParser) super.parse(); + } +} diff --git a/cli/src/main/java/bisq/cli/opts/TakeOfferOptionParser.java b/cli/src/main/java/bisq/cli/opts/TakeOfferOptionParser.java new file mode 100644 index 00000000000..75ef2885b04 --- /dev/null +++ b/cli/src/main/java/bisq/cli/opts/TakeOfferOptionParser.java @@ -0,0 +1,73 @@ +/* + * 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.opts; + + +import joptsimple.OptionSpec; + +import static bisq.cli.opts.OptLabel.OPT_FEE_CURRENCY; +import static bisq.cli.opts.OptLabel.OPT_OFFER_ID; +import static bisq.cli.opts.OptLabel.OPT_PAYMENT_ACCOUNT; +import static joptsimple.internal.Strings.EMPTY; + +public class TakeOfferOptionParser extends AbstractMethodOptionParser implements MethodOpts { + + final OptionSpec offerIdOpt = parser.accepts(OPT_OFFER_ID, "id of offer to take") + .withRequiredArg() + .defaultsTo(EMPTY); + + final OptionSpec paymentAccountIdOpt = parser.accepts(OPT_PAYMENT_ACCOUNT, "id of payment account used for trade") + .withRequiredArg() + .defaultsTo(EMPTY); + + final OptionSpec takerFeeCurrencyCodeOpt = parser.accepts(OPT_FEE_CURRENCY, "taker fee currency code (bsq|btc)") + .withOptionalArg() + .defaultsTo("btc"); + + public TakeOfferOptionParser(String[] args) { + super(args); + } + + public TakeOfferOptionParser parse() { + super.parse(); + + // Short circuit opt validation if user just wants help. + if (options.has(helpOpt)) + return this; + + if (!options.has(offerIdOpt)) + throw new IllegalArgumentException("no offer id specified"); + + if (!options.has(paymentAccountIdOpt)) + throw new IllegalArgumentException("no payment account id specified"); + + return this; + } + + public String getOfferId() { + return options.valueOf(offerIdOpt); + } + + public String getPaymentAccountId() { + return options.valueOf(paymentAccountIdOpt); + } + + public String getTakerFeeCurrencyCode() { + return options.has(takerFeeCurrencyCodeOpt) ? options.valueOf(takerFeeCurrencyCodeOpt) : "btc"; + } +} diff --git a/cli/src/main/java/bisq/cli/opts/UnlockWalletOptionParser.java b/cli/src/main/java/bisq/cli/opts/UnlockWalletOptionParser.java new file mode 100644 index 00000000000..4446138dd37 --- /dev/null +++ b/cli/src/main/java/bisq/cli/opts/UnlockWalletOptionParser.java @@ -0,0 +1,65 @@ +/* + * 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.opts; + + +import joptsimple.OptionSpec; + +import static bisq.cli.opts.OptLabel.OPT_TIMEOUT; +import static bisq.cli.opts.OptLabel.OPT_WALLET_PASSWORD; +import static joptsimple.internal.Strings.EMPTY; + +public class UnlockWalletOptionParser extends AbstractMethodOptionParser implements MethodOpts { + + final OptionSpec passwordOpt = parser.accepts(OPT_WALLET_PASSWORD, "bisq wallet password") + .withRequiredArg() + .defaultsTo(EMPTY); + + final OptionSpec unlockTimeoutOpt = parser.accepts(OPT_TIMEOUT, "wallet unlock timeout (s)") + .withRequiredArg() + .ofType(long.class) + .defaultsTo(0L); + + public UnlockWalletOptionParser(String[] args) { + super(args); + } + + public UnlockWalletOptionParser parse() { + super.parse(); + + // Short circuit opt validation if user just wants help. + if (options.has(helpOpt)) + return this; + + if (!options.has(passwordOpt)) + throw new IllegalArgumentException("no password specified"); + + if (!options.has(unlockTimeoutOpt) || options.valueOf(unlockTimeoutOpt) <= 0) + throw new IllegalArgumentException("no unlock timeout specified"); + + return this; + } + + public String getPassword() { + return options.valueOf(passwordOpt); + } + + public long getUnlockTimeout() { + return options.valueOf(unlockTimeoutOpt); + } +} diff --git a/cli/src/main/java/bisq/cli/opts/WithdrawFundsOptionParser.java b/cli/src/main/java/bisq/cli/opts/WithdrawFundsOptionParser.java new file mode 100644 index 00000000000..382fe2c883a --- /dev/null +++ b/cli/src/main/java/bisq/cli/opts/WithdrawFundsOptionParser.java @@ -0,0 +1,70 @@ +/* + * 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.opts; + + +import joptsimple.OptionSpec; + +import static bisq.cli.opts.OptLabel.OPT_ADDRESS; +import static bisq.cli.opts.OptLabel.OPT_MEMO; +import static bisq.cli.opts.OptLabel.OPT_TRADE_ID; +import static joptsimple.internal.Strings.EMPTY; + +public class WithdrawFundsOptionParser extends AbstractMethodOptionParser implements MethodOpts { + + final OptionSpec tradeIdOpt = parser.accepts(OPT_TRADE_ID, "id of trade to get") + .withRequiredArg() + .defaultsTo(EMPTY); + + final OptionSpec addressOpt = parser.accepts(OPT_ADDRESS, "destination btc address") + .withRequiredArg() + .defaultsTo(EMPTY); + + final OptionSpec memoOpt = parser.accepts(OPT_MEMO, "optional tx memo") + .withOptionalArg() + .defaultsTo(EMPTY); + + public WithdrawFundsOptionParser(String[] args) { + super(args); + } + + public WithdrawFundsOptionParser parse() { + super.parse(); + + // Short circuit opt validation if user just wants help. + if (options.has(helpOpt)) + return this; + + if (!options.has(tradeIdOpt)) + throw new IllegalArgumentException("no trade id specified"); + + return this; + } + + public String getTradeId() { + return options.valueOf(tradeIdOpt); + } + + public String getAddress() { + return options.valueOf(addressOpt); + } + + public String getMemo() { + return options.has(memoOpt) ? options.valueOf(memoOpt) : ""; + } +} From f4e735faec3233c0a0e892382bb6f4f4ab42239e Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Thu, 14 Jan 2021 10:25:08 -0300 Subject: [PATCH 04/10] Move CLI method enum to it's own class This helps reduce size of growing CLI class file. --- cli/src/main/java/bisq/cli/Method.java | 56 ++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 cli/src/main/java/bisq/cli/Method.java diff --git a/cli/src/main/java/bisq/cli/Method.java b/cli/src/main/java/bisq/cli/Method.java new file mode 100644 index 00000000000..037468b9bd0 --- /dev/null +++ b/cli/src/main/java/bisq/cli/Method.java @@ -0,0 +1,56 @@ +/* + * 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; + +/** + * Currently supported api methods. + */ +public enum Method { + createoffer, + canceloffer, + getoffer, + getmyoffer, + getoffers, + getmyoffers, + takeoffer, + gettrade, + confirmpaymentstarted, + confirmpaymentreceived, + keepfunds, + withdrawfunds, + getpaymentmethods, + getpaymentacctform, + createpaymentacct, + getpaymentaccts, + getversion, + getbalance, + getaddressbalance, + getfundingaddresses, + getunusedbsqaddress, + sendbsq, + sendbtc, + gettxfeerate, + settxfeerate, + unsettxfeerate, + gettransaction, + lockwallet, + unlockwallet, + removewalletpassword, + setwalletpassword, + registerdisputeagent +} From 9f0f083cf7f29d1820c6b7326d6e25ae0e3ad230 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Thu, 14 Jan 2021 10:26:11 -0300 Subject: [PATCH 05/10] Change CLI opts to posix-style --- cli/src/main/java/bisq/cli/CliMain.java | 628 +++++++++++++----------- 1 file changed, 351 insertions(+), 277 deletions(-) diff --git a/cli/src/main/java/bisq/cli/CliMain.java b/cli/src/main/java/bisq/cli/CliMain.java index 6df23f9b730..6c3e796942f 100644 --- a/cli/src/main/java/bisq/cli/CliMain.java +++ b/cli/src/main/java/bisq/cli/CliMain.java @@ -25,6 +25,7 @@ import bisq.proto.grpc.GetAddressBalanceRequest; import bisq.proto.grpc.GetBalancesRequest; import bisq.proto.grpc.GetFundingAddressesRequest; +import bisq.proto.grpc.GetMethodHelpRequest; import bisq.proto.grpc.GetMyOfferRequest; import bisq.proto.grpc.GetMyOffersRequest; import bisq.proto.grpc.GetOfferRequest; @@ -69,8 +70,6 @@ import java.io.PrintStream; import java.io.PrintWriter; -import java.math.BigDecimal; - import java.util.Date; import java.util.List; @@ -79,15 +78,43 @@ import static bisq.cli.CurrencyFormat.formatTxFeeRateInfo; import static bisq.cli.CurrencyFormat.toSatoshis; import static bisq.cli.CurrencyFormat.toSecurityDepositAsPct; -import static bisq.cli.NegativeNumberOptions.hasNegativeNumberOptions; +import static bisq.cli.Method.*; import static bisq.cli.TableFormat.*; +import static bisq.cli.opts.OptLabel.OPT_HELP; +import static bisq.cli.opts.OptLabel.OPT_HOST; +import static bisq.cli.opts.OptLabel.OPT_PASSWORD; +import static bisq.cli.opts.OptLabel.OPT_PORT; +import static bisq.proto.grpc.HelpGrpc.HelpBlockingStub; import static java.lang.String.format; import static java.lang.System.err; import static java.lang.System.exit; import static java.lang.System.out; -import static java.math.BigDecimal.ZERO; import static java.util.Collections.singletonList; + + +import bisq.cli.opts.ArgumentList; +import bisq.cli.opts.CancelOfferOptionParser; +import bisq.cli.opts.CreateOfferOptionParser; +import bisq.cli.opts.CreatePaymentAcctOptionParser; +import bisq.cli.opts.GetAddressBalanceOptionParser; +import bisq.cli.opts.GetBalanceOptionParser; +import bisq.cli.opts.GetOfferOptionParser; +import bisq.cli.opts.GetOffersOptionParser; +import bisq.cli.opts.GetPaymentAcctFormOptionParser; +import bisq.cli.opts.GetTradeOptionParser; +import bisq.cli.opts.GetTransactionOptionParser; +import bisq.cli.opts.RegisterDisputeAgentOptionParser; +import bisq.cli.opts.RemoveWalletPasswordOptionParser; +import bisq.cli.opts.SendBsqOptionParser; +import bisq.cli.opts.SendBtcOptionParser; +import bisq.cli.opts.SetTxFeeRateOptionParser; +import bisq.cli.opts.SetWalletPasswordOptionParser; +import bisq.cli.opts.SimpleMethodOptionParser; +import bisq.cli.opts.TakeOfferOptionParser; +import bisq.cli.opts.UnlockWalletOptionParser; +import bisq.cli.opts.WithdrawFundsOptionParser; + /** * A command-line client for the Bisq gRPC API. */ @@ -95,41 +122,6 @@ @Slf4j public class CliMain { - private enum Method { - createoffer, - canceloffer, - getoffer, - getmyoffer, - getoffers, - getmyoffers, - takeoffer, - gettrade, - confirmpaymentstarted, - confirmpaymentreceived, - keepfunds, - withdrawfunds, - getpaymentmethods, - getpaymentacctform, - createpaymentacct, - getpaymentaccts, - getversion, - getbalance, - getaddressbalance, - getfundingaddresses, - getunusedbsqaddress, - sendbsq, - sendbtc, - gettxfeerate, - settxfeerate, - unsettxfeerate, - gettransaction, - lockwallet, - unlockwallet, - removewalletpassword, - setwalletpassword, - registerdisputeagent - } - public static void main(String[] args) { try { run(args); @@ -142,48 +134,47 @@ public static void main(String[] args) { public static void run(String[] args) { var parser = new OptionParser(); - var helpOpt = parser.accepts("help", "Print this help text") + var helpOpt = parser.accepts(OPT_HELP, "Print this help text") .forHelp(); - var hostOpt = parser.accepts("host", "rpc server hostname or IP") + var hostOpt = parser.accepts(OPT_HOST, "rpc server hostname or ip") .withRequiredArg() .defaultsTo("localhost"); - var portOpt = parser.accepts("port", "rpc server port") + var portOpt = parser.accepts(OPT_PORT, "rpc server port") .withRequiredArg() .ofType(Integer.class) .defaultsTo(9998); - var passwordOpt = parser.accepts("password", "rpc server password") + var passwordOpt = parser.accepts(OPT_PASSWORD, "rpc server password") .withRequiredArg(); - var negativeNumberOpts = hasNegativeNumberOptions(args) - ? new NegativeNumberOptions() - : null; - - // Cache any negative number params that will not be accepted by the parser. - if (negativeNumberOpts != null) - args = negativeNumberOpts.removeNegativeNumberOptions(args); - - // Parse the options after temporarily removing any negative number params we - // do not want the parser recognizing as invalid option arguments, e.g., -0.05. - OptionSet options = parser.parse(args); - - if (options.has(helpOpt)) { - printHelp(parser, out); - return; - } - + // Parse the CLI opts host, port, password, method name, and help. The help opt + // may indicate the user is asking for method level help, and will be excluded + // from the parsed options if a method opt is present in String[] args. + OptionSet options = parser.parse(new ArgumentList(args).getCLIArguments()); @SuppressWarnings("unchecked") var nonOptionArgs = (List) options.nonOptionArguments(); - if (nonOptionArgs.isEmpty()) { + + // If neither the help opt nor a method name is present, print CLI level help + // to stderr and throw an exception. + if (!options.has(helpOpt) && nonOptionArgs.isEmpty()) { printHelp(parser, err); throw new IllegalArgumentException("no method specified"); } - // Restore any cached negative number params to the nonOptionArgs list. - if (negativeNumberOpts != null) - nonOptionArgs = negativeNumberOpts.restoreNegativeNumberOptions(nonOptionArgs); + // If the help opt is present, but not a method name, print CLI level help + // to stdout. + if (options.has(helpOpt) && nonOptionArgs.isEmpty()) { + printHelp(parser, out); + return; + } + + var host = options.valueOf(hostOpt); + var port = options.valueOf(portOpt); + var password = options.valueOf(passwordOpt); + if (password == null) + throw new IllegalArgumentException("missing required 'password' option"); var methodName = nonOptionArgs.get(0); Method method; @@ -193,14 +184,9 @@ public static void run(String[] args) { throw new IllegalArgumentException(format("'%s' is not a supported method", methodName)); } - var host = options.valueOf(hostOpt); - var port = options.valueOf(portOpt); - var password = options.valueOf(passwordOpt); - if (password == null) - throw new IllegalArgumentException("missing required 'password' option"); - GrpcStubs grpcStubs = new GrpcStubs(host, port, password); var disputeAgentsService = grpcStubs.disputeAgentsService; + var helpService = grpcStubs.helpService; var offersService = grpcStubs.offersService; var paymentAccountsService = grpcStubs.paymentAccountsService; var tradesService = grpcStubs.tradesService; @@ -210,15 +196,22 @@ public static void run(String[] args) { try { switch (method) { case getversion: { + if (new SimpleMethodOptionParser(args).parse().isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } var request = GetVersionRequest.newBuilder().build(); var version = versionService.getVersion(request).getVersion(); out.println(version); return; } case getbalance: { - var currencyCode = nonOptionArgs.size() == 2 - ? nonOptionArgs.get(1) - : ""; + var opts = new GetBalanceOptionParser(args).parse(); + if (opts.isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } + var currencyCode = opts.getCurrencyCode(); var request = GetBalancesRequest.newBuilder() .setCurrencyCode(currencyCode) .build(); @@ -238,41 +231,50 @@ public static void run(String[] args) { return; } case getaddressbalance: { - if (nonOptionArgs.size() < 2) - throw new IllegalArgumentException("no address specified"); - + var opts = new GetAddressBalanceOptionParser(args).parse(); + if (opts.isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } + var address = opts.getAddress(); var request = GetAddressBalanceRequest.newBuilder() - .setAddress(nonOptionArgs.get(1)).build(); + .setAddress(address).build(); var reply = walletsService.getAddressBalance(request); out.println(formatAddressBalanceTbl(singletonList(reply.getAddressBalanceInfo()))); return; } case getfundingaddresses: { + if (new SimpleMethodOptionParser(args).parse().isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } var request = GetFundingAddressesRequest.newBuilder().build(); var reply = walletsService.getFundingAddresses(request); out.println(formatAddressBalanceTbl(reply.getAddressBalanceInfoList())); return; } case getunusedbsqaddress: { + if (new SimpleMethodOptionParser(args).parse().isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } var request = GetUnusedBsqAddressRequest.newBuilder().build(); var reply = walletsService.getUnusedBsqAddress(request); out.println(reply.getAddress()); return; } case sendbsq: { - if (nonOptionArgs.size() < 2) - throw new IllegalArgumentException("no bsq address specified"); - - var address = nonOptionArgs.get(1); - - if (nonOptionArgs.size() < 3) - throw new IllegalArgumentException("no bsq amount specified"); - - var amount = nonOptionArgs.get(2); + var opts = new SendBsqOptionParser(args).parse(); + if (opts.isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } + var address = opts.getAddress(); + var amount = opts.getAmount(); verifyStringIsValidDecimal(amount); - var txFeeRate = nonOptionArgs.size() == 4 ? nonOptionArgs.get(3) : ""; - if (!txFeeRate.isEmpty()) + var txFeeRate = opts.getFeeRate(); + if (txFeeRate.isEmpty()) verifyStringIsValidLong(txFeeRate); var request = SendBsqRequest.newBuilder() @@ -289,24 +291,20 @@ public static void run(String[] args) { return; } case sendbtc: { - if (nonOptionArgs.size() < 2) - throw new IllegalArgumentException("no btc address specified"); - - var address = nonOptionArgs.get(1); - - if (nonOptionArgs.size() < 3) - throw new IllegalArgumentException("no btc amount specified"); - - var amount = nonOptionArgs.get(2); + var opts = new SendBtcOptionParser(args).parse(); + if (opts.isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } + var address = opts.getAddress(); + var amount = opts.getAmount(); verifyStringIsValidDecimal(amount); - // TODO Find a better way to handle the two optional parameters. - var txFeeRate = nonOptionArgs.size() >= 4 ? nonOptionArgs.get(3) : ""; - if (!txFeeRate.isEmpty()) + var txFeeRate = opts.getFeeRate(); + if (txFeeRate.isEmpty()) verifyStringIsValidLong(txFeeRate); - var memo = nonOptionArgs.size() == 5 ? nonOptionArgs.get(4) : ""; - + var memo = opts.getMemo(); var request = SendBtcRequest.newBuilder() .setAddress(address) .setAmount(amount) @@ -322,16 +320,22 @@ public static void run(String[] args) { return; } case gettxfeerate: { + if (new SimpleMethodOptionParser(args).parse().isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } var request = GetTxFeeRateRequest.newBuilder().build(); var reply = walletsService.getTxFeeRate(request); out.println(formatTxFeeRateInfo(reply.getTxFeeRateInfo())); return; } case settxfeerate: { - if (nonOptionArgs.size() < 2) - throw new IllegalArgumentException("no tx fee rate specified"); - - var txFeeRate = toLong(nonOptionArgs.get(2)); + var opts = new SetTxFeeRateOptionParser(args).parse(); + if (opts.isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } + var txFeeRate = toLong(opts.getFeeRate()); var request = SetTxFeeRatePreferenceRequest.newBuilder() .setTxFeeRatePreference(txFeeRate) .build(); @@ -340,16 +344,22 @@ public static void run(String[] args) { return; } case unsettxfeerate: { + if (new SimpleMethodOptionParser(args).parse().isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } var request = UnsetTxFeeRatePreferenceRequest.newBuilder().build(); var reply = walletsService.unsetTxFeeRatePreference(request); out.println(formatTxFeeRateInfo(reply.getTxFeeRateInfo())); return; } case gettransaction: { - if (nonOptionArgs.size() < 2) - throw new IllegalArgumentException("no tx id specified"); - - var txId = nonOptionArgs.get(1); + var opts = new GetTransactionOptionParser(args).parse(); + if (opts.isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } + var txId = opts.getTxId(); var request = GetTransactionRequest.newBuilder() .setTxId(txId) .build(); @@ -358,30 +368,21 @@ public static void run(String[] args) { return; } case createoffer: { - if (nonOptionArgs.size() < 9) - throw new IllegalArgumentException("incorrect parameter count," - + " expecting payment acct id, buy | sell, currency code, amount, min amount," - + " use-market-based-price, fixed-price | mkt-price-margin, security-deposit" - + " [,maker-fee-currency-code = bsq|btc]"); - - var paymentAcctId = nonOptionArgs.get(1); - var direction = nonOptionArgs.get(2); - var currencyCode = nonOptionArgs.get(3); - var amount = toSatoshis(nonOptionArgs.get(4)); - var minAmount = toSatoshis(nonOptionArgs.get(5)); - var useMarketBasedPrice = Boolean.parseBoolean(nonOptionArgs.get(6)); - var fixedPrice = ZERO.toString(); - var marketPriceMargin = ZERO; - if (useMarketBasedPrice) - marketPriceMargin = new BigDecimal(nonOptionArgs.get(7)); - else - fixedPrice = nonOptionArgs.get(7); - - var securityDeposit = toSecurityDepositAsPct(nonOptionArgs.get(8)); - var makerFeeCurrencyCode = nonOptionArgs.size() == 10 - ? nonOptionArgs.get(9) - : "btc"; - + var opts = new CreateOfferOptionParser(args).parse(); + if (opts.isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } + var paymentAcctId = opts.getPaymentAccountId(); + var direction = opts.getDirection(); + var currencyCode = opts.getCurrencyCode(); + var amount = toSatoshis(opts.getAmount()); + var minAmount = toSatoshis(opts.getMinAmount()); + var useMarketBasedPrice = opts.isUsingMktPriceMargin(); + var fixedPrice = opts.getFixedPrice(); + var marketPriceMargin = opts.getMktPriceMarginAsBigDecimal(); + var securityDeposit = toSecurityDepositAsPct(opts.getSecurityDeposit()); + var makerFeeCurrencyCode = opts.getMakerFeeCurrencyCode(); var request = CreateOfferRequest.newBuilder() .setDirection(direction) .setCurrencyCode(currencyCode) @@ -399,10 +400,12 @@ public static void run(String[] args) { return; } case canceloffer: { - if (nonOptionArgs.size() < 2) - throw new IllegalArgumentException("incorrect parameter count, expecting offer id"); - - var offerId = nonOptionArgs.get(1); + var opts = new CancelOfferOptionParser(args).parse(); + if (opts.isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } + var offerId = opts.getOfferId(); var request = CancelOfferRequest.newBuilder() .setId(offerId) .build(); @@ -411,10 +414,12 @@ public static void run(String[] args) { return; } case getoffer: { - if (nonOptionArgs.size() < 2) - throw new IllegalArgumentException("incorrect parameter count, expecting offer id"); - - var offerId = nonOptionArgs.get(1); + var opts = new GetOfferOptionParser(args).parse(); + if (opts.isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } + var offerId = opts.getOfferId(); var request = GetOfferRequest.newBuilder() .setId(offerId) .build(); @@ -424,10 +429,12 @@ public static void run(String[] args) { return; } case getmyoffer: { - if (nonOptionArgs.size() < 2) - throw new IllegalArgumentException("incorrect parameter count, expecting offer id"); - - var offerId = nonOptionArgs.get(1); + var opts = new GetOfferOptionParser(args).parse(); + if (opts.isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } + var offerId = opts.getOfferId(); var request = GetMyOfferRequest.newBuilder() .setId(offerId) .build(); @@ -437,13 +444,13 @@ public static void run(String[] args) { return; } case getoffers: { - if (nonOptionArgs.size() < 3) - throw new IllegalArgumentException("incorrect parameter count," - + " expecting direction (buy|sell), currency code"); - - var direction = nonOptionArgs.get(1); - var currencyCode = nonOptionArgs.get(2); - + var opts = new GetOffersOptionParser(args).parse(); + if (opts.isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } + var direction = opts.getDirection(); + var currencyCode = opts.getCurrencyCode(); var request = GetOffersRequest.newBuilder() .setDirection(direction) .setCurrencyCode(currencyCode) @@ -459,13 +466,13 @@ public static void run(String[] args) { return; } case getmyoffers: { - if (nonOptionArgs.size() < 3) - throw new IllegalArgumentException("incorrect parameter count," - + " expecting direction (buy|sell), currency code"); - - var direction = nonOptionArgs.get(1); - var currencyCode = nonOptionArgs.get(2); - + var opts = new GetOffersOptionParser(args).parse(); + if (opts.isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } + var direction = opts.getDirection(); + var currencyCode = opts.getCurrencyCode(); var request = GetMyOffersRequest.newBuilder() .setDirection(direction) .setCurrencyCode(currencyCode) @@ -481,16 +488,14 @@ public static void run(String[] args) { return; } case takeoffer: { - if (nonOptionArgs.size() < 3) - throw new IllegalArgumentException("incorrect parameter count, " + - " expecting offer id, payment acct id [,taker fee currency code = bsq|btc]"); - - var offerId = nonOptionArgs.get(1); - var paymentAccountId = nonOptionArgs.get(2); - var takerFeeCurrencyCode = nonOptionArgs.size() == 4 - ? nonOptionArgs.get(3) - : "btc"; - + var opts = new TakeOfferOptionParser(args).parse(); + if (opts.isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } + var offerId = opts.getOfferId(); + var paymentAccountId = opts.getPaymentAccountId(); + var takerFeeCurrencyCode = opts.getTakerFeeCurrencyCode(); var request = TakeOfferRequest.newBuilder() .setOfferId(offerId) .setPaymentAccountId(paymentAccountId) @@ -502,30 +507,32 @@ public static void run(String[] args) { } case gettrade: { // TODO make short-id a valid argument? - if (nonOptionArgs.size() < 2) - throw new IllegalArgumentException("incorrect parameter count, " - + " expecting trade id [,showcontract = true|false]"); - - var tradeId = nonOptionArgs.get(1); - var showContract = false; - if (nonOptionArgs.size() == 3) - showContract = Boolean.getBoolean(nonOptionArgs.get(2)); - + var opts = new GetTradeOptionParser(args).parse(); + if (opts.isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } + var tradeId = opts.getTradeId(); + var showContract = opts.getShowContract(); var request = GetTradeRequest.newBuilder() .setTradeId(tradeId) .build(); var reply = tradesService.getTrade(request); + if (showContract) out.println(reply.getTrade().getContractAsJson()); else out.println(TradeFormat.format(reply.getTrade())); + return; } case confirmpaymentstarted: { - if (nonOptionArgs.size() < 2) - throw new IllegalArgumentException("incorrect parameter count, expecting trade id"); - - var tradeId = nonOptionArgs.get(1); + var opts = new GetTradeOptionParser(args).parse(); + if (opts.isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } + var tradeId = opts.getTradeId(); var request = ConfirmPaymentStartedRequest.newBuilder() .setTradeId(tradeId) .build(); @@ -534,10 +541,12 @@ public static void run(String[] args) { return; } case confirmpaymentreceived: { - if (nonOptionArgs.size() < 2) - throw new IllegalArgumentException("incorrect parameter count, expecting trade id"); - - var tradeId = nonOptionArgs.get(1); + var opts = new GetTradeOptionParser(args).parse(); + if (opts.isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } + var tradeId = opts.getTradeId(); var request = ConfirmPaymentReceivedRequest.newBuilder() .setTradeId(tradeId) .build(); @@ -546,10 +555,12 @@ public static void run(String[] args) { return; } case keepfunds: { - if (nonOptionArgs.size() < 2) - throw new IllegalArgumentException("incorrect parameter count, expecting trade id"); - - var tradeId = nonOptionArgs.get(1); + var opts = new GetTradeOptionParser(args).parse(); + if (opts.isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } + var tradeId = opts.getTradeId(); var request = KeepFundsRequest.newBuilder() .setTradeId(tradeId) .build(); @@ -558,16 +569,15 @@ public static void run(String[] args) { return; } case withdrawfunds: { - if (nonOptionArgs.size() < 3) - throw new IllegalArgumentException("incorrect parameter count, " - + " expecting trade id, bitcoin wallet address [,\"memo\"]"); - - var tradeId = nonOptionArgs.get(1); - var address = nonOptionArgs.get(2); - // A multi-word memo must be double quoted. - var memo = nonOptionArgs.size() == 4 - ? nonOptionArgs.get(3) - : ""; + var opts = new WithdrawFundsOptionParser(args).parse(); + if (opts.isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } + var tradeId = opts.getTradeId(); + var address = opts.getAddress(); + // Multi-word memos must be double quoted. + var memo = opts.getMemo(); var request = WithdrawFundsRequest.newBuilder() .setTradeId(tradeId) .setAddress(address) @@ -578,16 +588,22 @@ public static void run(String[] args) { return; } case getpaymentmethods: { + if (new SimpleMethodOptionParser(args).parse().isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } var request = GetPaymentMethodsRequest.newBuilder().build(); var reply = paymentAccountsService.getPaymentMethods(request); reply.getPaymentMethodsList().forEach(p -> out.println(p.getId())); return; } case getpaymentacctform: { - if (nonOptionArgs.size() < 2) - throw new IllegalArgumentException("incorrect parameter count, expecting payment method id"); - - var paymentMethodId = nonOptionArgs.get(1); + var opts = new GetPaymentAcctFormOptionParser(args).parse(); + if (opts.isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } + var paymentMethodId = opts.getPaymentMethodId(); var request = GetPaymentAccountFormRequest.newBuilder() .setPaymentMethodId(paymentMethodId) .build(); @@ -602,22 +618,18 @@ public static void run(String[] args) { return; } case createpaymentacct: { - if (nonOptionArgs.size() < 2) - throw new IllegalArgumentException( - "incorrect parameter count, expecting path to payment account form"); - - var paymentAccountFormPath = Paths.get(nonOptionArgs.get(1)); - if (!paymentAccountFormPath.toFile().exists()) - throw new IllegalStateException( - format("payment account form '%s' could not be found", - paymentAccountFormPath.toString())); - + var opts = new CreatePaymentAcctOptionParser(args).parse(); + if (opts.isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } + var paymentAccountForm = opts.getPaymentAcctForm(); String jsonString; try { - jsonString = new String(Files.readAllBytes(paymentAccountFormPath)); + jsonString = new String(Files.readAllBytes(paymentAccountForm)); } catch (IOException e) { throw new IllegalStateException( - format("could not read %s", paymentAccountFormPath.toString())); + format("could not read %s", paymentAccountForm.toString())); } var request = CreatePaymentAccountRequest.newBuilder() @@ -629,6 +641,10 @@ public static void run(String[] args) { return; } case getpaymentaccts: { + if (new SimpleMethodOptionParser(args).parse().isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } var request = GetPaymentAccountsRequest.newBuilder().build(); var reply = paymentAccountsService.getPaymentAccounts(request); @@ -641,56 +657,66 @@ public static void run(String[] args) { return; } case lockwallet: { + if (new SimpleMethodOptionParser(args).parse().isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } var request = LockWalletRequest.newBuilder().build(); walletsService.lockWallet(request); out.println("wallet locked"); return; } case unlockwallet: { - if (nonOptionArgs.size() < 2) - throw new IllegalArgumentException("no password specified"); - - if (nonOptionArgs.size() < 3) - throw new IllegalArgumentException("no unlock timeout specified"); - - var timeout = toLong(nonOptionArgs.get(2)); + var opts = new UnlockWalletOptionParser(args).parse(); + if (opts.isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } + var walletPassword = opts.getPassword(); + var timeout = opts.getUnlockTimeout(); var request = UnlockWalletRequest.newBuilder() - .setPassword(nonOptionArgs.get(1)) + .setPassword(walletPassword) .setTimeout(timeout).build(); walletsService.unlockWallet(request); out.println("wallet unlocked"); return; } case removewalletpassword: { - if (nonOptionArgs.size() < 2) - throw new IllegalArgumentException("no password specified"); - + var opts = new RemoveWalletPasswordOptionParser(args).parse(); + if (opts.isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } + var walletPassword = opts.getPassword(); var request = RemoveWalletPasswordRequest.newBuilder() - .setPassword(nonOptionArgs.get(1)).build(); + .setPassword(walletPassword).build(); walletsService.removeWalletPassword(request); out.println("wallet decrypted"); return; } case setwalletpassword: { - if (nonOptionArgs.size() < 2) - throw new IllegalArgumentException("no password specified"); - + var opts = new SetWalletPasswordOptionParser(args).parse(); + if (opts.isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } + var walletPassword = opts.getPassword(); + var newWalletPassword = opts.getNewPassword(); var requestBuilder = SetWalletPasswordRequest.newBuilder() - .setPassword(nonOptionArgs.get(1)); - var hasNewPassword = nonOptionArgs.size() == 3; - if (hasNewPassword) - requestBuilder.setNewPassword(nonOptionArgs.get(2)); + .setPassword(walletPassword) + .setNewPassword(newWalletPassword); walletsService.setWalletPassword(requestBuilder.build()); - out.println("wallet encrypted" + (hasNewPassword ? " with new password" : "")); + out.println("wallet encrypted" + (!newWalletPassword.isEmpty() ? " with new password" : "")); return; } case registerdisputeagent: { - if (nonOptionArgs.size() < 3) - throw new IllegalArgumentException( - "incorrect parameter count, expecting dispute agent type, registration key"); - - var disputeAgentType = nonOptionArgs.get(1); - var registrationKey = nonOptionArgs.get(2); + var opts = new RegisterDisputeAgentOptionParser(args).parse(); + if (opts.isForHelp()) { + out.println(getMethodHelp(helpService, method)); + return; + } + var disputeAgentType = opts.getDisputeAgentType(); + var registrationKey = opts.getRegistrationKey(); var requestBuilder = RegisterDisputeAgentRequest.newBuilder() .setDisputeAgentType(disputeAgentType).setRegistrationKey(registrationKey); disputeAgentsService.registerDisputeAgent(requestBuilder.build()); @@ -759,7 +785,7 @@ private static File saveFileToDisk(String prefix, } } - private static void printHelp(OptionParser parser, PrintStream stream) { + private static void printHelp(OptionParser parser, @SuppressWarnings("SameParameterValue") PrintStream stream) { try { stream.println("Bisq RPC Client"); stream.println(); @@ -770,45 +796,93 @@ private static void printHelp(OptionParser parser, PrintStream stream) { String rowFormat = "%-24s%-52s%s%n"; stream.format(rowFormat, "Method", "Params", "Description"); stream.format(rowFormat, "------", "------", "------------"); - stream.format(rowFormat, "getversion", "", "Get server version"); - stream.format(rowFormat, "getbalance", "[currency code = bsq|btc]", "Get server wallet balances"); - stream.format(rowFormat, "getaddressbalance", "address", "Get server wallet address balance"); - stream.format(rowFormat, "getfundingaddresses", "", "Get BTC funding addresses"); - stream.format(rowFormat, "getunusedbsqaddress", "", "Get unused BSQ address"); - stream.format(rowFormat, "sendbsq", "address, amount [,tx fee rate (sats/byte)]", "Send BSQ"); - stream.format(rowFormat, "sendbtc", "address, amount [,tx fee rate (sats/byte), \"memo\"]", "Send BTC"); - stream.format(rowFormat, "gettxfeerate", "", "Get current tx fee rate in sats/byte"); - stream.format(rowFormat, "settxfeerate", "satoshis (per byte)", "Set custom tx fee rate in sats/byte"); - stream.format(rowFormat, "unsettxfeerate", "", "Unset custom tx fee rate"); - stream.format(rowFormat, "gettransaction", "transaction id", "Get transaction with id"); - stream.format(rowFormat, "createoffer", "payment acct id, buy | sell, currency code, \\", "Create and place an offer"); - stream.format(rowFormat, "", "amount (btc), min amount, use mkt based price, \\", ""); - stream.format(rowFormat, "", "fixed price (btc) | mkt price margin (%), security deposit (%) \\", ""); - stream.format(rowFormat, "", "[,maker fee currency code = bsq|btc]", ""); - stream.format(rowFormat, "canceloffer", "offer id", "Cancel offer with id"); - stream.format(rowFormat, "getoffer", "offer id", "Get current offer with id"); - stream.format(rowFormat, "getmyoffer", "offer id", "Get my current offer with id"); - stream.format(rowFormat, "getoffers", "buy | sell, currency code", "Get current offers"); - stream.format(rowFormat, "getmyoffers", "buy | sell, currency code", "Get my current offers"); - stream.format(rowFormat, "takeoffer", "offer id [,taker fee currency code = bsq|btc]", "Take offer with id"); - stream.format(rowFormat, "gettrade", "trade id [,showcontract = true|false]", "Get trade summary or full contract"); - stream.format(rowFormat, "confirmpaymentstarted", "trade id", "Confirm payment started"); - stream.format(rowFormat, "confirmpaymentreceived", "trade id", "Confirm payment received"); - stream.format(rowFormat, "keepfunds", "trade id", "Keep received funds in Bisq wallet"); - stream.format(rowFormat, "withdrawfunds", "trade id, bitcoin wallet address [,\"memo\"]", + stream.format(rowFormat, getversion.name(), "", "Get server version"); + stream.println(); + stream.format(rowFormat, getbalance.name(), "[--currency-code=]", "Get server wallet balances"); + stream.println(); + stream.format(rowFormat, getaddressbalance.name(), "--address=", "Get server wallet address balance"); + stream.println(); + stream.format(rowFormat, getfundingaddresses.name(), "", "Get BTC funding addresses"); + stream.println(); + stream.format(rowFormat, getunusedbsqaddress.name(), "", "Get unused BSQ address"); + stream.println(); + stream.format(rowFormat, sendbsq.name(), "--address= --amount= \\", "Send BSQ"); + stream.format(rowFormat, "", "[--tx-fee-rate=]", ""); + stream.println(); + stream.format(rowFormat, sendbtc.name(), "--address= --amount= \\", "Send BTC"); + stream.format(rowFormat, "", "[--tx-fee-rate=]", ""); + stream.println(); + stream.format(rowFormat, gettxfeerate.name(), "", "Get current tx fee rate in sats/byte"); + stream.println(); + stream.format(rowFormat, settxfeerate.name(), "--tx-fee-rate=", "Set custom tx fee rate in sats/byte"); + stream.println(); + stream.format(rowFormat, unsettxfeerate.name(), "", "Unset custom tx fee rate"); + stream.println(); + stream.format(rowFormat, gettransaction.name(), "--transaction-id=", "Get transaction with id"); + stream.println(); + stream.format(rowFormat, createoffer.name(), "--payment-account= \\", "Create and place an offer"); + stream.format(rowFormat, "", "--direction= \\", ""); + stream.format(rowFormat, "", "--currency-code= \\", ""); + stream.format(rowFormat, "", "--amount= \\", ""); + stream.format(rowFormat, "", "[--min-amount=] \\", ""); + stream.format(rowFormat, "", "--fixed-price= | --market-price=margin= \\", ""); + stream.format(rowFormat, "", "--security-deposit= \\", ""); + stream.format(rowFormat, "", "[--fee-currency=]", ""); + stream.println(); + stream.format(rowFormat, canceloffer.name(), "--offer-id=", "Cancel offer with id"); + stream.println(); + stream.format(rowFormat, getoffer.name(), "--offer-id=", "Get current offer with id"); + stream.println(); + stream.format(rowFormat, getmyoffer.name(), "--offer-id=", "Get my current offer with id"); + stream.println(); + stream.format(rowFormat, getoffers.name(), "--direction= \\", "Get current offers"); + stream.format(rowFormat, "", "--currency-code=", ""); + stream.println(); + stream.format(rowFormat, getmyoffers.name(), "--direction= \\", "Get my current offers"); + stream.format(rowFormat, "", "--currency-code=", ""); + stream.println(); + stream.format(rowFormat, takeoffer.name(), "--offer-id= [--fee-currency=]", "Take offer with id"); + stream.println(); + stream.format(rowFormat, gettrade.name(), "--trade-id= \\", "Get trade summary or full contract"); + stream.format(rowFormat, "", "[--show-contract=]", ""); + stream.println(); + stream.format(rowFormat, confirmpaymentstarted.name(), "--trade-id=", "Confirm payment started"); + stream.println(); + stream.format(rowFormat, confirmpaymentreceived.name(), "--trade-id=", "Confirm payment received"); + stream.println(); + stream.format(rowFormat, keepfunds.name(), "--trade-id=", "Keep received funds in Bisq wallet"); + stream.println(); + stream.format(rowFormat, withdrawfunds.name(), "--trade-id= --address= \\", "Withdraw received funds to external wallet address"); - stream.format(rowFormat, "getpaymentmethods", "", "Get list of supported payment account method ids"); - stream.format(rowFormat, "getpaymentacctform", "payment method id", "Get a new payment account form"); - stream.format(rowFormat, "createpaymentacct", "path to payment account form", "Create a new payment account"); - stream.format(rowFormat, "getpaymentaccts", "", "Get user payment accounts"); - stream.format(rowFormat, "lockwallet", "", "Remove wallet password from memory, locking the wallet"); - stream.format(rowFormat, "unlockwallet", "password timeout", + stream.format(rowFormat, "", "[--memo=<\"memo\">]", ""); + stream.println(); + stream.format(rowFormat, getpaymentmethods.name(), "", "Get list of supported payment account method ids"); + stream.println(); + stream.format(rowFormat, getpaymentacctform.name(), "--payment-method-id=", "Get a new payment account form"); + stream.println(); + stream.format(rowFormat, createpaymentacct.name(), "--payment-account-form=", "Create a new payment account"); + stream.println(); + stream.format(rowFormat, getpaymentaccts.name(), "", "Get user payment accounts"); + stream.println(); + stream.format(rowFormat, lockwallet.name(), "", "Remove wallet password from memory, locking the wallet"); + stream.println(); + stream.format(rowFormat, unlockwallet.name(), "--wallet-password= --timeout=", "Store wallet password in memory for timeout seconds"); - stream.format(rowFormat, "setwalletpassword", "password [newpassword]", + stream.println(); + stream.format(rowFormat, setwalletpassword.name(), "--wallet-password= \\", "Encrypt wallet with password, or set new password on encrypted wallet"); + stream.format(rowFormat, "", "[--new-wallet-password=]", ""); + stream.println(); + stream.println("Method Help Usage: bisq-cli [options] --help"); stream.println(); } catch (IOException ex) { ex.printStackTrace(stream); } } + + private static String getMethodHelp(HelpBlockingStub helpService, Method method) { + var request = GetMethodHelpRequest.newBuilder().setMethodName(method.name()).build(); + var reply = helpService.getMethodHelp(request); + return reply.getMethodHelp(); + } } From acf2c7c50ef1bf7c3f03f35c71a81ebc3b0e98eb Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Thu, 14 Jan 2021 10:27:47 -0300 Subject: [PATCH 06/10] Remove deprecated NegativeNumberOptions Not needed anymore, and all method opts are posix style. (The opts parsing lib used to treat negative numbers as opt labels.) --- .../java/bisq/cli/NegativeNumberOptions.java | 97 ------------------- 1 file changed, 97 deletions(-) delete mode 100644 cli/src/main/java/bisq/cli/NegativeNumberOptions.java diff --git a/cli/src/main/java/bisq/cli/NegativeNumberOptions.java b/cli/src/main/java/bisq/cli/NegativeNumberOptions.java deleted file mode 100644 index 6623f9ad150..00000000000 --- a/cli/src/main/java/bisq/cli/NegativeNumberOptions.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * 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 java.math.BigDecimal; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Predicate; - -import static java.util.Arrays.stream; -import static java.util.stream.IntStream.range; - -class NegativeNumberOptions { - - private final Map negativeNumberParams = new HashMap<>(); - - String[] removeNegativeNumberOptions(String[] args) { - // Cache any negative number params that will be rejected by the parser. - // This should be called before command line parsing. - int skipped = getIndexOfMethodInArgs(args); - for (int i = skipped; i < args.length; i++) { - if (isNegativeNumber.test(args[i])) { - String param = args[i]; - negativeNumberParams.put(i - skipped, new BigDecimal(param).toString()); - // Substitute a zero placeholder at the index containing the - // negative number positional option value. - args[i] = "0"; - } - } - return args; - } - - List restoreNegativeNumberOptions(List nonOptionArgs) { - // Put cached negative number params into a clone of the nonOptionArgs list. - // This should be called after command line parsing. - if (!negativeNumberParams.isEmpty()) { - List nonOptionArgsClone = new ArrayList<>(nonOptionArgs); - negativeNumberParams.forEach((k, v) -> { - int idx = k; - nonOptionArgsClone.set(idx, v); - }); - return Collections.unmodifiableList(nonOptionArgsClone); - } else { - // This should never happen. Instances of this class should not be created - // if there are no negative number options. - return nonOptionArgs; - } - } - - static boolean hasNegativeNumberOptions(String[] args) { - return stream(args).anyMatch(isNegativeNumber); - } - - private static final Predicate isNegativeNumber = (param) -> { - if (param.length() > 1 && param.startsWith("-")) { - try { - new BigDecimal(param); - return true; - } catch (NumberFormatException ignored) { - // empty - } - } - return false; - }; - - private int getIndexOfMethodInArgs(String[] args) { - // The first argument that does not start with '-' or '--' is the method name. - // Skip over the --password=xyz [--host=s --port=n] options. - int skipped = range(0, args.length) - .filter(i -> !args[i].startsWith("-")) - .findFirst() - .orElse(-1); - if (skipped >= 0) - return skipped; - else - throw new IllegalArgumentException("required --password option not found"); - } -} From baf79e2b50024998e2d3bda8886cfa01ada1f041 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Thu, 14 Jan 2021 19:03:49 -0300 Subject: [PATCH 07/10] Make opt description more generic --- cli/src/main/java/bisq/cli/opts/GetTradeOptionParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/main/java/bisq/cli/opts/GetTradeOptionParser.java b/cli/src/main/java/bisq/cli/opts/GetTradeOptionParser.java index d1edf181063..2b7681f3c69 100644 --- a/cli/src/main/java/bisq/cli/opts/GetTradeOptionParser.java +++ b/cli/src/main/java/bisq/cli/opts/GetTradeOptionParser.java @@ -26,7 +26,7 @@ public class GetTradeOptionParser extends AbstractMethodOptionParser implements MethodOpts { - final OptionSpec tradeIdOpt = parser.accepts(OPT_TRADE_ID, "id of trade to get") + final OptionSpec tradeIdOpt = parser.accepts(OPT_TRADE_ID, "id of trade") .withRequiredArg() .defaultsTo(EMPTY); From f2a899917c19aa8337cb46c238dff9a8ca2387c6 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Thu, 14 Jan 2021 19:05:27 -0300 Subject: [PATCH 08/10] Fix default opt values --- .../main/java/bisq/cli/opts/CreateOfferOptionParser.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/src/main/java/bisq/cli/opts/CreateOfferOptionParser.java b/cli/src/main/java/bisq/cli/opts/CreateOfferOptionParser.java index 4cb6a24a4cc..d4d7c05c7ad 100644 --- a/cli/src/main/java/bisq/cli/opts/CreateOfferOptionParser.java +++ b/cli/src/main/java/bisq/cli/opts/CreateOfferOptionParser.java @@ -50,7 +50,7 @@ public class CreateOfferOptionParser extends AbstractMethodOptionParser implemen final OptionSpec mktPriceMarginOpt = parser.accepts(OPT_MKT_PRICE_MARGIN, "market btc price margin (%)") .withOptionalArg() - .defaultsTo(EMPTY); + .defaultsTo("0.00"); final OptionSpec fixedPriceOpt = parser.accepts(OPT_FIXED_PRICE, "fixed btc price") .withOptionalArg() @@ -119,7 +119,7 @@ public boolean isUsingMktPriceMargin() { @SuppressWarnings("unused") public String getMktPriceMargin() { - return isUsingMktPriceMargin() ? options.valueOf(mktPriceMarginOpt) : ""; + return isUsingMktPriceMargin() ? options.valueOf(mktPriceMarginOpt) : "0.00"; } public BigDecimal getMktPriceMarginAsBigDecimal() { @@ -127,7 +127,7 @@ public BigDecimal getMktPriceMarginAsBigDecimal() { } public String getFixedPrice() { - return options.has(fixedPriceOpt) ? options.valueOf(fixedPriceOpt) : ""; + return options.has(fixedPriceOpt) ? options.valueOf(fixedPriceOpt) : "0.00"; } public String getSecurityDeposit() { From ec9f783eb5be49e92f8dcffd0a13150e7ce352fb Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Fri, 15 Jan 2021 16:25:39 -0300 Subject: [PATCH 09/10] Remove switch(hardcoded enum.name) There is no need for a switch here, the doc exists or it does not. --- .../java/bisq/core/api/CoreHelpService.java | 24 +++++-------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/bisq/core/api/CoreHelpService.java b/core/src/main/java/bisq/core/api/CoreHelpService.java index a294dcad627..a562856bbbf 100644 --- a/core/src/main/java/bisq/core/api/CoreHelpService.java +++ b/core/src/main/java/bisq/core/api/CoreHelpService.java @@ -40,21 +40,7 @@ public CoreHelpService() { } public String getMethodHelp(String methodName) { - switch (methodName) { - case "createoffer": - case "getfundingaddresses": - case "getpaymentaccts": - case "getpaymentmethods": - case "gettxfeerate": - case "getunusedbsqaddress": - case "getversion": - case "lockwallet": - case "takeoffer": - case "unsettxfeerate": - return getHelpText(methodName); - default: - throw new IllegalStateException("no help found for " + methodName); - } + return getHelpText(methodName); } private String getHelpText(String methodName) { @@ -63,7 +49,7 @@ private String getHelpText(String methodName) { return readHelpFile(resourceFile); } catch (NullPointerException ex) { log.error("", ex); - throw new IllegalStateException(format("could not find %s help doc", methodName)); + throw new IllegalStateException(format("no help found for api method %s", methodName)); } catch (IOException ex) { log.error("", ex); throw new IllegalStateException(format("could not read %s help doc", methodName)); @@ -87,7 +73,7 @@ private String readHelpFile(String resourceFile) throws NullPointerException, IO @SuppressWarnings("CommentedOutCode") public static void main(String[] args) { CoreHelpService coreHelpService = new CoreHelpService(); - // out.println(coreHelpService.getMethodHelp("getversion")); + out.println(coreHelpService.getMethodHelp("getversion")); // out.println(coreHelpService.getMethodHelp("getfundingaddresses")); // out.println(coreHelpService.getMethodHelp("getfundingaddresses")); // out.println(coreHelpService.getMethodHelp("getunusedbsqaddress")); @@ -96,8 +82,10 @@ public static void main(String[] args) { // out.println(coreHelpService.getMethodHelp("getpaymentaccts")); // out.println(coreHelpService.getMethodHelp("lockwallet")); // out.println(coreHelpService.getMethodHelp("gettxfeerate")); - out.println(coreHelpService.getMethodHelp("createoffer")); + // out.println(coreHelpService.getMethodHelp("createoffer")); // out.println(coreHelpService.getMethodHelp("takeoffer")); // out.println(coreHelpService.getMethodHelp("garbage")); + // out.println(coreHelpService.getMethodHelp("")); + // out.println(coreHelpService.getMethodHelp(null)); } } From 1ad7b351b62de1f4e50f752b94952a49bb1ccbf5 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Fri, 15 Jan 2021 16:33:52 -0300 Subject: [PATCH 10/10] Remove uneeded method --- core/src/main/java/bisq/core/api/CoreHelpService.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/src/main/java/bisq/core/api/CoreHelpService.java b/core/src/main/java/bisq/core/api/CoreHelpService.java index a562856bbbf..6a4605f7484 100644 --- a/core/src/main/java/bisq/core/api/CoreHelpService.java +++ b/core/src/main/java/bisq/core/api/CoreHelpService.java @@ -40,10 +40,6 @@ public CoreHelpService() { } public String getMethodHelp(String methodName) { - return getHelpText(methodName); - } - - private String getHelpText(String methodName) { String resourceFile = "/help" + separator + methodName + "-" + "help.txt"; try { return readHelpFile(resourceFile);