diff --git a/cli/src/main/java/bisq/cli/CliMain.java b/cli/src/main/java/bisq/cli/CliMain.java index 4dae0c6515..e5f94c3a98 100644 --- a/cli/src/main/java/bisq/cli/CliMain.java +++ b/cli/src/main/java/bisq/cli/CliMain.java @@ -80,6 +80,7 @@ import bisq.cli.opts.SendBtcOptionParser; import bisq.cli.opts.SetTxFeeRateOptionParser; import bisq.cli.opts.SetWalletPasswordOptionParser; import bisq.cli.opts.SimpleMethodOptionParser; +import bisq.cli.opts.TakeBsqSwapOfferOptionParser; import bisq.cli.opts.TakeOfferOptionParser; import bisq.cli.opts.UnlockWalletOptionParser; import bisq.cli.opts.VerifyBsqSentToAddressOptionParser; @@ -504,12 +505,15 @@ public class CliMain { // 'takeoffer' request. var offerCategory = client.getAvailableOfferCategory(offerId); if (offerCategory.equals(BSQ_SWAP)) { - trade = client.takeBsqSwapOffer(offerId); + var opts = new TakeBsqSwapOfferOptionParser(args).parse(); + var amount = toSatoshis(opts.getAmount()); + trade = client.takeBsqSwapOffer(offerId, amount); } else { var opts = new TakeOfferOptionParser(args).parse(); + var amount = toSatoshis(opts.getAmount()); var paymentAccountId = opts.getPaymentAccountId(); var takerFeeCurrencyCode = opts.getTakerFeeCurrencyCode(); - trade = client.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode); + trade = client.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode, amount); } out.printf("trade %s successfully taken%n", trade.getTradeId()); return; @@ -912,6 +916,7 @@ public class CliMain { stream.format(rowFormat, takeoffer.name(), "--offer-id= \\", "Take offer with id"); stream.format(rowFormat, "", "[--payment-account=]", ""); stream.format(rowFormat, "", "[--fee-currency=]", ""); + stream.format(rowFormat, "", "[--amount== amount <= btc-amount>]", ""); stream.println(); stream.format(rowFormat, gettrade.name(), "--trade-id= \\", "Get trade summary or full contract"); stream.format(rowFormat, "", "[--show-contract=]", ""); diff --git a/cli/src/main/java/bisq/cli/GrpcClient.java b/cli/src/main/java/bisq/cli/GrpcClient.java index ab44ff8776..2d5e4f6d99 100644 --- a/cli/src/main/java/bisq/cli/GrpcClient.java +++ b/cli/src/main/java/bisq/cli/GrpcClient.java @@ -325,12 +325,18 @@ public final class GrpcClient { return offersServiceRequest.getMyBsqSwapOffersSortedByDate(); } - public TradeInfo takeBsqSwapOffer(String offerId) { - return tradesServiceRequest.takeBsqSwapOffer(offerId); + public TradeInfo takeBsqSwapOffer(String offerId, long amount) { + return tradesServiceRequest.takeBsqSwapOffer(offerId, amount); } - public TradeInfo takeOffer(String offerId, String paymentAccountId, String takerFeeCurrencyCode) { - return tradesServiceRequest.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode); + public TradeInfo takeOffer(String offerId, + String paymentAccountId, + String takerFeeCurrencyCode, + long amount) { + return tradesServiceRequest.takeOffer(offerId, + paymentAccountId, + takerFeeCurrencyCode, + amount); } public TradeInfo getTrade(String tradeId) { diff --git a/cli/src/main/java/bisq/cli/opts/TakeBsqSwapOfferOptionParser.java b/cli/src/main/java/bisq/cli/opts/TakeBsqSwapOfferOptionParser.java new file mode 100644 index 0000000000..e3301b88b3 --- /dev/null +++ b/cli/src/main/java/bisq/cli/opts/TakeBsqSwapOfferOptionParser.java @@ -0,0 +1,60 @@ +/* + * 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.CurrencyFormat.toSatoshis; +import static bisq.cli.opts.OptLabel.OPT_AMOUNT; +import static bisq.cli.opts.OptLabel.OPT_FEE_CURRENCY; +import static bisq.cli.opts.OptLabel.OPT_PAYMENT_ACCOUNT_ID; + +public class TakeBsqSwapOfferOptionParser extends OfferIdOptionParser implements MethodOpts { + + final OptionSpec amountOpt = parser.accepts(OPT_AMOUNT, "intended amount of btc to buy or sell") + .withRequiredArg() + .defaultsTo("0"); + + public TakeBsqSwapOfferOptionParser(String[] args) { + super(args, true); + } + + public TakeBsqSwapOfferOptionParser parse() { + super.parse(); + + // Super class will short-circuit parsing if help option is present. + + if (options.has(amountOpt)) { + if (options.valueOf(amountOpt).isEmpty()) + throw new IllegalArgumentException("no intended btc trade amount specified"); + + try { + toSatoshis(options.valueOf(amountOpt)); + } catch (IllegalArgumentException ex) { + throw new IllegalArgumentException("invalid amount: " + ex.getMessage()); + } + } + + return this; + } + + public String getAmount() { + return options.valueOf(amountOpt); + } +} diff --git a/cli/src/main/java/bisq/cli/opts/TakeOfferOptionParser.java b/cli/src/main/java/bisq/cli/opts/TakeOfferOptionParser.java index 45602a26a7..a117a26bb2 100644 --- a/cli/src/main/java/bisq/cli/opts/TakeOfferOptionParser.java +++ b/cli/src/main/java/bisq/cli/opts/TakeOfferOptionParser.java @@ -20,11 +20,16 @@ package bisq.cli.opts; import joptsimple.OptionSpec; +import static bisq.cli.CurrencyFormat.toSatoshis; +import static bisq.cli.opts.OptLabel.OPT_AMOUNT; import static bisq.cli.opts.OptLabel.OPT_FEE_CURRENCY; import static bisq.cli.opts.OptLabel.OPT_PAYMENT_ACCOUNT_ID; public class TakeOfferOptionParser extends OfferIdOptionParser implements MethodOpts { + final OptionSpec amountOpt = parser.accepts(OPT_AMOUNT, "intended amount of btc to buy or sell") + .withRequiredArg() + .defaultsTo("0"); final OptionSpec paymentAccountIdOpt = parser.accepts(OPT_PAYMENT_ACCOUNT_ID, "id of payment account used for trade") .withRequiredArg(); @@ -41,12 +46,27 @@ public class TakeOfferOptionParser extends OfferIdOptionParser implements Method // Super class will short-circuit parsing if help option is present. + if (options.has(amountOpt)) { + if (options.valueOf(amountOpt).isEmpty()) + throw new IllegalArgumentException("no intended btc trade amount specified"); + + try { + toSatoshis(options.valueOf(amountOpt)); + } catch (IllegalArgumentException ex) { + throw new IllegalArgumentException("invalid amount: " + ex.getMessage()); + } + } + if (!options.has(paymentAccountIdOpt) || options.valueOf(paymentAccountIdOpt).isEmpty()) throw new IllegalArgumentException("no payment account id specified"); return this; } + public String getAmount() { + return options.valueOf(amountOpt); + } + public String getPaymentAccountId() { return options.valueOf(paymentAccountIdOpt); } diff --git a/cli/src/main/java/bisq/cli/request/TradesServiceRequest.java b/cli/src/main/java/bisq/cli/request/TradesServiceRequest.java index 42ec3486fd..a2292dd601 100644 --- a/cli/src/main/java/bisq/cli/request/TradesServiceRequest.java +++ b/cli/src/main/java/bisq/cli/request/TradesServiceRequest.java @@ -46,25 +46,38 @@ public class TradesServiceRequest { this.grpcStubs = grpcStubs; } - public TakeOfferReply getTakeOfferReply(String offerId, String paymentAccountId, String takerFeeCurrencyCode) { + public TakeOfferReply getTakeOfferReply(String offerId, + String paymentAccountId, + String takerFeeCurrencyCode, + long amount) { var request = TakeOfferRequest.newBuilder() .setOfferId(offerId) .setPaymentAccountId(paymentAccountId) .setTakerFeeCurrencyCode(takerFeeCurrencyCode) + .setAmount(amount) .build(); return grpcStubs.tradesService.takeOffer(request); } - public TradeInfo takeBsqSwapOffer(String offerId) { - var reply = getTakeOfferReply(offerId, "", ""); + public TradeInfo takeBsqSwapOffer(String offerId, long amount) { + var reply = getTakeOfferReply(offerId, + "", + "", + amount); if (reply.hasTrade()) return reply.getTrade(); else throw new IllegalStateException(reply.getFailureReason().getDescription()); } - public TradeInfo takeOffer(String offerId, String paymentAccountId, String takerFeeCurrencyCode) { - var reply = getTakeOfferReply(offerId, paymentAccountId, takerFeeCurrencyCode); + public TradeInfo takeOffer(String offerId, + String paymentAccountId, + String takerFeeCurrencyCode, + long amount) { + var reply = getTakeOfferReply(offerId, + paymentAccountId, + takerFeeCurrencyCode, + amount); if (reply.hasTrade()) return reply.getTrade(); else diff --git a/cli/src/test/java/bisq/cli/opts/OptionParsersTest.java b/cli/src/test/java/bisq/cli/opts/OptionParsersTest.java index 562214bdaf..04c479889d 100644 --- a/cli/src/test/java/bisq/cli/opts/OptionParsersTest.java +++ b/cli/src/test/java/bisq/cli/opts/OptionParsersTest.java @@ -2,10 +2,8 @@ package bisq.cli.opts; import org.junit.jupiter.api.Test; -import static bisq.cli.Method.canceloffer; -import static bisq.cli.Method.createcryptopaymentacct; -import static bisq.cli.Method.createoffer; -import static bisq.cli.Method.createpaymentacct; +import static bisq.cli.Method.*; +import static bisq.cli.Method.takeoffer; import static bisq.cli.opts.OptLabel.*; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -383,4 +381,67 @@ public class OptionParsersTest { assertEquals(currencyCode, parser.getCurrencyCode()); assertEquals(address, parser.getAddress()); } + + + // takeoffer opt parser tests + + @Test + public void testTakeOfferForDefaultAmount() { + var offerId = "ABC-OFFER-ID"; + var paymentAccountId = "ABC-ACCT-ID"; + var takerFeeCurrencyCode = "BSQ"; + var defaultAmount = "0"; + String[] args = new String[]{ + PASSWORD_OPT, + takeoffer.name(), + "--" + OPT_OFFER_ID + "=" + offerId, + "--" + OPT_PAYMENT_ACCOUNT_ID + "=" + paymentAccountId, + "--" + OPT_FEE_CURRENCY + "=" + takerFeeCurrencyCode + }; + var parser = new TakeOfferOptionParser(args).parse(); + assertEquals(offerId, parser.getOfferId()); + assertEquals(paymentAccountId, parser.getPaymentAccountId()); + assertEquals(takerFeeCurrencyCode, parser.getTakerFeeCurrencyCode()); + assertEquals(defaultAmount, parser.getAmount()); + } + + @Test + public void testTakeOfferForNegativeAmount() { + var offerId = "ABC-OFFER-ID"; + var paymentAccountId = "ABC-ACCT-ID"; + var takerFeeCurrencyCode = "BSQ"; + var amount = "-0.05"; + String[] args = new String[]{ + PASSWORD_OPT, + takeoffer.name(), + "--" + OPT_OFFER_ID + "=" + offerId, + "--" + OPT_PAYMENT_ACCOUNT_ID + "=" + paymentAccountId, + "--" + OPT_FEE_CURRENCY + "=" + takerFeeCurrencyCode, + "--" + OPT_AMOUNT + "=" + amount + }; + Throwable exception = assertThrows(RuntimeException.class, () -> + new TakeOfferOptionParser(args).parse()); + assertEquals("invalid amount: '-0.05' is not a positive number", exception.getMessage()); + } + + @Test + public void testTakeOffer() { + var offerId = "ABC-OFFER-ID"; + var paymentAccountId = "ABC-ACCT-ID"; + var takerFeeCurrencyCode = "BSQ"; + var amount = "0.05"; + String[] args = new String[]{ + PASSWORD_OPT, + takeoffer.name(), + "--" + OPT_OFFER_ID + "=" + offerId, + "--" + OPT_PAYMENT_ACCOUNT_ID + "=" + paymentAccountId, + "--" + OPT_FEE_CURRENCY + "=" + takerFeeCurrencyCode, + "--" + OPT_AMOUNT + "=" + amount + }; + var parser = new TakeOfferOptionParser(args).parse(); + assertEquals(offerId, parser.getOfferId()); + assertEquals(paymentAccountId, parser.getPaymentAccountId()); + assertEquals(takerFeeCurrencyCode, parser.getTakerFeeCurrencyCode()); + assertEquals(amount, parser.getAmount()); + } }