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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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 d756d3dcf377418daf1523bf095921c26e56053a Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Thu, 14 Jan 2021 19:15:16 -0300 Subject: [PATCH 09/15] Add simple api trading script Emulates Bob and Alice trading with each other with regtest BTC. --- apitest/scripts/simple-trading-script.sh | 209 ++++++++++++++++++ apitest/scripts/trading-script-env.sh | 256 +++++++++++++++++++++++ 2 files changed, 465 insertions(+) create mode 100755 apitest/scripts/simple-trading-script.sh create mode 100755 apitest/scripts/trading-script-env.sh diff --git a/apitest/scripts/simple-trading-script.sh b/apitest/scripts/simple-trading-script.sh new file mode 100755 index 00000000000..0a156758de1 --- /dev/null +++ b/apitest/scripts/simple-trading-script.sh @@ -0,0 +1,209 @@ +#! /bin/bash + +# Script for running simple fiat <-> btc trading scenarios using the API CLI on regtest. +# +# Prerequisites: +# +# - Linux or OSX with bash, Java 10, or Java 11-12 (JDK language compatibility 10). +# +# - Bisq must be fully built with apitest dao setup files installed. +# Build command: `./gradlew clean build :apitest:installDaoSetup` +# +# - All supporting nodes must be run locally, in dev/dao/regtest mode: +# bitcoind, seednode, arbdaemon, alicedaemon, bobdaemon +# +# These should be run using the apitest harness. From the root project dir, run: +# `./bisq-apitest --apiPassword=xyz --supportingApps=bitcoind,seednode,arbdaemon,alicedaemon,bobdaemon --shutdownAfterTests=false` +# +# - Only regtest btc can be bought or sold with the default dummy eur account. +# The CLI supports creating other fiat payment accts but this script doesn't (todo). +# +# Usage: +# +# This script must be run from the root of the project, e.g.: +# +# `apitest/scripts/simple-trading-script.sh -d buy -c eur -m 3.00 -a 0.125` +# +# Script options: -d -c -m - f -a +# +# Examples: +# +# Create a buy/eur offer to buy 0.125 btc at a mkt-price-margin of 0%: +# +# `apitest/scripts/simple-trading-script.sh -d buy -c usd -m 0.00 -a 0.125` +# +# Create a sell/eur offer to sell 0.125 btc at a fixed-price of 23,000 euros: +# +# `apitest/scripts/simple-trading-script.sh -d sell -c usd -f 23000 -a 0.125` + +APP_BASE_NAME=`basename "$0"` +APP_HOME="`pwd -P`" +APITEST_SCRIPTS_HOME="${APP_HOME}/apitest/scripts" + +# Source the env and some helper functions. +. ${APITEST_SCRIPTS_HOME}/trading-script-env.sh + +checksetup +parseopts "$@" + +printdate "Started ${APP_BASE_NAME} with parameters:" +printscriptparams + +registerdisputeagents + +# Get balances. +printdate "Bob & Alice's balances before trade:" +printdate_sameline "ALICE CLI:" +printbalances $ALICE_PORT +printbreak + +printdate_sameline "BOB CLI:" +printbalances $BOB_PORT +printbreak + +printdate_sameline "ALICE CLI:" +getpaymentaccts ${ALICE_PORT} + +ALICE_ACCT_ID=$(getdummyacctid ${ALICE_PORT}) +printdate "ALICE ${ALICE_ROLE}: Fiat Acct ID: ${ALICE_ACCT_ID}" +printbreak + + +printdate_sameline "BOB CLI:" +getpaymentaccts ${BOB_PORT} + +BOB_ACCT_ID=$(getdummyacctid ${BOB_PORT}) +printdate "Bob's Fiat Acct ID: ${BOB_ACCT_ID}" +printbreak + +printdate "ALICE ${ALICE_ROLE}: Creating ${DIRECTION} ${CURRENCY_CODE} offer with payment acct ${ALICE_ACCT_ID}." +CMD="$CLI_BASE --port=${ALICE_PORT} createoffer" +CMD+=" --payment-account=${ALICE_ACCT_ID}" +CMD+=" --direction=${DIRECTION}" +CMD+=" --currency-code=${CURRENCY_CODE}" +CMD+=" --amount=${AMOUNT}" +if [ -z "${MKT_PRICE_MARGIN}" ]; then + CMD+=" --fixed-price=${FIXED_PRICE}" +else + CMD+=" --market-price-margin=${MKT_PRICE_MARGIN}" +fi +CMD+=" --security-deposit=15.0" +CMD+=" --fee-currency=BSQ" +printdate_sameline "ALICE CLI:" +printcmd "$CMD" + +OFFER_ID=$(createoffer "${CMD}") +printdate "ALICE ${ALICE_ROLE}: Created offer with id: ${OFFER_ID}." +printbreak +sleeptraced 10 + +# Generate some btc blocks. +printdate "Generating btc blocks after publishing Alice's offer." +genbtcblocks 3 5 +printbreak +sleeptraced 10 + +# List offers. +printdate "BOB ${BOB_ROLE}: Looking at ${DIRECTION} ${CURRENCY_CODE} offers." +CMD="$CLI_BASE --port=${BOB_PORT} getoffers --direction=${DIRECTION} --currency-code=${CURRENCY_CODE}" +printdate_sameline "BOB CLI:" +printcmd "$CMD" +OFFERS=$($CMD) +echo "${OFFERS}" +printbreak +sleeptraced 3 + +# Take offer. +printdate "BOB ${BOB_ROLE}: Taking offer ${OFFER_ID} with payment acct ${BOB_ACCT_ID}." +CMD="$CLI_BASE --port=${BOB_PORT} takeoffer --offer-id=${OFFER_ID} --payment-account=${BOB_ACCT_ID} --fee-currency=bsq" +printdate_sameline "BOB CLI:" +printcmd "$CMD" +TRADE=$($CMD) +echo "${TRADE}" +printbreak +sleeptraced 10 + +# Generating some btc blocks +printdate "Generating btc blocks after Bob takes Alice's offer." +genbtcblocks 3 3 +printbreak +sleeptraced 6 + +# Send payment sent and received messages. +if [ $DIRECTION = "BUY" ] +then + PAYER="ALICE ${ALICE_ROLE}" + PAYER_PORT=${ALICE_PORT} + PAYER_CLI="ALICE CLI" + PAYEE="BOB ${BOB_ROLE}" + PAYEE_PORT=${BOB_PORT} + PAYEE_CLI="BOB CLI" +else + PAYER="BOB ${BOB_ROLE}" + PAYER_PORT=${BOB_PORT} + PAYER_CLI="BOB CLI" + PAYEE="ALICE ${ALICE_ROLE}" + PAYEE_PORT=${ALICE_PORT} + PAYEE_CLI="ALICE CLI" +fi + +# Confirm payment started. +printdate "${PAYER}: Sending fiat payment sent msg." +CMD="$CLI_BASE --port=${PAYER_PORT} confirmpaymentstarted --trade-id=${OFFER_ID}" +printdate_sameline "${PAYER_CLI}:" +printcmd "$CMD" +SENT_MSG=$($CMD) +printdate "${SENT_MSG}" +printbreak + +sleeptraced 2 +printdate "Generating btc blocks after fiat payment sent msg." +genbtcblocks 3 5 +sleeptraced 2 + +# Confirm payment received. +printdate "${PAYEE}: Sending fiat payment received msg." +CMD="$CLI_BASE --port=${PAYEE_PORT} confirmpaymentreceived --trade-id=${OFFER_ID}" +printdate_sameline "${PAYEE_CLI}:" +printcmd "$CMD" +RCVD_MSG=$($CMD) +printdate "${RCVD_MSG}" +printbreak +sleeptraced 4 + + +# Generate some btc blocks +printdate "Generating btc blocks after fiat transfer." +genbtcblocks 3 5 +printbreak +sleeptraced 3 + +# Complete the trade on the seller side. +if [ $DIRECTION = "BUY" ] +then + printdate "BOB ${BOB_ROLE}: Closing trade by keeping funds in Bisq wallet." + CMD="$CLI_BASE --port=${BOB_PORT} keepfunds --trade-id=${OFFER_ID}" + printdate_sameline "BOB CLI:" + printcmd "$CMD" +else + printdate "ALICE (taker): Closing trade by keeping funds in Bisq wallet." + CMD="$CLI_BASE --port=${ALICE_PORT} keepfunds --trade-id=${OFFER_ID}" + printdate_sameline "ALICE CLI:" + printcmd "$CMD" +fi +KEEP_FUNDS_MSG=$($CMD) +printdate "${KEEP_FUNDS_MSG}" +sleeptraced 5 +printbreak + + +# Get balances after trade completion. +printdate "Bob & Alice's balances after trade:" +printdate_sameline "ALICE CLI:" +printbalances $ALICE_PORT +printbreak +printdate_sameline "BOB CLI:" +printbalances $BOB_PORT +printbreak + +exit 0 diff --git a/apitest/scripts/trading-script-env.sh b/apitest/scripts/trading-script-env.sh new file mode 100755 index 00000000000..65ea614da2e --- /dev/null +++ b/apitest/scripts/trading-script-env.sh @@ -0,0 +1,256 @@ +#! /bin/bash + +# This file must be sourced by the main driver. + +export CLI_BASE="./bisq-cli --password=xyz" +export ALICE_PORT=9998 +export BOB_PORT=9999 + +printdate() { + echo "[`date`] $@" +} + +printdate "Started ${APP_HOME}/${APP_BASE_NAME}." + +checksetup() { + apitestusage() { + echo "The apitest harness must be running a local bitcoin regtest node, a seednode, arbitration node, and Bob & Alice daemons." + echo "" + echo "From the project's root dir, start all supporting nodes from a terminal:" + echo "./bisq-apitest --apiPassword=xyz --supportingApps=bitcoind,seednode,arbdaemon,alicedaemon,bobdaemon --shutdownAfterTests=false" + echo "" + echo "Register dispute agents in the arbitration daemon after it initializes." + echo "./bisq-cli --password=xyz --port=9997 registerdisputeagent --dispute-agent-type=mediator \ +# --registration-key=6ac43ea1df2a290c1c8391736aa42e4339c5cb4f110ff0257a13b63211977b7a" + echo "./bisq-cli --password=xyz --port=9997 registerdisputeagent --dispute-agent-type=refundagent \ +# --registration-key=6ac43ea1df2a290c1c8391736aa42e4339c5cb4f110ff0257a13b63211977b7a" + exit 1; + } + printdate "Checking ${APP_HOME} for some expected directories and files." + if [ -d "${APP_HOME}/apitest" ]; then + printdate "Subproject apitest exists."; + else + printdate "Error: Subproject apitest not found, maybe because you are not running the script from the project root dir." + exit 1 + fi + if [ -f "${APP_HOME}/bisq-cli" ]; then + printdate "The bisq-cli script exists."; + else + printdate "Error: The bisq-cli script not found, maybe because you are not running the script from the project root dir." + exit 1 + fi + printdate "Checking to see local bitcoind is running." + checkbitcoindrunning + printdate "Checking to see bisq servers are running." + if pgrep -f "bisq.seednode.SeedNodeMain" > /dev/null ; then + printdate "The seednode is running on host." + else + printdate "Error: seed is not running on host, exiting." + apitestusage + fi + if pgrep -f "bisq.daemon.app.BisqDaemonMain --appName=bisq-BTC_REGTEST_Arb_dao" > /dev/null ; then + printdate "The arbitration node is running on host." + else + printdate "Error: arbitration node is not running on host, exiting." + apitestusage + fi + if pgrep -f "bisq.daemon.app.BisqDaemonMain --appName=bisq-BTC_REGTEST_Alice_dao" > /dev/null ; then + printdate "Alice's daemon node is running on host." + else + printdate "Error: Alice's daemon node is not running on host, exiting." + apitestusage + fi + if pgrep -f "bisq.daemon.app.BisqDaemonMain --appName=bisq-BTC_REGTEST_Bob_dao" > /dev/null ; then + printdate "Bob's daemon node is running on host." + else + printdate "Error: Bob's daemon node is not running on host, exiting." + apitestusage + fi +} + +parseopts() { + usage() { + echo "Usage: $0 [-d buy|sell] [-c ] [-f || -m ] [-a ]" 1>&2 + exit 1; + } + + local OPTIND o d c f m a + while getopts "d:c:f:m:a:" o; do + case "${o}" in + d) d=$(echo ${OPTARG} | tr '[:lower:]' '[:upper:]') + ((d == "BUY" || d == "SELL")) || usage + export DIRECTION=${d} + ;; + c) c=$(echo ${OPTARG} | tr '[:lower:]' '[:upper:]') + export CURRENCY_CODE=${c} + ;; + f) f=${OPTARG} + export FIXED_PRICE=${f} + ;; + m) m=${OPTARG} + export MKT_PRICE_MARGIN=${m} + ;; + a) a=${OPTARG} + export AMOUNT=${a} + ;; + *) usage ;; + esac + done + shift $((OPTIND-1)) + + if [ -z "${d}" ] || [ -z "${c}" ] || [ -z "${a}" ]; then + usage + fi + + if [ -z "${f}" ] && [ -z "${m}" ]; then + usage + fi + + if [ -n "${f}" ] && [ -n "${m}" ]; then + printdate "Must use margin-from-price param (-m) or fixed-price param (-f), not both." + usage + fi + + if [ $DIRECTION = "SELL" ] + then + export BOB_ROLE="Bob (taker/buyer)" + export ALICE_ROLE="Alice (maker/seller)" + else + export BOB_ROLE="Bob (taker/seller)" + export ALICE_ROLE="Alice (maker/buyer)" + fi +} + +printscriptparams() { + echo " DIRECTION = ${DIRECTION}" + echo " CURRENCY_CODE = ${CURRENCY_CODE}" + echo " FIXED_PRICE = ${FIXED_PRICE}" + echo " MKT_PRICE_MARGIN = ${MKT_PRICE_MARGIN}" + echo " AMOUNT = ${AMOUNT}" + echo " BOB_ROLE = ${BOB_ROLE}" + echo " ALICE_ROLE = ${ALICE_ROLE}" +} + +checkbitcoindrunning() { + # There may be a '+' char in the path and we have to escape it for pgrep. + if [[ ${APP_HOME} == *"+"* ]]; then + ESCAPED_APP_HOME=$(escapepluschar ${APP_HOME}) + else + ESCAPED_APP_HOME=${APP_HOME} + fi + if pgrep -f "bitcoind -datadir=${ESCAPED_APP_HOME}/apitest/build/resources/main/Bitcoin-regtest" > /dev/null ; then + printdate "The regtest bitcoind node is running on host." + else + printdate "Error: regtest bitcoind node is not running on host, exiting." + apitestusage + fi +} + +registerdisputeagents() { + # Silently register dev dispute agents. It's easy to forget. + REG_KEY="6ac43ea1df2a290c1c8391736aa42e4339c5cb4f110ff0257a13b63211977b7a" + SILENT=$(./bisq-cli --password=xyz --port=9997 registerdisputeagent --dispute-agent-type=mediator --registration-key=${REG_KEY}) + SILENT=$(./bisq-cli --password=xyz --port=9997 registerdisputeagent --dispute-agent-type=refundagent --registration-key=${REG_KEY}) +} + + +printbreak() { + echo "" + echo "" +} + +printcmd() { + echo -en "$@\n" +} + +printdate_sameline() { + echo -n "[`date`] $@ " +} + +sleeptraced() { + PERIOD=$1 + printdate "sleeping for $PERIOD" + sleep $PERIOD +} + +printbalances() { + PORT=$1 + printcmd "${CLI_BASE} --port=${PORT} getbalance" + $CLI_BASE --port=${PORT} getbalance +} + +getpaymentaccts() { + PORT=$1 + printcmd "${CLI_BASE} --port=${PORT} getpaymentaccts" + $CLI_BASE --port=${PORT} getpaymentaccts +} + +getdummyacctid() { + PORT=$1 + PAYMENT_ACCTS=$(${CLI_BASE} --port=${PORT} getpaymentaccts) + DUMMY_ACCT_1=$(echo -e "${PAYMENT_ACCTS}" | sed -n '2p') + DUMMY_ACCT_2=$(echo -e "${PAYMENT_ACCTS}" | sed -n '3p') + if [[ "$DUMMY_ACCT_1=" == *"PerfectMoney dummy"* ]]; then + DUMMY_ACCT=$DUMMY_ACCT_1 + else + DUMMY_ACCT=$DUMMY_ACCT_2 + fi + ACCT_ID=$(echo -e $DUMMY_ACCT | awk '{print $NF}') + echo "${ACCT_ID}" +} + +createoffer() { + CREATE_OFFER_CMD=$1 + OFFER_DESC=$($CREATE_OFFER_CMD) + + ### This is an effort to handling any createoffer error. If error, should echo Error, and the calling script + ### should sleep awhile then exit 1. + if [[ "$OFFER_DESC" != "Buy/Sell"* ]]; then + echo "==========================================================" + echo "Error: ${OFFER_DESC}" + echo "==========================================================" + sleep 5 + fi + + OFFER_DETAIL=$(echo -e "${OFFER_DESC}" | sed -n '2p') + NEW_OFFER_ID=$(echo -e ${OFFER_DETAIL} | awk '{print $NF}') + echo "${NEW_OFFER_ID}" +} + +genbtcblocks() { + NUM_BLOCKS=$1 + SECONDS_BETWEEN_BLOCKS=$2 + for i in $(seq -f "%02g" 1 ${NUM_BLOCKS}) + do + genbtcblock + sleep ${SECONDS_BETWEEN_BLOCKS} + done +} + +genbtcblock() { + # TODO use bitcoin-cli to get new address; cannot assume same address exists on other regtest bitcoin-core hosts. + CMD='bitcoin-cli -regtest -rpcport=19443 -rpcuser=apitest -rpcpassword=apitest generatetoaddress 1 "2N5J6MyjAsWnashimGiNwoRzUXThsQzRmbv"' + printcmd "$CMD" + bitcoin-cli -regtest -rpcport=19443 -rpcuser=apitest -rpcpassword=apitest generatetoaddress 1 "2N5J6MyjAsWnashimGiNwoRzUXThsQzRmbv" +} + +escapepluschar() { + STRING=$1 + NEW_STRING=$(sed 's/+/\\&/g' <<< ${STRING}) + echo "${NEW_STRING}" +} + +# Keep this in case there is a need to ready user input from stdin. +readYesOrNo() { + question=$1 + echo -n "$question Yes or No: " + read answer + answer=`echo $answer | tr [a-z] [A-Z]` + if [ $answer = Y ] + then + echo You answered yes: $answer + else + echo You answered no: $answer + fi + return 0 +} From 9a935d289d40d5a367a5b67085921cb5e59f7e5b Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Fri, 15 Jan 2021 13:08:16 -0300 Subject: [PATCH 10/15] Fix bash syntax problems (codacy) Get new btc core address before generating btc blocks. --- apitest/scripts/simple-trading-script.sh | 23 ++++++----- apitest/scripts/trading-script-env.sh | 51 ++++++++++++++---------- 2 files changed, 42 insertions(+), 32 deletions(-) diff --git a/apitest/scripts/simple-trading-script.sh b/apitest/scripts/simple-trading-script.sh index 0a156758de1..5eb62026e50 100755 --- a/apitest/scripts/simple-trading-script.sh +++ b/apitest/scripts/simple-trading-script.sh @@ -36,43 +36,44 @@ # # `apitest/scripts/simple-trading-script.sh -d sell -c usd -f 23000 -a 0.125` -APP_BASE_NAME=`basename "$0"` -APP_HOME="`pwd -P`" + +APP_BASE_NAME=$(basename "$0") +APP_HOME=$(pwd -P) APITEST_SCRIPTS_HOME="${APP_HOME}/apitest/scripts" # Source the env and some helper functions. -. ${APITEST_SCRIPTS_HOME}/trading-script-env.sh +. "${APITEST_SCRIPTS_HOME}/trading-script-env.sh" checksetup parseopts "$@" printdate "Started ${APP_BASE_NAME} with parameters:" printscriptparams +printbreak registerdisputeagents # Get balances. printdate "Bob & Alice's balances before trade:" printdate_sameline "ALICE CLI:" -printbalances $ALICE_PORT +printbalances "$ALICE_PORT" printbreak printdate_sameline "BOB CLI:" -printbalances $BOB_PORT +printbalances "$BOB_PORT" printbreak printdate_sameline "ALICE CLI:" -getpaymentaccts ${ALICE_PORT} +getpaymentaccts "$ALICE_PORT" -ALICE_ACCT_ID=$(getdummyacctid ${ALICE_PORT}) +ALICE_ACCT_ID=$(getdummyacctid "$ALICE_PORT") printdate "ALICE ${ALICE_ROLE}: Fiat Acct ID: ${ALICE_ACCT_ID}" printbreak - printdate_sameline "BOB CLI:" getpaymentaccts ${BOB_PORT} -BOB_ACCT_ID=$(getdummyacctid ${BOB_PORT}) +BOB_ACCT_ID=$(getdummyacctid "$BOB_PORT") printdate "Bob's Fiat Acct ID: ${BOB_ACCT_ID}" printbreak @@ -130,7 +131,7 @@ printbreak sleeptraced 6 # Send payment sent and received messages. -if [ $DIRECTION = "BUY" ] +if [ "${DIRECTION}" = "BUY" ] then PAYER="ALICE ${ALICE_ROLE}" PAYER_PORT=${ALICE_PORT} @@ -179,7 +180,7 @@ printbreak sleeptraced 3 # Complete the trade on the seller side. -if [ $DIRECTION = "BUY" ] +if [ "${DIRECTION}" = "BUY" ] then printdate "BOB ${BOB_ROLE}: Closing trade by keeping funds in Bisq wallet." CMD="$CLI_BASE --port=${BOB_PORT} keepfunds --trade-id=${OFFER_ID}" diff --git a/apitest/scripts/trading-script-env.sh b/apitest/scripts/trading-script-env.sh index 65ea614da2e..a660b91086e 100755 --- a/apitest/scripts/trading-script-env.sh +++ b/apitest/scripts/trading-script-env.sh @@ -7,7 +7,7 @@ export ALICE_PORT=9998 export BOB_PORT=9999 printdate() { - echo "[`date`] $@" + echo "[$(date)] $@" } printdate "Started ${APP_HOME}/${APP_BASE_NAME}." @@ -21,9 +21,9 @@ checksetup() { echo "" echo "Register dispute agents in the arbitration daemon after it initializes." echo "./bisq-cli --password=xyz --port=9997 registerdisputeagent --dispute-agent-type=mediator \ -# --registration-key=6ac43ea1df2a290c1c8391736aa42e4339c5cb4f110ff0257a13b63211977b7a" + --registration-key=6ac43ea1df2a290c1c8391736aa42e4339c5cb4f110ff0257a13b63211977b7a" echo "./bisq-cli --password=xyz --port=9997 registerdisputeagent --dispute-agent-type=refundagent \ -# --registration-key=6ac43ea1df2a290c1c8391736aa42e4339c5cb4f110ff0257a13b63211977b7a" + --registration-key=6ac43ea1df2a290c1c8391736aa42e4339c5cb4f110ff0257a13b63211977b7a" exit 1; } printdate "Checking ${APP_HOME} for some expected directories and files." @@ -164,30 +164,30 @@ printcmd() { } printdate_sameline() { - echo -n "[`date`] $@ " + echo -n "[$(date)] $@ " } sleeptraced() { PERIOD=$1 printdate "sleeping for $PERIOD" - sleep $PERIOD + sleep "$PERIOD" } printbalances() { PORT=$1 printcmd "${CLI_BASE} --port=${PORT} getbalance" - $CLI_BASE --port=${PORT} getbalance + $CLI_BASE --port="$PORT" getbalance } getpaymentaccts() { PORT=$1 printcmd "${CLI_BASE} --port=${PORT} getpaymentaccts" - $CLI_BASE --port=${PORT} getpaymentaccts + $CLI_BASE --port="$PORT" getpaymentaccts } getdummyacctid() { PORT=$1 - PAYMENT_ACCTS=$(${CLI_BASE} --port=${PORT} getpaymentaccts) + PAYMENT_ACCTS=$(${CLI_BASE} --port="$PORT" getpaymentaccts) DUMMY_ACCT_1=$(echo -e "${PAYMENT_ACCTS}" | sed -n '2p') DUMMY_ACCT_2=$(echo -e "${PAYMENT_ACCTS}" | sed -n '3p') if [[ "$DUMMY_ACCT_1=" == *"PerfectMoney dummy"* ]]; then @@ -195,7 +195,7 @@ getdummyacctid() { else DUMMY_ACCT=$DUMMY_ACCT_2 fi - ACCT_ID=$(echo -e $DUMMY_ACCT | awk '{print $NF}') + ACCT_ID=$(echo -e "$DUMMY_ACCT" | awk '{print $NF}') echo "${ACCT_ID}" } @@ -213,30 +213,39 @@ createoffer() { fi OFFER_DETAIL=$(echo -e "${OFFER_DESC}" | sed -n '2p') - NEW_OFFER_ID=$(echo -e ${OFFER_DETAIL} | awk '{print $NF}') + NEW_OFFER_ID=$(echo -e "${OFFER_DETAIL}" | awk '{print $NF}') echo "${NEW_OFFER_ID}" } +getbtcoreaddress() { + CMD="bitcoin-cli -regtest -rpcport=19443 -rpcuser=apitest -rpcpassword=apitest getnewaddress" + NEW_ADDRESS=$(${CMD}) + echo "${NEW_ADDRESS}" +} + genbtcblocks() { NUM_BLOCKS=$1 SECONDS_BETWEEN_BLOCKS=$2 + CMD="bitcoin-cli -regtest -rpcport=19443 -rpcuser=apitest -rpcpassword=apitest generatetoaddress 1 " + CMD+="$(getbtcoreaddress)" + printdate "$CMD" for i in $(seq -f "%02g" 1 ${NUM_BLOCKS}) do - genbtcblock - sleep ${SECONDS_BETWEEN_BLOCKS} + NEW_BLOCK_HASH=$(genbtcblock "$CMD") + printdate "Block Hash #$i:${NEW_BLOCK_HASH}" + sleep "$SECONDS_BETWEEN_BLOCKS" done } genbtcblock() { - # TODO use bitcoin-cli to get new address; cannot assume same address exists on other regtest bitcoin-core hosts. - CMD='bitcoin-cli -regtest -rpcport=19443 -rpcuser=apitest -rpcpassword=apitest generatetoaddress 1 "2N5J6MyjAsWnashimGiNwoRzUXThsQzRmbv"' - printcmd "$CMD" - bitcoin-cli -regtest -rpcport=19443 -rpcuser=apitest -rpcpassword=apitest generatetoaddress 1 "2N5J6MyjAsWnashimGiNwoRzUXThsQzRmbv" + CMD=$1 + NEW_BLOCK_HASH=$(${CMD} | sed -n '2p') + echo "$NEW_BLOCK_HASH" } escapepluschar() { STRING=$1 - NEW_STRING=$(sed 's/+/\\&/g' <<< ${STRING}) + NEW_STRING=$(echo "${STRING//+/\\+}") echo "${NEW_STRING}" } @@ -245,12 +254,12 @@ readYesOrNo() { question=$1 echo -n "$question Yes or No: " read answer - answer=`echo $answer | tr [a-z] [A-Z]` - if [ $answer = Y ] + answer=$(echo $answer | tr [a-z] [A-Z]) + if [ "$answer" = "Y" ] then - echo You answered yes: $answer + echo You answered yes: "$answer" else - echo You answered no: $answer + echo You answered no: "$answer" fi return 0 } From 240167861073d0fdb2e323fffba0a540132ced5c Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Fri, 15 Jan 2021 13:18:42 -0300 Subject: [PATCH 11/15] Fix ROLE defs --- apitest/scripts/trading-script-env.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apitest/scripts/trading-script-env.sh b/apitest/scripts/trading-script-env.sh index a660b91086e..38e0536f587 100755 --- a/apitest/scripts/trading-script-env.sh +++ b/apitest/scripts/trading-script-env.sh @@ -113,11 +113,11 @@ parseopts() { if [ $DIRECTION = "SELL" ] then - export BOB_ROLE="Bob (taker/buyer)" - export ALICE_ROLE="Alice (maker/seller)" + export BOB_ROLE="(taker/buyer)" + export ALICE_ROLE="(maker/seller)" else - export BOB_ROLE="Bob (taker/seller)" - export ALICE_ROLE="Alice (maker/buyer)" + export BOB_ROLE="(taker/seller)" + export ALICE_ROLE="(maker/buyer)" fi } From 9de014d941711ec110c4ec556dadedefd7f935c9 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Fri, 15 Jan 2021 13:40:50 -0300 Subject: [PATCH 12/15] Fix more syntax problems for codacy --- apitest/scripts/simple-trading-script.sh | 6 +++--- apitest/scripts/trading-script-env.sh | 25 ++++++++++++++---------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/apitest/scripts/simple-trading-script.sh b/apitest/scripts/simple-trading-script.sh index 5eb62026e50..188f140a278 100755 --- a/apitest/scripts/simple-trading-script.sh +++ b/apitest/scripts/simple-trading-script.sh @@ -71,7 +71,7 @@ printdate "ALICE ${ALICE_ROLE}: Fiat Acct ID: ${ALICE_ACCT_ID}" printbreak printdate_sameline "BOB CLI:" -getpaymentaccts ${BOB_PORT} +getpaymentaccts "$BOB_PORT" BOB_ACCT_ID=$(getdummyacctid "$BOB_PORT") printdate "Bob's Fiat Acct ID: ${BOB_ACCT_ID}" @@ -201,10 +201,10 @@ printbreak # Get balances after trade completion. printdate "Bob & Alice's balances after trade:" printdate_sameline "ALICE CLI:" -printbalances $ALICE_PORT +printbalances "$ALICE_PORT" printbreak printdate_sameline "BOB CLI:" -printbalances $BOB_PORT +printbalances "$BOB_PORT" printbreak exit 0 diff --git a/apitest/scripts/trading-script-env.sh b/apitest/scripts/trading-script-env.sh index 38e0536f587..d078c8cb00c 100755 --- a/apitest/scripts/trading-script-env.sh +++ b/apitest/scripts/trading-script-env.sh @@ -77,11 +77,11 @@ parseopts() { local OPTIND o d c f m a while getopts "d:c:f:m:a:" o; do case "${o}" in - d) d=$(echo ${OPTARG} | tr '[:lower:]' '[:upper:]') + d) d=$(echo "${OPTARG}" | tr '[:lower:]' '[:upper:]') ((d == "BUY" || d == "SELL")) || usage export DIRECTION=${d} ;; - c) c=$(echo ${OPTARG} | tr '[:lower:]' '[:upper:]') + c) c=$(echo "${OPTARG}"| tr '[:lower:]' '[:upper:]') export CURRENCY_CODE=${c} ;; f) f=${OPTARG} @@ -111,7 +111,7 @@ parseopts() { usage fi - if [ $DIRECTION = "SELL" ] + if [ "$DIRECTION" = "SELL" ] then export BOB_ROLE="(taker/buyer)" export ALICE_ROLE="(maker/seller)" @@ -134,9 +134,9 @@ printscriptparams() { checkbitcoindrunning() { # There may be a '+' char in the path and we have to escape it for pgrep. if [[ ${APP_HOME} == *"+"* ]]; then - ESCAPED_APP_HOME=$(escapepluschar ${APP_HOME}) + ESCAPED_APP_HOME=$(escapepluschar "${APP_HOME}") else - ESCAPED_APP_HOME=${APP_HOME} + ESCAPED_APP_HOME="${APP_HOME}" fi if pgrep -f "bitcoind -datadir=${ESCAPED_APP_HOME}/apitest/build/resources/main/Bitcoin-regtest" > /dev/null ; then printdate "The regtest bitcoind node is running on host." @@ -151,6 +151,8 @@ registerdisputeagents() { REG_KEY="6ac43ea1df2a290c1c8391736aa42e4339c5cb4f110ff0257a13b63211977b7a" SILENT=$(./bisq-cli --password=xyz --port=9997 registerdisputeagent --dispute-agent-type=mediator --registration-key=${REG_KEY}) SILENT=$(./bisq-cli --password=xyz --port=9997 registerdisputeagent --dispute-agent-type=refundagent --registration-key=${REG_KEY}) + # Do something with $SILENT to keep codacy happy. + echo "$SILENT" > /dev/null } @@ -226,10 +228,13 @@ getbtcoreaddress() { genbtcblocks() { NUM_BLOCKS=$1 SECONDS_BETWEEN_BLOCKS=$2 - CMD="bitcoin-cli -regtest -rpcport=19443 -rpcuser=apitest -rpcpassword=apitest generatetoaddress 1 " - CMD+="$(getbtcoreaddress)" - printdate "$CMD" - for i in $(seq -f "%02g" 1 ${NUM_BLOCKS}) + ADDR_PARAM="$(getbtcoreaddress)" + CMD_PREFIX="bitcoin-cli -regtest -rpcport=19443 -rpcuser=apitest -rpcpassword=apitest generatetoaddress 1" + # Print the generatetoaddress command with double quoted address param, to make it cut & pastable from the console. + printdate "$CMD_PREFIX \"$ADDR_PARAM\"" + # Now create the full generatetoaddress command to be run now. + CMD="$CMD_PREFIX $ADDR_PARAM" + for i in $(seq -f "%02g" 1 "$NUM_BLOCKS") do NEW_BLOCK_HASH=$(genbtcblock "$CMD") printdate "Block Hash #$i:${NEW_BLOCK_HASH}" @@ -254,7 +259,7 @@ readYesOrNo() { question=$1 echo -n "$question Yes or No: " read answer - answer=$(echo $answer | tr [a-z] [A-Z]) + answer=$(echo "$answer" | tr [a-z] [A-Z]) if [ "$answer" = "Y" ] then echo You answered yes: "$answer" From bb48a9d075ac316c01f7a23f3cbe550e154c5d13 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Fri, 15 Jan 2021 13:48:25 -0300 Subject: [PATCH 13/15] Fix log statement --- apitest/scripts/simple-trading-script.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apitest/scripts/simple-trading-script.sh b/apitest/scripts/simple-trading-script.sh index 188f140a278..b631cce3ebb 100755 --- a/apitest/scripts/simple-trading-script.sh +++ b/apitest/scripts/simple-trading-script.sh @@ -74,7 +74,7 @@ printdate_sameline "BOB CLI:" getpaymentaccts "$BOB_PORT" BOB_ACCT_ID=$(getdummyacctid "$BOB_PORT") -printdate "Bob's Fiat Acct ID: ${BOB_ACCT_ID}" +printdate "BOB ${BOB_ROLE}: Fiat Acct ID: ${BOB_ACCT_ID}" printbreak printdate "ALICE ${ALICE_ROLE}: Creating ${DIRECTION} ${CURRENCY_CODE} offer with payment acct ${ALICE_ACCT_ID}." From dcbb4b716b1557c258cb19160e1fc522fdca179a Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Mon, 18 Jan 2021 11:33:47 -0300 Subject: [PATCH 14/15] Fix inconsistent indendation problems --- apitest/scripts/trading-script-env.sh | 44 ++++++++++++--------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/apitest/scripts/trading-script-env.sh b/apitest/scripts/trading-script-env.sh index d078c8cb00c..6ef8733f505 100755 --- a/apitest/scripts/trading-script-env.sh +++ b/apitest/scripts/trading-script-env.sh @@ -188,35 +188,31 @@ getpaymentaccts() { } getdummyacctid() { - PORT=$1 - PAYMENT_ACCTS=$(${CLI_BASE} --port="$PORT" getpaymentaccts) - DUMMY_ACCT_1=$(echo -e "${PAYMENT_ACCTS}" | sed -n '2p') - DUMMY_ACCT_2=$(echo -e "${PAYMENT_ACCTS}" | sed -n '3p') - if [[ "$DUMMY_ACCT_1=" == *"PerfectMoney dummy"* ]]; then - DUMMY_ACCT=$DUMMY_ACCT_1 - else - DUMMY_ACCT=$DUMMY_ACCT_2 - fi - ACCT_ID=$(echo -e "$DUMMY_ACCT" | awk '{print $NF}') - echo "${ACCT_ID}" + PORT=$1 + PAYMENT_ACCTS=$(${CLI_BASE} --port="$PORT" getpaymentaccts) + DUMMY_ACCT_1=$(echo -e "${PAYMENT_ACCTS}" | sed -n '2p') + DUMMY_ACCT_2=$(echo -e "${PAYMENT_ACCTS}" | sed -n '3p') + if [[ "$DUMMY_ACCT_1=" == *"PerfectMoney dummy"* ]]; then + DUMMY_ACCT=$DUMMY_ACCT_1 + else + DUMMY_ACCT=$DUMMY_ACCT_2 + fi + ACCT_ID=$(echo -e "$DUMMY_ACCT" | awk '{print $NF}') + echo "${ACCT_ID}" } createoffer() { - CREATE_OFFER_CMD=$1 + CREATE_OFFER_CMD=$1 OFFER_DESC=$($CREATE_OFFER_CMD) - - ### This is an effort to handling any createoffer error. If error, should echo Error, and the calling script - ### should sleep awhile then exit 1. - if [[ "$OFFER_DESC" != "Buy/Sell"* ]]; then - echo "==========================================================" + if [[ "$OFFER_DESC" != "Buy/Sell"* ]]; then + echo "==========================================================" echo "Error: ${OFFER_DESC}" echo "==========================================================" - sleep 5 + else + OFFER_DETAIL=$(echo -e "${OFFER_DESC}" | sed -n '2p') + NEW_OFFER_ID=$(echo -e "${OFFER_DETAIL}" | awk '{print $NF}') + echo "${NEW_OFFER_ID}" fi - - OFFER_DETAIL=$(echo -e "${OFFER_DESC}" | sed -n '2p') - NEW_OFFER_ID=$(echo -e "${OFFER_DETAIL}" | awk '{print $NF}') - echo "${NEW_OFFER_ID}" } getbtcoreaddress() { @@ -249,8 +245,8 @@ genbtcblock() { } escapepluschar() { - STRING=$1 - NEW_STRING=$(echo "${STRING//+/\\+}") + STRING=$1 + NEW_STRING=$(echo "${STRING//+/\\+}") echo "${NEW_STRING}" } From be2fb31bf3957899a193dd5674edb5148c095cb0 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Mon, 18 Jan 2021 14:10:52 -0300 Subject: [PATCH 15/15] Fail fast if the CLI exits with a non-zero status code --- apitest/scripts/simple-trading-script.sh | 9 ++++++ apitest/scripts/trading-script-env.sh | 37 +++++++++++++++++------- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/apitest/scripts/simple-trading-script.sh b/apitest/scripts/simple-trading-script.sh index b631cce3ebb..5a87f10d70d 100755 --- a/apitest/scripts/simple-trading-script.sh +++ b/apitest/scripts/simple-trading-script.sh @@ -94,6 +94,7 @@ printdate_sameline "ALICE CLI:" printcmd "$CMD" OFFER_ID=$(createoffer "${CMD}") +exitoncommandalert $? printdate "ALICE ${ALICE_ROLE}: Created offer with id: ${OFFER_ID}." printbreak sleeptraced 10 @@ -120,6 +121,9 @@ CMD="$CLI_BASE --port=${BOB_PORT} takeoffer --offer-id=${OFFER_ID} --payment-acc printdate_sameline "BOB CLI:" printcmd "$CMD" TRADE=$($CMD) +# Will exit if takeoffer cmd fails. +commandalert $? "Take offer command" + echo "${TRADE}" printbreak sleeptraced 10 @@ -154,6 +158,9 @@ CMD="$CLI_BASE --port=${PAYER_PORT} confirmpaymentstarted --trade-id=${OFFER_ID} printdate_sameline "${PAYER_CLI}:" printcmd "$CMD" SENT_MSG=$($CMD) +# Will exit if confirmpaymentstarted cmd fails. +commandalert $? "The confirmpaymentstarted command" + printdate "${SENT_MSG}" printbreak @@ -168,6 +175,7 @@ CMD="$CLI_BASE --port=${PAYEE_PORT} confirmpaymentreceived --trade-id=${OFFER_ID printdate_sameline "${PAYEE_CLI}:" printcmd "$CMD" RCVD_MSG=$($CMD) +commandalert $? "The confirmpaymentreceived command" printdate "${RCVD_MSG}" printbreak sleeptraced 4 @@ -193,6 +201,7 @@ else printcmd "$CMD" fi KEEP_FUNDS_MSG=$($CMD) +commandalert $? "The keepfunds command" printdate "${KEEP_FUNDS_MSG}" sleeptraced 5 printbreak diff --git a/apitest/scripts/trading-script-env.sh b/apitest/scripts/trading-script-env.sh index 6ef8733f505..b75ff0a91a0 100755 --- a/apitest/scripts/trading-script-env.sh +++ b/apitest/scripts/trading-script-env.sh @@ -6,6 +6,25 @@ export CLI_BASE="./bisq-cli --password=xyz" export ALICE_PORT=9998 export BOB_PORT=9999 +commandalert() { + # Used in a script function when it needs to fail early with an error message, & pass the error code to the caller. + # usage: commandalert <$?> + if [ "$1" -ne 0 ] + then + printdate "Error: $2 did not complete successfully." >&2 + exit $1 + fi +} + +exitoncommandalert() { + # Used in a parent script when you need it to fail immediately, with no error message. + # usage: exitoncommandalert <$?> + if [ "$1" -ne 0 ] + then + exit $1 + fi +} + printdate() { echo "[$(date)] $@" } @@ -155,7 +174,6 @@ registerdisputeagents() { echo "$SILENT" > /dev/null } - printbreak() { echo "" echo "" @@ -204,15 +222,14 @@ getdummyacctid() { createoffer() { CREATE_OFFER_CMD=$1 OFFER_DESC=$($CREATE_OFFER_CMD) - if [[ "$OFFER_DESC" != "Buy/Sell"* ]]; then - echo "==========================================================" - echo "Error: ${OFFER_DESC}" - echo "==========================================================" - else - OFFER_DETAIL=$(echo -e "${OFFER_DESC}" | sed -n '2p') - NEW_OFFER_ID=$(echo -e "${OFFER_DETAIL}" | awk '{print $NF}') - echo "${NEW_OFFER_ID}" - fi + + # If the CLI command exited with an error, print the CLI error, and + # return from this function now, passing the error status code to the caller. + commandalert $? "Create offer command" + + OFFER_DETAIL=$(echo -e "${OFFER_DESC}" | sed -n '2p') + NEW_OFFER_ID=$(echo -e "${OFFER_DETAIL}" | awk '{print $NF}') + echo "${NEW_OFFER_ID}" } getbtcoreaddress() {