From c3f5669cf88f13ebaae76927976b667ddbab1d2a Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Thu, 25 Mar 2021 18:36:26 -0300 Subject: [PATCH 1/4] Add api method createcryptopaymentacct This change supports creation of BSQ BLOCKCHAIN payment method accounts. - Added proto message defs to grpc.proto. - Added grpc server boilerplate to GrpcPaymentAccountsService. - Added server impl to CoreApi, CorePaymentAccountsService. - Added createcryptopaymentacct-help.txt. - Added CLI side support for new api method. - Added opt parsing unit tests to OptionParsersTest. This is the 1st PR in a series, with the goal of supporting the BTC/BSQ trading pair. Support for other crypto currency payment accounts will be added later. --- cli/src/main/java/bisq/cli/CliMain.java | 19 ++++ cli/src/main/java/bisq/cli/GrpcClient.java | 49 +++++++++- cli/src/main/java/bisq/cli/Method.java | 1 + .../main/java/bisq/cli/opts/ArgumentList.java | 1 + ...CryptoCurrencyPaymentAcctOptionParser.java | 75 ++++++++++++++++ .../main/java/bisq/cli/opts/MethodOpts.java | 1 - cli/src/main/java/bisq/cli/opts/OptLabel.java | 1 + .../java/bisq/cli/opt/OptionParsersTest.java | 89 ++++++++++++++++++- core/src/main/java/bisq/core/api/CoreApi.java | 8 ++ .../core/api/CorePaymentAccountsService.java | 50 ++++++++++- .../bisq/core/api/CoreWalletsService.java | 20 ++--- .../help/createcryptopaymentacct-help.txt | 39 ++++++++ .../grpc/GrpcPaymentAccountsService.java | 37 ++++++++ proto/src/main/proto/grpc.proto | 21 +++++ 14 files changed, 393 insertions(+), 18 deletions(-) create mode 100644 cli/src/main/java/bisq/cli/opts/CreateCryptoCurrencyPaymentAcctOptionParser.java create mode 100644 core/src/main/resources/help/createcryptopaymentacct-help.txt diff --git a/cli/src/main/java/bisq/cli/CliMain.java b/cli/src/main/java/bisq/cli/CliMain.java index 5e6e913711c..37bda937eb8 100644 --- a/cli/src/main/java/bisq/cli/CliMain.java +++ b/cli/src/main/java/bisq/cli/CliMain.java @@ -56,6 +56,7 @@ import bisq.cli.opts.ArgumentList; import bisq.cli.opts.CancelOfferOptionParser; +import bisq.cli.opts.CreateCryptoCurrencyPaymentAcctOptionParser; import bisq.cli.opts.CreateOfferOptionParser; import bisq.cli.opts.CreatePaymentAcctOptionParser; import bisq.cli.opts.GetAddressBalanceOptionParser; @@ -517,6 +518,22 @@ public static void run(String[] args) { out.println(formatPaymentAcctTbl(singletonList(paymentAccount))); return; } + case createcryptopaymentacct: { + var opts = new CreateCryptoCurrencyPaymentAcctOptionParser(args).parse(); + if (opts.isForHelp()) { + out.println(client.getMethodHelp(method)); + return; + } + var accountName = opts.getAccountName(); + var currencyCode = opts.getCurrencyCode(); + var address = opts.getAddress(); + var paymentAccount = client.createCryptoCurrencyPaymentAccount(accountName, + currencyCode, + address); + out.println("payment account saved"); + out.println(formatPaymentAcctTbl(singletonList(paymentAccount))); + return; + } case getpaymentaccts: { if (new SimpleMethodOptionParser(args).parse().isForHelp()) { out.println(client.getMethodHelp(method)); @@ -748,6 +765,8 @@ private static void printHelp(OptionParser parser, @SuppressWarnings("SameParame stream.println(); stream.format(rowFormat, createpaymentacct.name(), "--payment-account-form=", "Create a new payment account"); stream.println(); + stream.format(rowFormat, createcryptopaymentacct.name(), "--TODO=", "Create a new cryptocurrency 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"); diff --git a/cli/src/main/java/bisq/cli/GrpcClient.java b/cli/src/main/java/bisq/cli/GrpcClient.java index ea20c6aef88..5d3dde4eccb 100644 --- a/cli/src/main/java/bisq/cli/GrpcClient.java +++ b/cli/src/main/java/bisq/cli/GrpcClient.java @@ -24,10 +24,12 @@ import bisq.proto.grpc.CancelOfferRequest; import bisq.proto.grpc.ConfirmPaymentReceivedRequest; import bisq.proto.grpc.ConfirmPaymentStartedRequest; +import bisq.proto.grpc.CreateCryptoCurrencyPaymentAccountRequest; import bisq.proto.grpc.CreateOfferRequest; import bisq.proto.grpc.CreatePaymentAccountRequest; import bisq.proto.grpc.GetAddressBalanceRequest; import bisq.proto.grpc.GetBalancesRequest; +import bisq.proto.grpc.GetCryptoCurrencyPaymentMethodsRequest; import bisq.proto.grpc.GetFundingAddressesRequest; import bisq.proto.grpc.GetMethodHelpRequest; import bisq.proto.grpc.GetMyOfferRequest; @@ -67,11 +69,11 @@ import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import static java.util.Comparator.comparing; +import static java.util.stream.Collectors.toList; import static protobuf.OfferPayload.Direction.BUY; import static protobuf.OfferPayload.Direction.SELL; @@ -228,7 +230,6 @@ public OfferInfo createMarketBasedPricedOffer(String direction, makerFeeCurrencyCode); } - // TODO make private, move to bottom of class public OfferInfo createOffer(String direction, String currencyCode, long amount, @@ -283,6 +284,12 @@ public List getOffers(String direction, String currencyCode) { return grpcStubs.offersService.getOffers(request).getOffersList(); } + public List getBsqOffers(String direction) { + return getOffers(direction, "BTC").stream() + .filter(o -> o.getBaseCurrencyCode().equals("BSQ")) + .collect(toList()); + } + public List getOffersSortedByDate(String currencyCode) { ArrayList offers = new ArrayList<>(); offers.addAll(getOffers(BUY.name(), currencyCode)); @@ -295,6 +302,13 @@ public List getOffersSortedByDate(String direction, String currencyCo return offers.isEmpty() ? offers : sortOffersByDate(offers); } + public List getBsqOffersSortedByDate() { + ArrayList offers = new ArrayList<>(); + offers.addAll(getBsqOffers(BUY.name())); + offers.addAll(getBsqOffers(SELL.name())); + return sortOffersByDate(offers); + } + public List getMyOffers(String direction, String currencyCode) { var request = GetMyOffersRequest.newBuilder() .setDirection(direction) @@ -303,6 +317,12 @@ public List getMyOffers(String direction, String currencyCode) { return grpcStubs.offersService.getMyOffers(request).getOffersList(); } + public List getMyBsqOffers(String direction) { + return getMyOffers(direction, "BTC").stream() + .filter(o -> o.getBaseCurrencyCode().equals("BSQ")) + .collect(toList()); + } + public List getMyOffersSortedByDate(String direction, String currencyCode) { var offers = getMyOffers(direction, currencyCode); return offers.isEmpty() ? offers : sortOffersByDate(offers); @@ -315,6 +335,13 @@ public List getMyOffersSortedByDate(String currencyCode) { return sortOffersByDate(offers); } + public List getMyBsqOffersSortedByDate() { + ArrayList offers = new ArrayList<>(); + offers.addAll(getMyBsqOffers(BUY.name())); + offers.addAll(getMyBsqOffers(SELL.name())); + return sortOffersByDate(offers); + } + public OfferInfo getMostRecentOffer(String direction, String currencyCode) { List offers = getOffersSortedByDate(direction, currencyCode); return offers.isEmpty() ? null : offers.get(offers.size() - 1); @@ -323,7 +350,7 @@ public OfferInfo getMostRecentOffer(String direction, String currencyCode) { public List sortOffersByDate(List offerInfoList) { return offerInfoList.stream() .sorted(comparing(OfferInfo::getDate)) - .collect(Collectors.toList()); + .collect(toList()); } public TakeOfferReply getTakeOfferReply(String offerId, String paymentAccountId, String takerFeeCurrencyCode) { @@ -404,6 +431,22 @@ public List getPaymentAccounts() { return grpcStubs.paymentAccountsService.getPaymentAccounts(request).getPaymentAccountsList(); } + public PaymentAccount createCryptoCurrencyPaymentAccount(String accountName, + String currencyCode, + String address) { + var request = CreateCryptoCurrencyPaymentAccountRequest.newBuilder() + .setAccountName(accountName) + .setCurrencyCode(currencyCode) + .setAddress(address) + .build(); + return grpcStubs.paymentAccountsService.createCryptoCurrencyPaymentAccount(request).getPaymentAccount(); + } + + public List getCryptoPaymentMethods() { + var request = GetCryptoCurrencyPaymentMethodsRequest.newBuilder().build(); + return grpcStubs.paymentAccountsService.getCryptoCurrencyPaymentMethods(request).getPaymentMethodsList(); + } + public void lockWallet() { var request = LockWalletRequest.newBuilder().build(); grpcStubs.walletsService.lockWallet(request); diff --git a/cli/src/main/java/bisq/cli/Method.java b/cli/src/main/java/bisq/cli/Method.java index 67b582f13c6..908321a0902 100644 --- a/cli/src/main/java/bisq/cli/Method.java +++ b/cli/src/main/java/bisq/cli/Method.java @@ -26,6 +26,7 @@ public enum Method { confirmpaymentstarted, createoffer, createpaymentacct, + createcryptopaymentacct, getaddressbalance, getbalance, getbtcprice, diff --git a/cli/src/main/java/bisq/cli/opts/ArgumentList.java b/cli/src/main/java/bisq/cli/opts/ArgumentList.java index 3b52fb34a90..b416946d646 100644 --- a/cli/src/main/java/bisq/cli/opts/ArgumentList.java +++ b/cli/src/main/java/bisq/cli/opts/ArgumentList.java @@ -113,6 +113,7 @@ boolean hasMore() { return currentIndex < arguments.length; } + @SuppressWarnings("UnusedReturnValue") String next() { return arguments[currentIndex++]; } diff --git a/cli/src/main/java/bisq/cli/opts/CreateCryptoCurrencyPaymentAcctOptionParser.java b/cli/src/main/java/bisq/cli/opts/CreateCryptoCurrencyPaymentAcctOptionParser.java new file mode 100644 index 00000000000..90542e1e0b7 --- /dev/null +++ b/cli/src/main/java/bisq/cli/opts/CreateCryptoCurrencyPaymentAcctOptionParser.java @@ -0,0 +1,75 @@ +/* + * 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_ACCOUNT_NAME; +import static bisq.cli.opts.OptLabel.OPT_ADDRESS; +import static bisq.cli.opts.OptLabel.OPT_CURRENCY_CODE; + +public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodOptionParser implements MethodOpts { + + final OptionSpec accountNameOpt = parser.accepts(OPT_ACCOUNT_NAME, "crypto currency account name") + .withRequiredArg(); + + final OptionSpec currencyCodeOpt = parser.accepts(OPT_CURRENCY_CODE, "crypto currency code (bsq only)") + .withRequiredArg(); + + final OptionSpec addressOpt = parser.accepts(OPT_ADDRESS, "bsq address") + .withRequiredArg(); + + public CreateCryptoCurrencyPaymentAcctOptionParser(String[] args) { + super(args); + } + + public CreateCryptoCurrencyPaymentAcctOptionParser parse() { + super.parse(); + + // Short circuit opt validation if user just wants help. + if (options.has(helpOpt)) + return this; + + if (!options.has(accountNameOpt) || options.valueOf(accountNameOpt).isEmpty()) + throw new IllegalArgumentException("no payment account name specified"); + + if (!options.has(currencyCodeOpt) || options.valueOf(currencyCodeOpt).isEmpty()) + throw new IllegalArgumentException("no currency code specified"); + + if (!options.valueOf(currencyCodeOpt).equalsIgnoreCase("bsq")) + throw new IllegalArgumentException("api only supports bsq crypto currency payment accounts"); + + if (!options.has(addressOpt) || options.valueOf(addressOpt).isEmpty()) + throw new IllegalArgumentException("no bsq address specified"); + + return this; + } + + public String getAccountName() { + return options.valueOf(accountNameOpt); + } + + public String getCurrencyCode() { + return options.valueOf(currencyCodeOpt); + } + + public String getAddress() { + return options.valueOf(addressOpt); + } +} diff --git a/cli/src/main/java/bisq/cli/opts/MethodOpts.java b/cli/src/main/java/bisq/cli/opts/MethodOpts.java index da639857522..9f6c2d1a3e4 100644 --- a/cli/src/main/java/bisq/cli/opts/MethodOpts.java +++ b/cli/src/main/java/bisq/cli/opts/MethodOpts.java @@ -22,5 +22,4 @@ 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 index 5cbd068ce53..fcabd0820e4 100644 --- a/cli/src/main/java/bisq/cli/opts/OptLabel.java +++ b/cli/src/main/java/bisq/cli/opts/OptLabel.java @@ -21,6 +21,7 @@ * CLI opt label definitions. */ public class OptLabel { + public final static String OPT_ACCOUNT_NAME = "account-name"; public final static String OPT_ADDRESS = "address"; public final static String OPT_AMOUNT = "amount"; public final static String OPT_CURRENCY_CODE = "currency-code"; diff --git a/cli/src/test/java/bisq/cli/opt/OptionParsersTest.java b/cli/src/test/java/bisq/cli/opt/OptionParsersTest.java index 1f939198d69..d5cfe5d708f 100644 --- a/cli/src/test/java/bisq/cli/opt/OptionParsersTest.java +++ b/cli/src/test/java/bisq/cli/opt/OptionParsersTest.java @@ -3,6 +3,7 @@ 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.opts.OptLabel.*; @@ -12,6 +13,7 @@ import bisq.cli.opts.CancelOfferOptionParser; +import bisq.cli.opts.CreateCryptoCurrencyPaymentAcctOptionParser; import bisq.cli.opts.CreateOfferOptionParser; import bisq.cli.opts.CreatePaymentAcctOptionParser; @@ -20,7 +22,7 @@ public class OptionParsersTest { private static final String PASSWORD_OPT = "--" + OPT_PASSWORD + "=" + "xyz"; - // CancelOffer opt parsing tests + // canceloffer opt parser tests @Test public void testCancelOfferWithMissingOfferIdOptShouldThrowException() { @@ -67,7 +69,7 @@ public void testValidCancelOfferOpts() { new CancelOfferOptionParser(args).parse(); } - // CreateOffer opt parsing tests + // createoffer opt parser tests @Test public void testCreateOfferOptParserWithMissingPaymentAccountIdOptShouldThrowException() { @@ -139,7 +141,7 @@ public void testValidCreateOfferOpts() { assertEquals("25.0", parser.getSecurityDeposit()); } - // CreatePaymentAcct opt parser tests + // createpaymentacct opt parser tests @Test public void testCreatePaymentAcctOptParserWithMissingPaymentFormOptShouldThrowException() { @@ -177,4 +179,85 @@ public void testCreatePaymentAcctOptParserWithInvalidPaymentFormOptValueShouldTh assertEquals("json payment account form '/tmp/milkyway/solarsystem/mars' could not be found", exception.getMessage()); } + + // createcryptopaymentacct parser tests + + @Test + public void testCreateCryptoCurrencyPaymentAcctOptionParserWithMissingAcctNameOptShouldThrowException() { + String[] args = new String[]{ + PASSWORD_OPT, + createcryptopaymentacct.name() + }; + Throwable exception = assertThrows(RuntimeException.class, () -> + new CreateCryptoCurrencyPaymentAcctOptionParser(args).parse()); + assertEquals("no payment account name specified", exception.getMessage()); + } + + @Test + public void testCreateCryptoCurrencyPaymentAcctOptionParserWithEmptyAcctNameOptShouldThrowException() { + String[] args = new String[]{ + PASSWORD_OPT, + createcryptopaymentacct.name(), + "--" + OPT_ACCOUNT_NAME + }; + Throwable exception = assertThrows(RuntimeException.class, () -> + new CreateCryptoCurrencyPaymentAcctOptionParser(args).parse()); + assertEquals("account-name requires an argument", exception.getMessage()); + } + + @Test + public void testCreateCryptoCurrencyPaymentAcctOptionParserWithMissingCurrencyCodeOptShouldThrowException() { + String[] args = new String[]{ + PASSWORD_OPT, + createcryptopaymentacct.name(), + "--" + OPT_ACCOUNT_NAME + "=" + "bsq payment account" + }; + Throwable exception = assertThrows(RuntimeException.class, () -> + new CreateCryptoCurrencyPaymentAcctOptionParser(args).parse()); + assertEquals("no currency code specified", exception.getMessage()); + } + + @Test + public void testCreateCryptoCurrencyPaymentAcctOptionParserWithInvalidCurrencyCodeOptShouldThrowException() { + String[] args = new String[]{ + PASSWORD_OPT, + createcryptopaymentacct.name(), + "--" + OPT_ACCOUNT_NAME + "=" + "bsq payment account", + "--" + OPT_CURRENCY_CODE + "=" + "xmr" + }; + Throwable exception = assertThrows(RuntimeException.class, () -> + new CreateCryptoCurrencyPaymentAcctOptionParser(args).parse()); + assertEquals("api only supports bsq crypto currency payment accounts", exception.getMessage()); + } + + @Test + public void testCreateCryptoCurrencyPaymentAcctOptionParserWithMissingAddressOptShouldThrowException() { + String[] args = new String[]{ + PASSWORD_OPT, + createcryptopaymentacct.name(), + "--" + OPT_ACCOUNT_NAME + "=" + "bsq payment account", + "--" + OPT_CURRENCY_CODE + "=" + "bsq" + }; + Throwable exception = assertThrows(RuntimeException.class, () -> + new CreateCryptoCurrencyPaymentAcctOptionParser(args).parse()); + assertEquals("no bsq address specified", exception.getMessage()); + } + + @Test + public void testCreateCryptoCurrencyPaymentAcctOptionParser() { + var acctName = "bsq payment account"; + var currencyCode = "bsq"; + var address = "B1nXyZ"; // address is validated on server + String[] args = new String[]{ + PASSWORD_OPT, + createcryptopaymentacct.name(), + "--" + OPT_ACCOUNT_NAME + "=" + acctName, + "--" + OPT_CURRENCY_CODE + "=" + currencyCode, + "--" + OPT_ADDRESS + "=" + address + }; + var parser = new CreateCryptoCurrencyPaymentAcctOptionParser(args).parse(); + assertEquals(acctName, parser.getAccountName()); + assertEquals(currencyCode, parser.getCurrencyCode()); + assertEquals(address, parser.getAddress()); + } } diff --git a/core/src/main/java/bisq/core/api/CoreApi.java b/core/src/main/java/bisq/core/api/CoreApi.java index 8861620adbc..6db1c667ed9 100644 --- a/core/src/main/java/bisq/core/api/CoreApi.java +++ b/core/src/main/java/bisq/core/api/CoreApi.java @@ -210,6 +210,14 @@ public String getPaymentAccountForm(String paymentMethodId) { return paymentAccountsService.getPaymentAccountFormAsString(paymentMethodId); } + public PaymentAccount createCryptoCurrencyPaymentAccount(String accountName, String currencyCode, String address) { + return paymentAccountsService.createCryptoCurrencyPaymentAccount(accountName, currencyCode, address); + } + + public List getCryptoCurrencyPaymentMethods() { + return paymentAccountsService.getCryptoCurrencyPaymentMethods(); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Prices /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java b/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java index b915b3ab4be..a316b76ed07 100644 --- a/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java +++ b/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java @@ -19,7 +19,10 @@ import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.api.model.PaymentAccountForm; +import bisq.core.locale.CryptoCurrency; +import bisq.core.payment.CryptoCurrencyAccount; import bisq.core.payment.PaymentAccount; +import bisq.core.payment.PaymentAccountFactory; import bisq.core.payment.payload.PaymentMethod; import bisq.core.user.User; @@ -30,30 +33,37 @@ import java.util.Comparator; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; +import static bisq.core.locale.CurrencyUtil.getCryptoCurrency; import static java.lang.String.format; @Singleton @Slf4j class CorePaymentAccountsService { + private final CoreWalletsService coreWalletsService; private final AccountAgeWitnessService accountAgeWitnessService; private final PaymentAccountForm paymentAccountForm; private final User user; @Inject - public CorePaymentAccountsService(AccountAgeWitnessService accountAgeWitnessService, + public CorePaymentAccountsService(CoreWalletsService coreWalletsService, + AccountAgeWitnessService accountAgeWitnessService, PaymentAccountForm paymentAccountForm, User user) { + this.coreWalletsService = coreWalletsService; this.accountAgeWitnessService = accountAgeWitnessService; this.paymentAccountForm = paymentAccountForm; this.user = user; } + // Fiat Currency Accounts + PaymentAccount createPaymentAccount(String jsonString) { PaymentAccount paymentAccount = paymentAccountForm.toPaymentAccount(jsonString); verifyPaymentAccountHasRequiredFields(paymentAccount); @@ -86,6 +96,44 @@ File getPaymentAccountForm(String paymentMethodId) { return paymentAccountForm.getPaymentAccountForm(paymentMethodId); } + // Crypto Currency Accounts + + PaymentAccount createCryptoCurrencyPaymentAccount(String accountName, + String currencyCode, + String address) { + String bsqCode = currencyCode.toUpperCase(); + if (!bsqCode.equals("BSQ")) + throw new IllegalArgumentException("api does not currently support " + currencyCode + " accounts"); + + // Validate the BSQ address string but ignore the return value. + coreWalletsService.getValidBsqLegacyAddress(address); + + CryptoCurrencyAccount cryptoCurrencyAccount = + (CryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS); + cryptoCurrencyAccount.init(); + cryptoCurrencyAccount.setAccountName(accountName); + cryptoCurrencyAccount.setAddress(address); + Optional cryptoCurrency = getCryptoCurrency(bsqCode); + cryptoCurrency.ifPresent(cryptoCurrencyAccount::setSingleTradeCurrency); + user.addPaymentAccount(cryptoCurrencyAccount); + accountAgeWitnessService.publishMyAccountAgeWitness(cryptoCurrencyAccount.getPaymentAccountPayload()); + log.info("Saved crypto payment account with id {} and payment method {}.", + cryptoCurrencyAccount.getId(), + cryptoCurrencyAccount.getPaymentAccountPayload().getPaymentMethodId()); + return cryptoCurrencyAccount; + } + + // TODO Support all alt coin payment methods supported by UI. + // The getCryptoCurrencyPaymentMethods method below will be + // callable from the CLI when more are supported. + + List getCryptoCurrencyPaymentMethods() { + return PaymentMethod.getPaymentMethods().stream() + .filter(PaymentMethod::isAsset) + .sorted(Comparator.comparing(PaymentMethod::getId)) + .collect(Collectors.toList()); + } + private void verifyPaymentAccountHasRequiredFields(PaymentAccount paymentAccount) { // Do checks here to make sure required fields are populated. if (paymentAccount.isTransferwiseAccount() && paymentAccount.getTradeCurrencies().isEmpty()) diff --git a/core/src/main/java/bisq/core/api/CoreWalletsService.java b/core/src/main/java/bisq/core/api/CoreWalletsService.java index 3438f640d8e..d6aa8a240a3 100644 --- a/core/src/main/java/bisq/core/api/CoreWalletsService.java +++ b/core/src/main/java/bisq/core/api/CoreWalletsService.java @@ -511,6 +511,16 @@ void verifyApplicationIsFullyInitialized() { throw new IllegalStateException("server is not fully initialized"); } + // Returns a LegacyAddress for the string, or a RuntimeException if invalid. + LegacyAddress getValidBsqLegacyAddress(String address) { + try { + return bsqFormatter.getAddressFromBsqAddress(address); + } catch (Throwable t) { + log.error("", t); + throw new IllegalStateException(format("%s is not a valid bsq address", address)); + } + } + // Throws a RuntimeException if wallet currency code is not BSQ or BTC. private void verifyWalletCurrencyCodeIsValid(String currencyCode) { if (currencyCode == null || currencyCode.isEmpty()) @@ -575,16 +585,6 @@ private BtcBalanceInfo getBtcBalances() { lockedBalance.value); } - // Returns a LegacyAddress for the string, or a RuntimeException if invalid. - private LegacyAddress getValidBsqLegacyAddress(String address) { - try { - return bsqFormatter.getAddressFromBsqAddress(address); - } catch (Throwable t) { - log.error("", t); - throw new IllegalStateException(format("%s is not a valid bsq address", address)); - } - } - // Returns a Coin for the transfer amount string, or a RuntimeException if invalid. private Coin getValidTransferAmount(String amount, CoinFormatter coinFormatter) { Coin amountAsCoin = parseToCoin(amount, coinFormatter); diff --git a/core/src/main/resources/help/createcryptopaymentacct-help.txt b/core/src/main/resources/help/createcryptopaymentacct-help.txt new file mode 100644 index 00000000000..c1724550e1c --- /dev/null +++ b/core/src/main/resources/help/createcryptopaymentacct-help.txt @@ -0,0 +1,39 @@ +createcryptopaymentacct + +NAME +---- +createcryptopaymentacct - create a cryptocurrency payment account + +SYNOPSIS +-------- +createcryptopaymentacct + --account-name= + --currency-code= + --address= + +DESCRIPTION +----------- +Creates a cryptocurrency (altcoin) trading account for buying and selling BTC. + +OPTIONS +------- +--account-name + The name of the cryptocurrency payment account. + +--currency-code + The three letter code for the cryptocurrency used to buy or sell BTC, e.g., BSQ. + +--address + A valid BSQ wallet address. + +EXAMPLES +-------- +To create a new BSQ payment account, find an unused BSQ wallet address: +$ ./bisq-cli --password=xyz --port=9998 getunusedbsqaddress + +With the returned BSQ address, e.g., Bn3PCQgRwhkrGnaMp1RYwt9tFwL51YELqne create the cryptocurrency payment account: +$ ./bisq-cli --password=xyz --port=9998 createcryptopaymentacct \ + --account-name="My BSQ Account" \ + --currency-code=BSQ \ + --address=Bn3PCQgRwhkrGnaMp1RYwt9tFwL51YELqne + diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcPaymentAccountsService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcPaymentAccountsService.java index e1df2b16cb1..af801004841 100644 --- a/daemon/src/main/java/bisq/daemon/grpc/GrpcPaymentAccountsService.java +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcPaymentAccountsService.java @@ -21,8 +21,12 @@ import bisq.core.payment.PaymentAccount; import bisq.core.payment.payload.PaymentMethod; +import bisq.proto.grpc.CreateCryptoCurrencyPaymentAccountReply; +import bisq.proto.grpc.CreateCryptoCurrencyPaymentAccountRequest; import bisq.proto.grpc.CreatePaymentAccountReply; import bisq.proto.grpc.CreatePaymentAccountRequest; +import bisq.proto.grpc.GetCryptoCurrencyPaymentMethodsReply; +import bisq.proto.grpc.GetCryptoCurrencyPaymentMethodsRequest; import bisq.proto.grpc.GetPaymentAccountFormReply; import bisq.proto.grpc.GetPaymentAccountFormRequest; import bisq.proto.grpc.GetPaymentAccountsReply; @@ -125,6 +129,39 @@ public void getPaymentAccountForm(GetPaymentAccountFormRequest req, } } + @Override + public void createCryptoCurrencyPaymentAccount(CreateCryptoCurrencyPaymentAccountRequest req, + StreamObserver responseObserver) { + try { + PaymentAccount paymentAccount = coreApi.createCryptoCurrencyPaymentAccount(req.getAccountName(), + req.getCurrencyCode(), + req.getAddress()); + var reply = CreateCryptoCurrencyPaymentAccountReply.newBuilder() + .setPaymentAccount(paymentAccount.toProtoMessage()) + .build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } catch (Throwable cause) { + exceptionHandler.handleException(log, cause, responseObserver); + } + } + + @Override + public void getCryptoCurrencyPaymentMethods(GetCryptoCurrencyPaymentMethodsRequest req, + StreamObserver responseObserver) { + try { + var paymentMethods = coreApi.getCryptoCurrencyPaymentMethods().stream() + .map(PaymentMethod::toProtoMessage) + .collect(Collectors.toList()); + var reply = GetCryptoCurrencyPaymentMethodsReply.newBuilder() + .addAllPaymentMethods(paymentMethods).build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } catch (Throwable cause) { + exceptionHandler.handleException(log, cause, responseObserver); + } + } + final ServerInterceptor[] interceptors() { Optional rateMeteringInterceptor = rateMeteringInterceptor(); return rateMeteringInterceptor.map(serverInterceptor -> diff --git a/proto/src/main/proto/grpc.proto b/proto/src/main/proto/grpc.proto index 8a819ff83ba..3fd783ada38 100644 --- a/proto/src/main/proto/grpc.proto +++ b/proto/src/main/proto/grpc.proto @@ -170,6 +170,10 @@ service PaymentAccounts { } rpc GetPaymentAccountForm (GetPaymentAccountFormRequest) returns (GetPaymentAccountFormReply) { } + rpc CreateCryptoCurrencyPaymentAccount (CreateCryptoCurrencyPaymentAccountRequest) returns (CreateCryptoCurrencyPaymentAccountReply) { + } + rpc GetCryptoCurrencyPaymentMethods (GetCryptoCurrencyPaymentMethodsRequest) returns (GetCryptoCurrencyPaymentMethodsReply) { + } } message CreatePaymentAccountRequest { @@ -202,6 +206,23 @@ message GetPaymentAccountFormReply { string paymentAccountFormJson = 1; } +message CreateCryptoCurrencyPaymentAccountRequest { + string accountName = 1; + string currencyCode = 2; + string address = 3; +} + +message CreateCryptoCurrencyPaymentAccountReply { + PaymentAccount paymentAccount = 1; +} + +message GetCryptoCurrencyPaymentMethodsRequest { +} + +message GetCryptoCurrencyPaymentMethodsReply { + repeated PaymentMethod paymentMethods = 1; +} + /////////////////////////////////////////////////////////////////////////////////////////// // Price /////////////////////////////////////////////////////////////////////////////////////////// From 27b090005dfc601352fd366bb2bbab27b520a32d Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Thu, 25 Mar 2021 18:46:41 -0300 Subject: [PATCH 2/4] Add cli side help for createcryptopaymentacct --- cli/src/main/java/bisq/cli/CliMain.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cli/src/main/java/bisq/cli/CliMain.java b/cli/src/main/java/bisq/cli/CliMain.java index 37bda937eb8..4b6568712b4 100644 --- a/cli/src/main/java/bisq/cli/CliMain.java +++ b/cli/src/main/java/bisq/cli/CliMain.java @@ -693,7 +693,7 @@ private static void printHelp(OptionParser parser, @SuppressWarnings("SameParame stream.println(); parser.printHelpOn(stream); stream.println(); - String rowFormat = "%-24s%-52s%s%n"; + String rowFormat = "%-25s%-52s%s%n"; stream.format(rowFormat, "Method", "Params", "Description"); stream.format(rowFormat, "------", "------", "------------"); stream.format(rowFormat, getversion.name(), "", "Get server version"); @@ -765,7 +765,9 @@ private static void printHelp(OptionParser parser, @SuppressWarnings("SameParame stream.println(); stream.format(rowFormat, createpaymentacct.name(), "--payment-account-form=", "Create a new payment account"); stream.println(); - stream.format(rowFormat, createcryptopaymentacct.name(), "--TODO=", "Create a new cryptocurrency payment account"); + stream.format(rowFormat, createcryptopaymentacct.name(), "--account-name= \\", "Create a new cryptocurrency payment account"); + stream.format(rowFormat, "", "--currency-code= \\", ""); + stream.format(rowFormat, "", "--address=", ""); stream.println(); stream.format(rowFormat, getpaymentaccts.name(), "", "Get user payment accounts"); stream.println(); From 58c885efc193db456def73c04a570d97e724b6d9 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Thu, 1 Apr 2021 15:43:48 -0300 Subject: [PATCH 3/4] Add support for creating instant altcoin payment accounts in api - Added bool tradeInstant field to proto message def. - Adjusted core createcryptopaymentacct impl to new tradeInstant request param. - Adjusted cli side createcryptopaymentacct impl to new tradeInstant request param. - Fixed CliMain's takeoffer help text (was missing the --payment-account opt). --- cli/src/main/java/bisq/cli/CliMain.java | 9 +++-- cli/src/main/java/bisq/cli/GrpcClient.java | 4 ++- ...CryptoCurrencyPaymentAcctOptionParser.java | 10 ++++++ cli/src/main/java/bisq/cli/opts/OptLabel.java | 1 + core/src/main/java/bisq/core/api/CoreApi.java | 10 ++++-- .../core/api/CorePaymentAccountsService.java | 9 +++-- .../help/createcryptopaymentacct-help.txt | 36 +++++++++++-------- .../grpc/GrpcPaymentAccountsService.java | 3 +- proto/src/main/proto/grpc.proto | 1 + 9 files changed, 60 insertions(+), 23 deletions(-) diff --git a/cli/src/main/java/bisq/cli/CliMain.java b/cli/src/main/java/bisq/cli/CliMain.java index 4b6568712b4..b85858fa120 100644 --- a/cli/src/main/java/bisq/cli/CliMain.java +++ b/cli/src/main/java/bisq/cli/CliMain.java @@ -527,9 +527,11 @@ public static void run(String[] args) { var accountName = opts.getAccountName(); var currencyCode = opts.getCurrencyCode(); var address = opts.getAddress(); + var isTradeInstant = opts.getIsTradeInstant(); var paymentAccount = client.createCryptoCurrencyPaymentAccount(accountName, currencyCode, - address); + address, + isTradeInstant); out.println("payment account saved"); out.println(formatPaymentAcctTbl(singletonList(paymentAccount))); return; @@ -744,7 +746,9 @@ private static void printHelp(OptionParser parser, @SuppressWarnings("SameParame 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.format(rowFormat, takeoffer.name(), "--offer-id= \\", "Take offer with id"); + stream.format(rowFormat, "", "--payment-account=", ""); + stream.format(rowFormat, "", "[--fee-currency=]", ""); stream.println(); stream.format(rowFormat, gettrade.name(), "--trade-id= \\", "Get trade summary or full contract"); stream.format(rowFormat, "", "[--show-contract=]", ""); @@ -768,6 +772,7 @@ private static void printHelp(OptionParser parser, @SuppressWarnings("SameParame stream.format(rowFormat, createcryptopaymentacct.name(), "--account-name= \\", "Create a new cryptocurrency payment account"); stream.format(rowFormat, "", "--currency-code= \\", ""); stream.format(rowFormat, "", "--address=", ""); + stream.format(rowFormat, "", "--trade-instant=", ""); stream.println(); stream.format(rowFormat, getpaymentaccts.name(), "", "Get user payment accounts"); stream.println(); diff --git a/cli/src/main/java/bisq/cli/GrpcClient.java b/cli/src/main/java/bisq/cli/GrpcClient.java index 5d3dde4eccb..e9e87f0ed65 100644 --- a/cli/src/main/java/bisq/cli/GrpcClient.java +++ b/cli/src/main/java/bisq/cli/GrpcClient.java @@ -433,11 +433,13 @@ public List getPaymentAccounts() { public PaymentAccount createCryptoCurrencyPaymentAccount(String accountName, String currencyCode, - String address) { + String address, + boolean tradeInstant) { var request = CreateCryptoCurrencyPaymentAccountRequest.newBuilder() .setAccountName(accountName) .setCurrencyCode(currencyCode) .setAddress(address) + .setTradeInstant(tradeInstant) .build(); return grpcStubs.paymentAccountsService.createCryptoCurrencyPaymentAccount(request).getPaymentAccount(); } diff --git a/cli/src/main/java/bisq/cli/opts/CreateCryptoCurrencyPaymentAcctOptionParser.java b/cli/src/main/java/bisq/cli/opts/CreateCryptoCurrencyPaymentAcctOptionParser.java index 90542e1e0b7..a37a9f109bb 100644 --- a/cli/src/main/java/bisq/cli/opts/CreateCryptoCurrencyPaymentAcctOptionParser.java +++ b/cli/src/main/java/bisq/cli/opts/CreateCryptoCurrencyPaymentAcctOptionParser.java @@ -23,6 +23,7 @@ import static bisq.cli.opts.OptLabel.OPT_ACCOUNT_NAME; import static bisq.cli.opts.OptLabel.OPT_ADDRESS; import static bisq.cli.opts.OptLabel.OPT_CURRENCY_CODE; +import static bisq.cli.opts.OptLabel.OPT_TRADE_INSTANT; public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodOptionParser implements MethodOpts { @@ -35,6 +36,11 @@ public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodO final OptionSpec addressOpt = parser.accepts(OPT_ADDRESS, "bsq address") .withRequiredArg(); + final OptionSpec tradeInstantOpt = parser.accepts(OPT_TRADE_INSTANT, "create trade instant account") + .withOptionalArg() + .ofType(boolean.class) + .defaultsTo(Boolean.FALSE); + public CreateCryptoCurrencyPaymentAcctOptionParser(String[] args) { super(args); } @@ -72,4 +78,8 @@ public String getCurrencyCode() { public String getAddress() { return options.valueOf(addressOpt); } + + public boolean getIsTradeInstant() { + return options.valueOf(tradeInstantOpt); + } } diff --git a/cli/src/main/java/bisq/cli/opts/OptLabel.java b/cli/src/main/java/bisq/cli/opts/OptLabel.java index fcabd0820e4..084c230aae3 100644 --- a/cli/src/main/java/bisq/cli/opts/OptLabel.java +++ b/cli/src/main/java/bisq/cli/opts/OptLabel.java @@ -44,6 +44,7 @@ public class OptLabel { 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_TRADE_INSTANT = "trade-instant"; 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"; diff --git a/core/src/main/java/bisq/core/api/CoreApi.java b/core/src/main/java/bisq/core/api/CoreApi.java index 6db1c667ed9..24ead3f1aba 100644 --- a/core/src/main/java/bisq/core/api/CoreApi.java +++ b/core/src/main/java/bisq/core/api/CoreApi.java @@ -210,8 +210,14 @@ public String getPaymentAccountForm(String paymentMethodId) { return paymentAccountsService.getPaymentAccountFormAsString(paymentMethodId); } - public PaymentAccount createCryptoCurrencyPaymentAccount(String accountName, String currencyCode, String address) { - return paymentAccountsService.createCryptoCurrencyPaymentAccount(accountName, currencyCode, address); + public PaymentAccount createCryptoCurrencyPaymentAccount(String accountName, + String currencyCode, + String address, + boolean tradeInstant) { + return paymentAccountsService.createCryptoCurrencyPaymentAccount(accountName, + currencyCode, + address, + tradeInstant); } public List getCryptoCurrencyPaymentMethods() { diff --git a/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java b/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java index a316b76ed07..0843e20ab76 100644 --- a/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java +++ b/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java @@ -21,6 +21,7 @@ import bisq.core.api.model.PaymentAccountForm; import bisq.core.locale.CryptoCurrency; import bisq.core.payment.CryptoCurrencyAccount; +import bisq.core.payment.InstantCryptoCurrencyAccount; import bisq.core.payment.PaymentAccount; import bisq.core.payment.PaymentAccountFactory; import bisq.core.payment.payload.PaymentMethod; @@ -100,7 +101,8 @@ File getPaymentAccountForm(String paymentMethodId) { PaymentAccount createCryptoCurrencyPaymentAccount(String accountName, String currencyCode, - String address) { + String address, + boolean tradeInstant) { String bsqCode = currencyCode.toUpperCase(); if (!bsqCode.equals("BSQ")) throw new IllegalArgumentException("api does not currently support " + currencyCode + " accounts"); @@ -108,8 +110,9 @@ PaymentAccount createCryptoCurrencyPaymentAccount(String accountName, // Validate the BSQ address string but ignore the return value. coreWalletsService.getValidBsqLegacyAddress(address); - CryptoCurrencyAccount cryptoCurrencyAccount = - (CryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS); + var cryptoCurrencyAccount = tradeInstant + ? (InstantCryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS_INSTANT) + : (CryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS); cryptoCurrencyAccount.init(); cryptoCurrencyAccount.setAccountName(accountName); cryptoCurrencyAccount.setAddress(address); diff --git a/core/src/main/resources/help/createcryptopaymentacct-help.txt b/core/src/main/resources/help/createcryptopaymentacct-help.txt index c1724550e1c..83fa2e9f633 100644 --- a/core/src/main/resources/help/createcryptopaymentacct-help.txt +++ b/core/src/main/resources/help/createcryptopaymentacct-help.txt @@ -7,33 +7,41 @@ createcryptopaymentacct - create a cryptocurrency payment account SYNOPSIS -------- createcryptopaymentacct - --account-name= + --account-name= --currency-code= - --address= + --address= + [--trade-instant=] DESCRIPTION ----------- -Creates a cryptocurrency (altcoin) trading account for buying and selling BTC. +Create an cryptocurrency (altcoin) payment account. Only BSQ payment accounts are currently supported. OPTIONS ------- --account-name - The name of the cryptocurrency payment account. + The name of the cryptocurrency payment account used to create and take altcoin offers. --currency-code - The three letter code for the cryptocurrency used to buy or sell BTC, e.g., BSQ. + The three letter code for the altcoin, e.g., BSQ. --address - A valid BSQ wallet address. + The altcoin address to be used receive cryptocurrency payment when selling BTC. + +--trade-instant + True for creating an instant cryptocurrency payment account, false otherwise. + Default is false. EXAMPLES -------- -To create a new BSQ payment account, find an unused BSQ wallet address: -$ ./bisq-cli --password=xyz --port=9998 getunusedbsqaddress - -With the returned BSQ address, e.g., Bn3PCQgRwhkrGnaMp1RYwt9tFwL51YELqne create the cryptocurrency payment account: -$ ./bisq-cli --password=xyz --port=9998 createcryptopaymentacct \ - --account-name="My BSQ Account" \ - --currency-code=BSQ \ - --address=Bn3PCQgRwhkrGnaMp1RYwt9tFwL51YELqne +To create a BSQ Altcoin payment account: +$ ./bisq-cli --password=xyz --port=9998 createcryptopaymentacct --account-name="My BSQ Account" \ + --currency-code=bsq \ + --address=Bn3PCQgRwhkrGnaMp1RYwt9tFwL51YELqne \ + --trade-instant=false + +To create a BSQ Instant Altcoin payment account: +$ ./bisq-cli --password=xyz --port=9998 createcryptopaymentacct --account-name="My Instant BSQ Account" \ + --currency-code=bsq \ + --address=Bn3PCQgRwhkrGnaMp1RYwt9tFwL51YELqne \ + --trade-instant=true diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcPaymentAccountsService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcPaymentAccountsService.java index af801004841..2c1b5501b28 100644 --- a/daemon/src/main/java/bisq/daemon/grpc/GrpcPaymentAccountsService.java +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcPaymentAccountsService.java @@ -135,7 +135,8 @@ public void createCryptoCurrencyPaymentAccount(CreateCryptoCurrencyPaymentAccoun try { PaymentAccount paymentAccount = coreApi.createCryptoCurrencyPaymentAccount(req.getAccountName(), req.getCurrencyCode(), - req.getAddress()); + req.getAddress(), + req.getTradeInstant()); var reply = CreateCryptoCurrencyPaymentAccountReply.newBuilder() .setPaymentAccount(paymentAccount.toProtoMessage()) .build(); diff --git a/proto/src/main/proto/grpc.proto b/proto/src/main/proto/grpc.proto index 3fd783ada38..432547edc01 100644 --- a/proto/src/main/proto/grpc.proto +++ b/proto/src/main/proto/grpc.proto @@ -210,6 +210,7 @@ message CreateCryptoCurrencyPaymentAccountRequest { string accountName = 1; string currencyCode = 2; string address = 3; + bool tradeInstant = 4; } message CreateCryptoCurrencyPaymentAccountReply { From 4fbdc32ba49cca800e2745fcdd5757a2cc9a44f1 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Thu, 1 Apr 2021 15:45:53 -0300 Subject: [PATCH 4/4] Remove space char --- proto/src/main/proto/grpc.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proto/src/main/proto/grpc.proto b/proto/src/main/proto/grpc.proto index 432547edc01..2d686506654 100644 --- a/proto/src/main/proto/grpc.proto +++ b/proto/src/main/proto/grpc.proto @@ -210,7 +210,7 @@ message CreateCryptoCurrencyPaymentAccountRequest { string accountName = 1; string currencyCode = 2; string address = 3; - bool tradeInstant = 4; + bool tradeInstant = 4; } message CreateCryptoCurrencyPaymentAccountReply {