diff --git a/apitest/docs/build-run.md b/apitest/docs/build-run.md
index 308fe02cf66..58832bf4534 100644
--- a/apitest/docs/build-run.md
+++ b/apitest/docs/build-run.md
@@ -48,7 +48,7 @@ To run all test cases in a package:
To run a single test case:
- $ ./gradlew :apitest:test --tests "bisq.apitest.method.GetBalanceTest" -DrunApiTests=true
+ $ ./gradlew :apitest:test --tests "bisq.apitest.scenario.WalletTest" -DrunApiTests=true
To run test cases from Intellij, add two JVM arguments to your JUnit launchers:
diff --git a/apitest/scripts/mainnet-test.sh b/apitest/scripts/mainnet-test.sh
index 72b29cdfbf3..48fe4023bf1 100755
--- a/apitest/scripts/mainnet-test.sh
+++ b/apitest/scripts/mainnet-test.sh
@@ -93,8 +93,6 @@
@test "test getbalance while wallet unlocked for 8s" {
run ./bisq-cli --password=xyz getbalance
[ "$status" -eq 0 ]
- echo "actual output: $output" >&2
- [ "$output" = "0.00000000" ]
sleep 8
}
@@ -145,8 +143,6 @@
@test "test getbalance when wallet available & unlocked with 0 btc balance" {
run ./bisq-cli --password=xyz getbalance
[ "$status" -eq 0 ]
- echo "actual output: $output" >&2
- [ "$output" = "0.00000000" ]
}
@test "test getfundingaddresses" {
@@ -154,6 +150,11 @@
[ "$status" -eq 0 ]
}
+@test "test getunusedbsqaddress" {
+ run ./bisq-cli --password=xyz getfundingaddresses
+ [ "$status" -eq 0 ]
+}
+
@test "test getaddressbalance missing address argument" {
run ./bisq-cli --password=xyz getaddressbalance
[ "$status" -eq 1 ]
@@ -168,15 +169,8 @@
[ "$output" = "Error: address bogus not found in wallet" ]
}
-@test "test createpaymentacct PerfectMoneyDummy (missing name, nbr, ccy params)" {
- run ./bisq-cli --password=xyz createpaymentacct PERFECT_MONEY
- [ "$status" -eq 1 ]
- echo "actual output: $output" >&2
- [ "$output" = "Error: incorrect parameter count, expecting payment method id, account name, account number, currency code" ]
-}
-
-@test "test createpaymentacct PERFECT_MONEY PerfectMoneyDummy 0123456789 USD" {
- run ./bisq-cli --password=xyz createpaymentacct PERFECT_MONEY PerfectMoneyDummy 0123456789 USD
+@test "test getpaymentmethods" {
+ run ./bisq-cli --password=xyz getpaymentmethods
[ "$status" -eq 0 ]
}
diff --git a/apitest/src/test/java/bisq/apitest/method/CreatePaymentAccountTest.java b/apitest/src/test/java/bisq/apitest/method/CreatePaymentAccountTest.java
deleted file mode 100644
index 9e8b0af878a..00000000000
--- a/apitest/src/test/java/bisq/apitest/method/CreatePaymentAccountTest.java
+++ /dev/null
@@ -1,99 +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.apitest.method;
-
-import bisq.proto.grpc.GetPaymentAccountsRequest;
-
-import protobuf.PaymentAccount;
-import protobuf.PerfectMoneyAccountPayload;
-
-import java.util.List;
-import java.util.stream.Collectors;
-
-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.Scaffold.BitcoinCoreApp.bitcoind;
-import static bisq.apitest.config.BisqAppConfig.alicedaemon;
-import static java.util.Comparator.comparing;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.fail;
-import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
-
-@Disabled
-@Slf4j
-@TestMethodOrder(OrderAnnotation.class)
-public class CreatePaymentAccountTest extends MethodTest {
-
- static final String PERFECT_MONEY_ACCT_NAME = "Perfect Money USD";
- static final String PERFECT_MONEY_ACCT_NUMBER = "0123456789";
-
- @BeforeAll
- public static void setUp() {
- try {
- setUpScaffold(bitcoind, alicedaemon);
- } catch (Exception ex) {
- fail(ex);
- }
- }
-
- @Test
- @Order(1)
- public void testCreatePerfectMoneyUSDPaymentAccount() {
- var perfectMoneyPaymentAccountRequest = createCreatePerfectMoneyPaymentAccountRequest(
- PERFECT_MONEY_ACCT_NAME,
- PERFECT_MONEY_ACCT_NUMBER,
- "USD");
- //noinspection ResultOfMethodCallIgnored
- grpcStubs(alicedaemon).paymentAccountsService.createPaymentAccount(perfectMoneyPaymentAccountRequest);
-
- var getPaymentAccountsRequest = GetPaymentAccountsRequest.newBuilder().build();
- var reply = grpcStubs(alicedaemon).paymentAccountsService.getPaymentAccounts(getPaymentAccountsRequest);
-
- // The daemon is running against the regtest/dao setup files, and was set up with
- // two dummy accounts ("PerfectMoney dummy", "ETH dummy") before any tests ran.
- // We just added 1 test account, making 3 total.
- assertEquals(3, reply.getPaymentAccountsCount());
-
- // Sort the returned list by creation date; the last item in the sorted
- // list will be the payment acct we just created.
- List paymentAccountList = reply.getPaymentAccountsList().stream()
- .sorted(comparing(PaymentAccount::getCreationDate))
- .collect(Collectors.toList());
- PaymentAccount paymentAccount = paymentAccountList.get(2);
- PerfectMoneyAccountPayload perfectMoneyAccount = paymentAccount
- .getPaymentAccountPayload()
- .getPerfectMoneyAccountPayload();
-
- assertEquals(PERFECT_MONEY_ACCT_NAME, paymentAccount.getAccountName());
- assertEquals("USD",
- paymentAccount.getSelectedTradeCurrency().getFiatCurrency().getCurrency().getCurrencyCode());
- assertEquals(PERFECT_MONEY_ACCT_NUMBER, perfectMoneyAccount.getAccountNr());
- }
-
- @AfterAll
- public static void tearDown() {
- tearDownScaffold();
- }
-}
diff --git a/apitest/src/test/java/bisq/apitest/method/GetBalanceTest.java b/apitest/src/test/java/bisq/apitest/method/GetBalanceTest.java
deleted file mode 100644
index 1d44590837b..00000000000
--- a/apitest/src/test/java/bisq/apitest/method/GetBalanceTest.java
+++ /dev/null
@@ -1,73 +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.apitest.method;
-
-import bisq.proto.grpc.GetBalanceRequest;
-
-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.Scaffold.BitcoinCoreApp.bitcoind;
-import static bisq.apitest.config.BisqAppConfig.alicedaemon;
-import static bisq.apitest.config.BisqAppConfig.seednode;
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.fail;
-import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
-
-@Disabled
-@Slf4j
-@TestMethodOrder(OrderAnnotation.class)
-public class GetBalanceTest extends MethodTest {
-
- @BeforeAll
- public static void setUp() {
- try {
- setUpScaffold(bitcoind, seednode, alicedaemon);
-
- // Have to generate 1 regtest block for alice's wallet to show 10 BTC balance.
- bitcoinCli.generateBlocks(1);
-
- // Give the alicedaemon time to parse the new block.
- MILLISECONDS.sleep(1500);
- } catch (Exception ex) {
- fail(ex);
- }
- }
-
- @Test
- @Order(1)
- public void testGetBalance() {
- // All tests depend on the DAO / regtest environment, and Alice's wallet is
- // initialized with 10 BTC during the scaffolding setup.
- var balance = grpcStubs(alicedaemon).walletsService
- .getBalance(GetBalanceRequest.newBuilder().build()).getBalance();
- assertEquals(1000000000, balance);
- }
-
- @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 43073ba995b..153f3c90c53 100644
--- a/apitest/src/test/java/bisq/apitest/method/MethodTest.java
+++ b/apitest/src/test/java/bisq/apitest/method/MethodTest.java
@@ -17,21 +17,33 @@
package bisq.apitest.method;
+import bisq.core.api.model.PaymentAccountForm;
+import bisq.core.proto.CoreProtoResolver;
+
+import bisq.proto.grpc.AddressBalanceInfo;
+import bisq.proto.grpc.BalancesInfo;
+import bisq.proto.grpc.BsqBalanceInfo;
+import bisq.proto.grpc.BtcBalanceInfo;
import bisq.proto.grpc.CancelOfferRequest;
import bisq.proto.grpc.ConfirmPaymentReceivedRequest;
import bisq.proto.grpc.ConfirmPaymentStartedRequest;
import bisq.proto.grpc.CreatePaymentAccountRequest;
-import bisq.proto.grpc.GetBalanceRequest;
+import bisq.proto.grpc.GetAddressBalanceRequest;
+import bisq.proto.grpc.GetBalancesRequest;
import bisq.proto.grpc.GetFundingAddressesRequest;
import bisq.proto.grpc.GetOfferRequest;
+import bisq.proto.grpc.GetPaymentAccountFormRequest;
import bisq.proto.grpc.GetPaymentAccountsRequest;
+import bisq.proto.grpc.GetPaymentMethodsRequest;
import bisq.proto.grpc.GetTradeRequest;
+import bisq.proto.grpc.GetUnusedBsqAddressRequest;
import bisq.proto.grpc.KeepFundsRequest;
import bisq.proto.grpc.LockWalletRequest;
import bisq.proto.grpc.MarketPriceRequest;
import bisq.proto.grpc.OfferInfo;
import bisq.proto.grpc.RegisterDisputeAgentRequest;
import bisq.proto.grpc.RemoveWalletPasswordRequest;
+import bisq.proto.grpc.SendBsqRequest;
import bisq.proto.grpc.SetWalletPasswordRequest;
import bisq.proto.grpc.TakeOfferRequest;
import bisq.proto.grpc.TradeInfo;
@@ -39,14 +51,21 @@
import bisq.proto.grpc.WithdrawFundsRequest;
import protobuf.PaymentAccount;
+import protobuf.PaymentMethod;
+
+import java.nio.charset.StandardCharsets;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.List;
import java.util.stream.Collectors;
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
import static bisq.apitest.config.BisqAppConfig.arbdaemon;
import static bisq.apitest.config.BisqAppConfig.bobdaemon;
import static bisq.common.app.DevEnv.DEV_PRIVILEGE_PRIV_KEY;
-import static bisq.core.payment.payload.PaymentMethod.PERFECT_MONEY;
import static java.util.Arrays.stream;
import static java.util.Comparator.comparing;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -70,6 +89,8 @@ public class MethodTest extends ApiTestCase {
protected static PaymentAccount alicesDummyAcct;
protected static PaymentAccount bobsDummyAcct;
+ private static final CoreProtoResolver CORE_PROTO_RESOLVER = new CoreProtoResolver();
+
public static void startSupportingApps(boolean registerDisputeAgents,
boolean generateBtcBlock,
Enum>... supportingApps) {
@@ -102,9 +123,12 @@ public static void startSupportingApps(boolean registerDisputeAgents,
}
// Convenience methods for building gRPC request objects
+ protected final GetBalancesRequest createGetBalancesRequest(String currencyCode) {
+ return GetBalancesRequest.newBuilder().setCurrencyCode(currencyCode).build();
+ }
- protected final GetBalanceRequest createBalanceRequest() {
- return GetBalanceRequest.newBuilder().build();
+ protected final GetAddressBalanceRequest createGetAddressBalanceRequest(String address) {
+ return GetAddressBalanceRequest.newBuilder().setAddress(address).build();
}
protected final SetWalletPasswordRequest createSetWalletPasswordRequest(String password) {
@@ -127,6 +151,14 @@ protected final LockWalletRequest createLockWalletRequest() {
return LockWalletRequest.newBuilder().build();
}
+ protected final GetUnusedBsqAddressRequest createGetUnusedBsqAddressRequest() {
+ return GetUnusedBsqAddressRequest.newBuilder().build();
+ }
+
+ protected final SendBsqRequest createSendBsqRequest(String address, double amount) {
+ return SendBsqRequest.newBuilder().setAddress(address).setAmount(amount).build();
+ }
+
protected final GetFundingAddressesRequest createGetFundingAddressesRequest() {
return GetFundingAddressesRequest.newBuilder().build();
}
@@ -143,8 +175,14 @@ protected final CancelOfferRequest createCancelOfferRequest(String offerId) {
return CancelOfferRequest.newBuilder().setId(offerId).build();
}
- protected final TakeOfferRequest createTakeOfferRequest(String offerId, String paymentAccountId) {
- return TakeOfferRequest.newBuilder().setOfferId(offerId).setPaymentAccountId(paymentAccountId).build();
+ protected final TakeOfferRequest createTakeOfferRequest(String offerId,
+ String paymentAccountId,
+ String takerFeeCurrencyCode) {
+ return TakeOfferRequest.newBuilder()
+ .setOfferId(offerId)
+ .setPaymentAccountId(paymentAccountId)
+ .setTakerFeeCurrencyCode(takerFeeCurrencyCode)
+ .build();
}
protected final GetTradeRequest createGetTradeRequest(String tradeId) {
@@ -173,9 +211,21 @@ protected final WithdrawFundsRequest createWithdrawFundsRequest(String tradeId,
}
// Convenience methods for calling frequently used & thoroughly tested gRPC services.
+ protected final BalancesInfo getBalances(BisqAppConfig bisqAppConfig, String currencyCode) {
+ return grpcStubs(bisqAppConfig).walletsService.getBalances(
+ createGetBalancesRequest(currencyCode)).getBalances();
+ }
- protected final long getBalance(BisqAppConfig bisqAppConfig) {
- return grpcStubs(bisqAppConfig).walletsService.getBalance(createBalanceRequest()).getBalance();
+ protected final BsqBalanceInfo getBsqBalances(BisqAppConfig bisqAppConfig) {
+ return getBalances(bisqAppConfig, "bsq").getBsq();
+ }
+
+ protected final BtcBalanceInfo getBtcBalances(BisqAppConfig bisqAppConfig) {
+ return getBalances(bisqAppConfig, "btc").getBtc();
+ }
+
+ protected final AddressBalanceInfo getAddressBalance(BisqAppConfig bisqAppConfig, String address) {
+ return grpcStubs(bisqAppConfig).walletsService.getAddressBalance(createGetAddressBalanceRequest(address)).getAddressBalanceInfo();
}
protected final void unlockWallet(BisqAppConfig bisqAppConfig, String password, long timeout) {
@@ -188,6 +238,15 @@ protected final void lockWallet(BisqAppConfig bisqAppConfig) {
grpcStubs(bisqAppConfig).walletsService.lockWallet(createLockWalletRequest());
}
+ protected final String getUnusedBsqAddress(BisqAppConfig bisqAppConfig) {
+ return grpcStubs(bisqAppConfig).walletsService.getUnusedBsqAddress(createGetUnusedBsqAddressRequest()).getAddress();
+ }
+
+ protected final void sendBsq(BisqAppConfig bisqAppConfig, String address, double amount) {
+ //noinspection ResultOfMethodCallIgnored
+ grpcStubs(bisqAppConfig).walletsService.sendBsq(createSendBsqRequest(address, amount));
+ }
+
protected final String getUnusedBtcAddress(BisqAppConfig bisqAppConfig) {
//noinspection OptionalGetWithoutIsPresent
return grpcStubs(bisqAppConfig).walletsService.getFundingAddresses(createGetFundingAddressesRequest())
@@ -199,26 +258,53 @@ protected final String getUnusedBtcAddress(BisqAppConfig bisqAppConfig) {
.getAddress();
}
- protected final CreatePaymentAccountRequest createCreatePerfectMoneyPaymentAccountRequest(
- String accountName,
- String accountNumber,
- String currencyCode) {
- return CreatePaymentAccountRequest.newBuilder()
- .setPaymentMethodId(PERFECT_MONEY.getId())
- .setAccountName(accountName)
- .setAccountNumber(accountNumber)
- .setCurrencyCode(currencyCode)
+ protected final List getPaymentMethods(BisqAppConfig bisqAppConfig) {
+ var req = GetPaymentMethodsRequest.newBuilder().build();
+ return grpcStubs(bisqAppConfig).paymentAccountsService.getPaymentMethods(req).getPaymentMethodsList();
+ }
+
+ protected final File getPaymentAccountForm(BisqAppConfig bisqAppConfig, String paymentMethodId) {
+ // We take seemingly unnecessary steps to get a File object, but the point is to
+ // test the API, and we do not directly ask bisq.core.api.model.PaymentAccountForm
+ // for an empty json form (file).
+ var req = GetPaymentAccountFormRequest.newBuilder()
+ .setPaymentMethodId(paymentMethodId)
.build();
+ String jsonString = grpcStubs(bisqAppConfig).paymentAccountsService.getPaymentAccountForm(req)
+ .getPaymentAccountFormJson();
+ // Write the json string to a file here in the test case.
+ File jsonFile = PaymentAccountForm.getTmpJsonFile(paymentMethodId);
+ try (PrintWriter out = new PrintWriter(jsonFile, StandardCharsets.UTF_8)) {
+ out.println(jsonString);
+ } catch (IOException ex) {
+ fail("Could not create tmp payment account form.", ex);
+ }
+ return jsonFile;
}
- protected static PaymentAccount getDefaultPerfectDummyPaymentAccount(BisqAppConfig bisqAppConfig) {
- var req = GetPaymentAccountsRequest.newBuilder().build();
+ protected final bisq.core.payment.PaymentAccount createPaymentAccount(BisqAppConfig bisqAppConfig,
+ String jsonString) {
+ var req = CreatePaymentAccountRequest.newBuilder()
+ .setPaymentAccountForm(jsonString)
+ .build();
var paymentAccountsService = grpcStubs(bisqAppConfig).paymentAccountsService;
- PaymentAccount paymentAccount = paymentAccountsService.getPaymentAccounts(req)
+ // Normally, we can do asserts on the protos from the gRPC service, but in this
+ // case we need to return a bisq.core.payment.PaymentAccount so it can be cast
+ // to its sub type.
+ return fromProto(paymentAccountsService.createPaymentAccount(req).getPaymentAccount());
+ }
+
+ protected static List getPaymentAccounts(BisqAppConfig bisqAppConfig) {
+ var req = GetPaymentAccountsRequest.newBuilder().build();
+ return grpcStubs(bisqAppConfig).paymentAccountsService.getPaymentAccounts(req)
.getPaymentAccountsList()
.stream()
.sorted(comparing(PaymentAccount::getCreationDate))
- .collect(Collectors.toList()).get(0);
+ .collect(Collectors.toList());
+ }
+
+ protected static PaymentAccount getDefaultPerfectDummyPaymentAccount(BisqAppConfig bisqAppConfig) {
+ PaymentAccount paymentAccount = getPaymentAccounts(bisqAppConfig).get(0);
assertEquals("PerfectMoney dummy", paymentAccount.getAccountName());
return paymentAccount;
}
@@ -275,10 +361,14 @@ protected static RegisterDisputeAgentRequest createRegisterDisputeAgentRequest(S
.setRegistrationKey(DEV_PRIVILEGE_PRIV_KEY).build();
}
- @SuppressWarnings("ResultOfMethodCallIgnored")
+ @SuppressWarnings({"ResultOfMethodCallIgnored", "SameParameterValue"})
protected static void registerDisputeAgents(BisqAppConfig bisqAppConfig) {
var disputeAgentsService = grpcStubs(bisqAppConfig).disputeAgentsService;
disputeAgentsService.registerDisputeAgent(createRegisterDisputeAgentRequest(MEDIATOR));
disputeAgentsService.registerDisputeAgent(createRegisterDisputeAgentRequest(REFUND_AGENT));
}
+
+ private bisq.core.payment.PaymentAccount fromProto(PaymentAccount proto) {
+ return bisq.core.payment.PaymentAccount.fromProto(proto, CORE_PROTO_RESOLVER);
+ }
}
diff --git a/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java b/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java
index fe9a98aaaae..cd9a5771a80 100644
--- a/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java
+++ b/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java
@@ -73,22 +73,35 @@ public static void setUp() {
protected final OfferInfo createAliceOffer(PaymentAccount paymentAccount,
String direction,
String currencyCode,
- long amount) {
- return createMarketBasedPricedOffer(aliceStubs, paymentAccount, direction, currencyCode, amount);
+ long amount,
+ String makerFeeCurrencyCode) {
+ return createMarketBasedPricedOffer(aliceStubs,
+ paymentAccount,
+ direction,
+ currencyCode,
+ amount,
+ makerFeeCurrencyCode);
}
protected final OfferInfo createBobOffer(PaymentAccount paymentAccount,
String direction,
String currencyCode,
- long amount) {
- return createMarketBasedPricedOffer(bobStubs, paymentAccount, direction, currencyCode, amount);
+ long amount,
+ String makerFeeCurrencyCode) {
+ return createMarketBasedPricedOffer(bobStubs,
+ paymentAccount,
+ direction,
+ currencyCode,
+ amount,
+ makerFeeCurrencyCode);
}
protected final OfferInfo createMarketBasedPricedOffer(GrpcStubs grpcStubs,
PaymentAccount paymentAccount,
String direction,
String currencyCode,
- long amount) {
+ long amount,
+ String makerFeeCurrencyCode) {
var req = CreateOfferRequest.newBuilder()
.setPaymentAccountId(paymentAccount.getId())
.setDirection(direction)
@@ -99,6 +112,7 @@ protected final OfferInfo createMarketBasedPricedOffer(GrpcStubs grpcStubs,
.setMarketPriceMargin(0.00)
.setPrice("0")
.setBuyerSecurityDeposit(getDefaultBuyerSecurityDepositAsPercent())
+ .setMakerFeeCurrencyCode(makerFeeCurrencyCode)
.build();
return grpcStubs.offersService.createOffer(req).getOffer();
}
diff --git a/apitest/src/test/java/bisq/apitest/method/offer/CancelOfferTest.java b/apitest/src/test/java/bisq/apitest/method/offer/CancelOfferTest.java
index 334fb022bb3..8ec90aedad3 100644
--- a/apitest/src/test/java/bisq/apitest/method/offer/CancelOfferTest.java
+++ b/apitest/src/test/java/bisq/apitest/method/offer/CancelOfferTest.java
@@ -54,6 +54,7 @@ public void testCancelOffer() {
.setMarketPriceMargin(0.00)
.setPrice("0")
.setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent())
+ .setMakerFeeCurrencyCode("bsq")
.build();
// Create some offers.
diff --git a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingFixedPriceTest.java b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingFixedPriceTest.java
index 72ff91f3115..daa85d5e8e9 100644
--- a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingFixedPriceTest.java
+++ b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingFixedPriceTest.java
@@ -38,6 +38,8 @@
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
+ private static final String MAKER_FEE_CURRENCY_CODE = "bsq";
+
@Test
@Order(1)
public void testCreateAUDBTCBuyOfferUsingFixedPrice16000() {
@@ -51,6 +53,7 @@ public void testCreateAUDBTCBuyOfferUsingFixedPrice16000() {
.setMarketPriceMargin(0.00)
.setPrice("16000")
.setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent())
+ .setMakerFeeCurrencyCode(MAKER_FEE_CURRENCY_CODE)
.build();
var newOffer = aliceStubs.offersService.createOffer(req).getOffer();
String newOfferId = newOffer.getId();
@@ -64,6 +67,7 @@ public void testCreateAUDBTCBuyOfferUsingFixedPrice16000() {
assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId());
assertEquals("BTC", newOffer.getBaseCurrencyCode());
assertEquals("AUD", newOffer.getCounterCurrencyCode());
+ assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
newOffer = getOffer(newOfferId);
assertEquals(newOfferId, newOffer.getId());
@@ -76,6 +80,7 @@ public void testCreateAUDBTCBuyOfferUsingFixedPrice16000() {
assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId());
assertEquals("BTC", newOffer.getBaseCurrencyCode());
assertEquals("AUD", newOffer.getCounterCurrencyCode());
+ assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
}
@Test
@@ -91,6 +96,7 @@ public void testCreateUSDBTCBuyOfferUsingFixedPrice100001234() {
.setMarketPriceMargin(0.00)
.setPrice("10000.1234")
.setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent())
+ .setMakerFeeCurrencyCode(MAKER_FEE_CURRENCY_CODE)
.build();
var newOffer = aliceStubs.offersService.createOffer(req).getOffer();
String newOfferId = newOffer.getId();
@@ -104,6 +110,7 @@ public void testCreateUSDBTCBuyOfferUsingFixedPrice100001234() {
assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId());
assertEquals("BTC", newOffer.getBaseCurrencyCode());
assertEquals("USD", newOffer.getCounterCurrencyCode());
+ assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
newOffer = getOffer(newOfferId);
assertEquals(newOfferId, newOffer.getId());
@@ -116,6 +123,7 @@ public void testCreateUSDBTCBuyOfferUsingFixedPrice100001234() {
assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId());
assertEquals("BTC", newOffer.getBaseCurrencyCode());
assertEquals("USD", newOffer.getCounterCurrencyCode());
+ assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
}
@Test
@@ -131,6 +139,7 @@ public void testCreateEURBTCSellOfferUsingFixedPrice95001234() {
.setMarketPriceMargin(0.00)
.setPrice("9500.1234")
.setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent())
+ .setMakerFeeCurrencyCode(MAKER_FEE_CURRENCY_CODE)
.build();
var newOffer = aliceStubs.offersService.createOffer(req).getOffer();
String newOfferId = newOffer.getId();
@@ -144,6 +153,7 @@ public void testCreateEURBTCSellOfferUsingFixedPrice95001234() {
assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId());
assertEquals("BTC", newOffer.getBaseCurrencyCode());
assertEquals("EUR", newOffer.getCounterCurrencyCode());
+ assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
newOffer = getOffer(newOfferId);
assertEquals(newOfferId, newOffer.getId());
@@ -156,5 +166,6 @@ public void testCreateEURBTCSellOfferUsingFixedPrice95001234() {
assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId());
assertEquals("BTC", newOffer.getBaseCurrencyCode());
assertEquals("EUR", newOffer.getCounterCurrencyCode());
+ assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
}
}
diff --git a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java
index 345bd130d71..dbf712c9355 100644
--- a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java
+++ b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java
@@ -50,6 +50,8 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
private static final double MKT_PRICE_MARGIN_ERROR_TOLERANCE = 0.0050; // 0.50%
private static final double MKT_PRICE_MARGIN_WARNING_TOLERANCE = 0.0001; // 0.01%
+ private static final String MAKER_FEE_CURRENCY_CODE = "btc";
+
@Test
@Order(1)
public void testCreateUSDBTCBuyOffer5PctPriceMargin() {
@@ -64,6 +66,7 @@ public void testCreateUSDBTCBuyOffer5PctPriceMargin() {
.setMarketPriceMargin(priceMarginPctInput)
.setPrice("0")
.setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent())
+ .setMakerFeeCurrencyCode(MAKER_FEE_CURRENCY_CODE)
.build();
var newOffer = aliceStubs.offersService.createOffer(req).getOffer();
String newOfferId = newOffer.getId();
@@ -76,6 +79,7 @@ public void testCreateUSDBTCBuyOffer5PctPriceMargin() {
assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId());
assertEquals("BTC", newOffer.getBaseCurrencyCode());
assertEquals("USD", newOffer.getCounterCurrencyCode());
+ assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
newOffer = getOffer(newOfferId);
assertEquals(newOfferId, newOffer.getId());
@@ -87,6 +91,7 @@ public void testCreateUSDBTCBuyOffer5PctPriceMargin() {
assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId());
assertEquals("BTC", newOffer.getBaseCurrencyCode());
assertEquals("USD", newOffer.getCounterCurrencyCode());
+ assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
}
@@ -105,6 +110,7 @@ public void testCreateNZDBTCBuyOfferMinus2PctPriceMargin() {
.setMarketPriceMargin(priceMarginPctInput)
.setPrice("0")
.setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent())
+ .setMakerFeeCurrencyCode(MAKER_FEE_CURRENCY_CODE)
.build();
var newOffer = aliceStubs.offersService.createOffer(req).getOffer();
String newOfferId = newOffer.getId();
@@ -117,6 +123,7 @@ public void testCreateNZDBTCBuyOfferMinus2PctPriceMargin() {
assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId());
assertEquals("BTC", newOffer.getBaseCurrencyCode());
assertEquals("NZD", newOffer.getCounterCurrencyCode());
+ assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
newOffer = getOffer(newOfferId);
assertEquals(newOfferId, newOffer.getId());
@@ -128,6 +135,7 @@ public void testCreateNZDBTCBuyOfferMinus2PctPriceMargin() {
assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId());
assertEquals("BTC", newOffer.getBaseCurrencyCode());
assertEquals("NZD", newOffer.getCounterCurrencyCode());
+ assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
}
@@ -146,6 +154,7 @@ public void testCreateGBPBTCSellOfferMinus1Point5PctPriceMargin() {
.setMarketPriceMargin(priceMarginPctInput)
.setPrice("0")
.setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent())
+ .setMakerFeeCurrencyCode(MAKER_FEE_CURRENCY_CODE)
.build();
var newOffer = aliceStubs.offersService.createOffer(req).getOffer();
@@ -159,6 +168,7 @@ public void testCreateGBPBTCSellOfferMinus1Point5PctPriceMargin() {
assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId());
assertEquals("BTC", newOffer.getBaseCurrencyCode());
assertEquals("GBP", newOffer.getCounterCurrencyCode());
+ assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
newOffer = getOffer(newOfferId);
assertEquals(newOfferId, newOffer.getId());
@@ -170,6 +180,7 @@ public void testCreateGBPBTCSellOfferMinus1Point5PctPriceMargin() {
assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId());
assertEquals("BTC", newOffer.getBaseCurrencyCode());
assertEquals("GBP", newOffer.getCounterCurrencyCode());
+ assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
}
@@ -188,6 +199,7 @@ public void testCreateBRLBTCSellOffer6Point55PctPriceMargin() {
.setMarketPriceMargin(priceMarginPctInput)
.setPrice("0")
.setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent())
+ .setMakerFeeCurrencyCode(MAKER_FEE_CURRENCY_CODE)
.build();
var newOffer = aliceStubs.offersService.createOffer(req).getOffer();
@@ -201,6 +213,7 @@ public void testCreateBRLBTCSellOffer6Point55PctPriceMargin() {
assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId());
assertEquals("BTC", newOffer.getBaseCurrencyCode());
assertEquals("BRL", newOffer.getCounterCurrencyCode());
+ assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
newOffer = getOffer(newOfferId);
assertEquals(newOfferId, newOffer.getId());
@@ -212,6 +225,7 @@ public void testCreateBRLBTCSellOffer6Point55PctPriceMargin() {
assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId());
assertEquals("BTC", newOffer.getBaseCurrencyCode());
assertEquals("BRL", newOffer.getCounterCurrencyCode());
+ assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
}
diff --git a/apitest/src/test/java/bisq/apitest/method/offer/ValidateCreateOfferTest.java b/apitest/src/test/java/bisq/apitest/method/offer/ValidateCreateOfferTest.java
index 3ddd8cb3030..0225238b6a9 100644
--- a/apitest/src/test/java/bisq/apitest/method/offer/ValidateCreateOfferTest.java
+++ b/apitest/src/test/java/bisq/apitest/method/offer/ValidateCreateOfferTest.java
@@ -52,6 +52,7 @@ public void testAmtTooLargeShouldThrowException() {
.setMarketPriceMargin(0.00)
.setPrice("10000.0000")
.setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent())
+ .setMakerFeeCurrencyCode("bsq")
.build();
@SuppressWarnings("ResultOfMethodCallIgnored")
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
diff --git a/apitest/src/test/java/bisq/apitest/method/payment/AbstractPaymentAccountTest.java b/apitest/src/test/java/bisq/apitest/method/payment/AbstractPaymentAccountTest.java
new file mode 100644
index 00000000000..14b145e0444
--- /dev/null
+++ b/apitest/src/test/java/bisq/apitest/method/payment/AbstractPaymentAccountTest.java
@@ -0,0 +1,196 @@
+package bisq.apitest.method.payment;
+
+import bisq.core.api.model.PaymentAccountForm;
+import bisq.core.locale.Res;
+import bisq.core.locale.TradeCurrency;
+import bisq.core.payment.PaymentAccount;
+
+import bisq.proto.grpc.GetPaymentAccountsRequest;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.stream.JsonWriter;
+
+import java.nio.file.Paths;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import lombok.extern.slf4j.Slf4j;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.TestInfo;
+
+import static bisq.apitest.config.BisqAppConfig.alicedaemon;
+import static java.lang.String.format;
+import static java.lang.System.getProperty;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.*;
+
+
+
+import bisq.apitest.method.MethodTest;
+
+@Slf4j
+public class AbstractPaymentAccountTest extends MethodTest {
+
+ static final String PROPERTY_NAME_COMMENT = "_COMMENT_";
+ static final String PROPERTY_VALUE_COMMENT = "Please do not edit the paymentMethodId field.";
+
+ static final String PROPERTY_NAME_PAYMENT_METHOD_ID = "paymentMethodId";
+
+ static final String PROPERTY_NAME_ACCOUNT_ID = "accountId";
+ static final String PROPERTY_NAME_ACCOUNT_NAME = "accountName";
+ static final String PROPERTY_NAME_ACCOUNT_NR = "accountNr";
+ static final String PROPERTY_NAME_ACCOUNT_TYPE = "accountType";
+ static final String PROPERTY_NAME_ANSWER = "answer";
+ static final String PROPERTY_NAME_BANK_ACCOUNT_NAME = "bankAccountName";
+ static final String PROPERTY_NAME_BANK_ACCOUNT_NUMBER = "bankAccountNumber";
+ static final String PROPERTY_NAME_BANK_ACCOUNT_TYPE = "bankAccountType";
+ static final String PROPERTY_NAME_BANK_BRANCH_CODE = "bankBranchCode";
+ static final String PROPERTY_NAME_BANK_BRANCH_NAME = "bankBranchName";
+ static final String PROPERTY_NAME_BANK_CODE = "bankCode";
+ @SuppressWarnings("unused")
+ static final String PROPERTY_NAME_BANK_ID = "bankId";
+ static final String PROPERTY_NAME_BANK_NAME = "bankName";
+ static final String PROPERTY_NAME_BRANCH_ID = "branchId";
+ static final String PROPERTY_NAME_BIC = "bic";
+ static final String PROPERTY_NAME_COUNTRY = "country";
+ static final String PROPERTY_NAME_CITY = "city";
+ static final String PROPERTY_NAME_CONTACT = "contact";
+ static final String PROPERTY_NAME_EMAIL = "email";
+ static final String PROPERTY_NAME_EMAIL_OR_MOBILE_NR = "emailOrMobileNr";
+ static final String PROPERTY_NAME_EXTRA_INFO = "extraInfo";
+ static final String PROPERTY_NAME_HOLDER_EMAIL = "holderEmail";
+ static final String PROPERTY_NAME_HOLDER_NAME = "holderName";
+ static final String PROPERTY_NAME_HOLDER_TAX_ID = "holderTaxId";
+ static final String PROPERTY_NAME_IBAN = "iban";
+ static final String PROPERTY_NAME_MOBILE_NR = "mobileNr";
+ static final String PROPERTY_NAME_NATIONAL_ACCOUNT_ID = "nationalAccountId";
+ static final String PROPERTY_NAME_PAY_ID = "payid";
+ static final String PROPERTY_NAME_POSTAL_ADDRESS = "postalAddress";
+ static final String PROPERTY_NAME_PROMPT_PAY_ID = "promptPayId";
+ static final String PROPERTY_NAME_QUESTION = "question";
+ static final String PROPERTY_NAME_REQUIREMENTS = "requirements";
+ static final String PROPERTY_NAME_SORT_CODE = "sortCode";
+ static final String PROPERTY_NAME_STATE = "state";
+ static final String PROPERTY_NAME_USERNAME = "userName";
+
+ static final Gson GSON = new GsonBuilder()
+ .setPrettyPrinting()
+ .serializeNulls()
+ .create();
+
+ static final Map COMPLETED_FORM_MAP = new HashMap<>();
+
+ // A payment account serializer / deserializer.
+ static final PaymentAccountForm PAYMENT_ACCOUNT_FORM = new PaymentAccountForm();
+
+ @BeforeEach
+ public void setup() {
+ Res.setup();
+ }
+
+ protected final File getEmptyForm(TestInfo testInfo, String paymentMethodId) {
+ // This would normally be done in @BeforeEach, but these test cases might be
+ // called from a single 'scenario' test case, and the @BeforeEach -> clear()
+ // would be skipped.
+ COMPLETED_FORM_MAP.clear();
+
+ File emptyForm = getPaymentAccountForm(alicedaemon, paymentMethodId);
+ // A short cut over the API:
+ // File emptyForm = PAYMENT_ACCOUNT_FORM.getPaymentAccountForm(paymentMethodId);
+ emptyForm.deleteOnExit();
+
+ if (log.isDebugEnabled())
+ log.debug("{} Empty form saved to {}", testName(testInfo), PAYMENT_ACCOUNT_FORM.getClickableURI(emptyForm));
+
+ return emptyForm;
+ }
+
+ protected final void verifyEmptyForm(File jsonForm, String paymentMethodId, String... fields) {
+ @SuppressWarnings("unchecked")
+ Map emptyForm = (Map) GSON.fromJson(
+ PAYMENT_ACCOUNT_FORM.toJsonString(jsonForm),
+ Object.class);
+ assertNotNull(emptyForm);
+ assertEquals(PROPERTY_VALUE_COMMENT, emptyForm.get(PROPERTY_NAME_COMMENT));
+ assertEquals(paymentMethodId, emptyForm.get(PROPERTY_NAME_PAYMENT_METHOD_ID));
+ assertEquals("Your accountname", emptyForm.get(PROPERTY_NAME_ACCOUNT_NAME));
+ for (String field : fields) {
+ assertEquals("Your " + field.toLowerCase(), emptyForm.get(field));
+ }
+ }
+
+ protected final void verifyCommonFormEntries(PaymentAccount paymentAccount) {
+ // All PaymentAccount subclasses have paymentMethodId and an accountName fields.
+ assertNotNull(paymentAccount);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_PAYMENT_METHOD_ID), paymentAccount.getPaymentMethod().getId());
+ assertTrue(paymentAccount.getCreationDate().getTime() > 0);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NAME), paymentAccount.getAccountName());
+ }
+
+ protected final void verifyAccountSingleTradeCurrency(String expectedCurrencyCode, PaymentAccount paymentAccount) {
+ assertNotNull(paymentAccount.getSingleTradeCurrency());
+ assertEquals(expectedCurrencyCode, paymentAccount.getSingleTradeCurrency().getCode());
+ }
+
+ protected final void verifyAccountTradeCurrencies(List expectedTradeCurrencies,
+ PaymentAccount paymentAccount) {
+ assertNotNull(paymentAccount.getTradeCurrencies());
+ assertArrayEquals(expectedTradeCurrencies.toArray(), paymentAccount.getTradeCurrencies().toArray());
+ }
+
+ protected final void verifyUserPayloadHasPaymentAccountWithId(String paymentAccountId) {
+ var getPaymentAccountsRequest = GetPaymentAccountsRequest.newBuilder().build();
+ var reply = grpcStubs(alicedaemon)
+ .paymentAccountsService.getPaymentAccounts(getPaymentAccountsRequest);
+ Optional paymentAccount = reply.getPaymentAccountsList().stream()
+ .filter(a -> a.getId().equals(paymentAccountId))
+ .findFirst();
+ assertTrue(paymentAccount.isPresent());
+ }
+
+ protected final String getCompletedFormAsJsonString() {
+ File completedForm = fillPaymentAccountForm();
+ String jsonString = PAYMENT_ACCOUNT_FORM.toJsonString(completedForm);
+ if (log.isDebugEnabled())
+ log.debug("Completed form: {}", jsonString);
+
+ return jsonString;
+ }
+
+ private File fillPaymentAccountForm() {
+ File tmpJsonForm = null;
+ try {
+ tmpJsonForm = File.createTempFile("temp_acct_form_",
+ ".json",
+ Paths.get(getProperty("java.io.tmpdir")).toFile());
+ tmpJsonForm.deleteOnExit();
+ JsonWriter writer = new JsonWriter(new OutputStreamWriter(new FileOutputStream(tmpJsonForm), UTF_8));
+ writer.beginObject();
+ writer.name(PROPERTY_NAME_COMMENT);
+ writer.value(PROPERTY_VALUE_COMMENT);
+ for (Map.Entry entry : COMPLETED_FORM_MAP.entrySet()) {
+ String k = entry.getKey();
+ Object v = entry.getValue();
+ writer.name(k);
+ writer.value(v.toString());
+ }
+ writer.endObject();
+ writer.close();
+ } catch (IOException ex) {
+ log.error("", ex);
+ fail(format("Could not write json file from form entries %s", COMPLETED_FORM_MAP));
+ }
+ return tmpJsonForm;
+ }
+
+}
diff --git a/apitest/src/test/java/bisq/apitest/method/payment/CreatePaymentAccountTest.java b/apitest/src/test/java/bisq/apitest/method/payment/CreatePaymentAccountTest.java
new file mode 100644
index 00000000000..5e1305b0c0c
--- /dev/null
+++ b/apitest/src/test/java/bisq/apitest/method/payment/CreatePaymentAccountTest.java
@@ -0,0 +1,839 @@
+/*
+ * 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.payment;
+
+import bisq.core.payment.AdvancedCashAccount;
+import bisq.core.payment.AliPayAccount;
+import bisq.core.payment.AustraliaPayid;
+import bisq.core.payment.CashDepositAccount;
+import bisq.core.payment.ChaseQuickPayAccount;
+import bisq.core.payment.ClearXchangeAccount;
+import bisq.core.payment.F2FAccount;
+import bisq.core.payment.FasterPaymentsAccount;
+import bisq.core.payment.HalCashAccount;
+import bisq.core.payment.InteracETransferAccount;
+import bisq.core.payment.JapanBankAccount;
+import bisq.core.payment.MoneyBeamAccount;
+import bisq.core.payment.MoneyGramAccount;
+import bisq.core.payment.NationalBankAccount;
+import bisq.core.payment.PerfectMoneyAccount;
+import bisq.core.payment.PopmoneyAccount;
+import bisq.core.payment.PromptPayAccount;
+import bisq.core.payment.RevolutAccount;
+import bisq.core.payment.SameBankAccount;
+import bisq.core.payment.SepaAccount;
+import bisq.core.payment.SepaInstantAccount;
+import bisq.core.payment.SpecificBanksAccount;
+import bisq.core.payment.SwishAccount;
+import bisq.core.payment.TransferwiseAccount;
+import bisq.core.payment.USPostalMoneyOrderAccount;
+import bisq.core.payment.UpholdAccount;
+import bisq.core.payment.WeChatPayAccount;
+import bisq.core.payment.WesternUnionAccount;
+import bisq.core.payment.payload.BankAccountPayload;
+import bisq.core.payment.payload.CashDepositAccountPayload;
+import bisq.core.payment.payload.SameBankAccountPayload;
+import bisq.core.payment.payload.SpecificBanksAccountPayload;
+
+import java.io.File;
+
+import java.util.Objects;
+
+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.Test;
+import org.junit.jupiter.api.TestInfo;
+import org.junit.jupiter.api.TestMethodOrder;
+
+import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
+import static bisq.apitest.config.BisqAppConfig.alicedaemon;
+import static bisq.core.locale.CurrencyUtil.*;
+import static bisq.core.payment.payload.PaymentMethod.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
+
+@Disabled
+@Slf4j
+@TestMethodOrder(OrderAnnotation.class)
+public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
+
+ @BeforeAll
+ public static void setUp() {
+ try {
+ setUpScaffold(bitcoind, alicedaemon);
+ } catch (Exception ex) {
+ fail(ex);
+ }
+ }
+
+ @Test
+ public void testCreateAdvancedCashAccount(TestInfo testInfo) {
+ File emptyForm = getEmptyForm(testInfo, ADVANCED_CASH_ID);
+ verifyEmptyForm(emptyForm,
+ ADVANCED_CASH_ID,
+ PROPERTY_NAME_ACCOUNT_NR);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, ADVANCED_CASH_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Advanced Cash Acct");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NR, "0000 1111 2222");
+ String jsonString = getCompletedFormAsJsonString();
+ AdvancedCashAccount paymentAccount = (AdvancedCashAccount) createPaymentAccount(alicedaemon, jsonString);
+ verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
+ verifyAccountTradeCurrencies(getAllAdvancedCashCurrencies(), paymentAccount);
+ verifyCommonFormEntries(paymentAccount);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), paymentAccount.getAccountNr());
+ if (log.isDebugEnabled())
+ log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
+ }
+
+ @Test
+ public void testCreateAliPayAccount(TestInfo testInfo) {
+ File emptyForm = getEmptyForm(testInfo, ALI_PAY_ID);
+ verifyEmptyForm(emptyForm,
+ ALI_PAY_ID,
+ PROPERTY_NAME_ACCOUNT_NR);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, ALI_PAY_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Ali Pay Acct");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NR, "2222 3333 4444");
+ String jsonString = getCompletedFormAsJsonString();
+ AliPayAccount paymentAccount = (AliPayAccount) createPaymentAccount(alicedaemon, jsonString);
+ verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
+ verifyAccountSingleTradeCurrency("CNY", paymentAccount);
+ verifyCommonFormEntries(paymentAccount);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), paymentAccount.getAccountNr());
+ if (log.isDebugEnabled())
+ log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
+ }
+
+ @Test
+ public void testCreateAustraliaPayidAccount(TestInfo testInfo) {
+ File emptyForm = getEmptyForm(testInfo, AUSTRALIA_PAYID_ID);
+ verifyEmptyForm(emptyForm,
+ AUSTRALIA_PAYID_ID,
+ PROPERTY_NAME_BANK_ACCOUNT_NAME);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, AUSTRALIA_PAYID_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Australia Pay ID Account");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAY_ID, "123 456 789");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_ACCOUNT_NAME, "Credit Union Australia");
+ String jsonString = getCompletedFormAsJsonString();
+ AustraliaPayid paymentAccount = (AustraliaPayid) createPaymentAccount(alicedaemon, jsonString);
+ verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
+ verifyAccountSingleTradeCurrency("AUD", paymentAccount);
+ verifyCommonFormEntries(paymentAccount);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_PAY_ID), paymentAccount.getPayid());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_ACCOUNT_NAME), paymentAccount.getBankAccountName());
+ if (log.isDebugEnabled())
+ log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
+ }
+
+ @Test
+ public void testCreateCashDepositAccount(TestInfo testInfo) {
+ File emptyForm = getEmptyForm(testInfo, CASH_DEPOSIT_ID);
+ verifyEmptyForm(emptyForm,
+ CASH_DEPOSIT_ID,
+ PROPERTY_NAME_ACCOUNT_NR,
+ PROPERTY_NAME_ACCOUNT_TYPE,
+ PROPERTY_NAME_BANK_ID,
+ PROPERTY_NAME_BANK_NAME,
+ PROPERTY_NAME_BRANCH_ID,
+ PROPERTY_NAME_COUNTRY,
+ PROPERTY_NAME_HOLDER_EMAIL,
+ PROPERTY_NAME_HOLDER_NAME,
+ PROPERTY_NAME_HOLDER_TAX_ID,
+ PROPERTY_NAME_NATIONAL_ACCOUNT_ID,
+ PROPERTY_NAME_REQUIREMENTS);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, CASH_DEPOSIT_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Cash Deposit Account");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NR, "4444 5555 6666");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_TYPE, "Checking");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_ID, "0001");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_NAME, "BoF");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_BRANCH_ID, "99-8888-7654");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_COUNTRY, "FR");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_EMAIL, "jean@johnson.info");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "Jean Johnson");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_TAX_ID, "123456789");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_NATIONAL_ACCOUNT_ID, "123456789");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_REQUIREMENTS, "Requirements...");
+ String jsonString = getCompletedFormAsJsonString();
+ CashDepositAccount paymentAccount = (CashDepositAccount) createPaymentAccount(alicedaemon, jsonString);
+ verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
+ verifyAccountSingleTradeCurrency("EUR", paymentAccount);
+ verifyCommonFormEntries(paymentAccount);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
+ Objects.requireNonNull(paymentAccount.getCountry()).code);
+
+ CashDepositAccountPayload payload = (CashDepositAccountPayload) paymentAccount.getPaymentAccountPayload();
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), payload.getAccountNr());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_TYPE), payload.getAccountType());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_ID), payload.getBankId());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_NAME), payload.getBankName());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BRANCH_ID), payload.getBranchId());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_EMAIL), payload.getHolderEmail());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), payload.getHolderName());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_TAX_ID), payload.getHolderTaxId());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_NATIONAL_ACCOUNT_ID), payload.getNationalAccountId());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_REQUIREMENTS), payload.getRequirements());
+ if (log.isDebugEnabled())
+ log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
+ }
+
+ @Test
+ public void testCreateBrazilNationalBankAccount(TestInfo testInfo) {
+ File emptyForm = getEmptyForm(testInfo, NATIONAL_BANK_ID);
+ verifyEmptyForm(emptyForm,
+ NATIONAL_BANK_ID,
+ PROPERTY_NAME_ACCOUNT_NR,
+ PROPERTY_NAME_ACCOUNT_TYPE,
+ PROPERTY_NAME_BANK_NAME,
+ PROPERTY_NAME_BRANCH_ID,
+ PROPERTY_NAME_COUNTRY,
+ PROPERTY_NAME_HOLDER_NAME,
+ PROPERTY_NAME_HOLDER_TAX_ID,
+ PROPERTY_NAME_NATIONAL_ACCOUNT_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, NATIONAL_BANK_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Banco do Brasil");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NR, "456789-87");
+ // No BankId is required for BR.
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_NAME, "Banco do Brasil");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_BRANCH_ID, "456789-10");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_COUNTRY, "BR");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "Joao da Silva");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_TAX_ID, "123456789");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_NATIONAL_ACCOUNT_ID, "123456789");
+ String jsonString = getCompletedFormAsJsonString();
+ NationalBankAccount paymentAccount = (NationalBankAccount) createPaymentAccount(alicedaemon, jsonString);
+ verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
+ verifyAccountSingleTradeCurrency("BRL", paymentAccount);
+ verifyCommonFormEntries(paymentAccount);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
+ Objects.requireNonNull(paymentAccount.getCountry()).code);
+
+ BankAccountPayload payload = (BankAccountPayload) paymentAccount.getPaymentAccountPayload();
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), payload.getAccountNr());
+ // When no BankId is required, getBankId() returns bankName.
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_NAME), payload.getBankId());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_NAME), payload.getBankName());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BRANCH_ID), payload.getBranchId());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), payload.getHolderName());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_TAX_ID), payload.getHolderTaxId());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_NATIONAL_ACCOUNT_ID), payload.getNationalAccountId());
+ if (log.isDebugEnabled())
+ log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
+ }
+
+ @Test
+ public void testCreateChaseQuickPayAccount(TestInfo testInfo) {
+ File emptyForm = getEmptyForm(testInfo, CHASE_QUICK_PAY_ID);
+ verifyEmptyForm(emptyForm,
+ CHASE_QUICK_PAY_ID,
+ PROPERTY_NAME_EMAIL,
+ PROPERTY_NAME_HOLDER_NAME);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, CHASE_QUICK_PAY_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Quick Pay Acct");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "johndoe@quickpay.com");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "John Doe");
+ String jsonString = getCompletedFormAsJsonString();
+ ChaseQuickPayAccount paymentAccount = (ChaseQuickPayAccount) createPaymentAccount(alicedaemon, jsonString);
+ verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
+ verifyAccountSingleTradeCurrency("USD", paymentAccount);
+ verifyCommonFormEntries(paymentAccount);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
+ if (log.isDebugEnabled())
+ log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
+ }
+
+ @Test
+ public void testCreateClearXChangeAccount(TestInfo testInfo) {
+ File emptyForm = getEmptyForm(testInfo, CLEAR_X_CHANGE_ID);
+ verifyEmptyForm(emptyForm,
+ CLEAR_X_CHANGE_ID,
+ PROPERTY_NAME_EMAIL_OR_MOBILE_NR,
+ PROPERTY_NAME_HOLDER_NAME);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, CLEAR_X_CHANGE_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "USD Zelle Account");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL_OR_MOBILE_NR, "jane@doe.com");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "Jane Doe");
+ String jsonString = getCompletedFormAsJsonString();
+ ClearXchangeAccount paymentAccount = (ClearXchangeAccount) createPaymentAccount(alicedaemon, jsonString);
+ verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
+ verifyAccountSingleTradeCurrency("USD", paymentAccount);
+ verifyCommonFormEntries(paymentAccount);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL_OR_MOBILE_NR), paymentAccount.getEmailOrMobileNr());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
+ if (log.isDebugEnabled())
+ log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
+ }
+
+ @Test
+ public void testCreateF2FAccount(TestInfo testInfo) {
+ File emptyForm = getEmptyForm(testInfo, F2F_ID);
+ verifyEmptyForm(emptyForm,
+ F2F_ID,
+ PROPERTY_NAME_COUNTRY,
+ PROPERTY_NAME_CITY,
+ PROPERTY_NAME_CONTACT,
+ PROPERTY_NAME_EXTRA_INFO);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, F2F_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Conta Cara a Cara");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_COUNTRY, "BR");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_CITY, "Rio de Janeiro");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_CONTACT, "Freddy Beira Mar");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_EXTRA_INFO, "So fim de semana");
+ String jsonString = getCompletedFormAsJsonString();
+ F2FAccount paymentAccount = (F2FAccount) createPaymentAccount(alicedaemon, jsonString);
+ verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
+ verifyAccountSingleTradeCurrency("BRL", paymentAccount);
+ verifyCommonFormEntries(paymentAccount);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
+ Objects.requireNonNull(paymentAccount.getCountry()).code);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_CITY), paymentAccount.getCity());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_CONTACT), paymentAccount.getContact());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EXTRA_INFO), paymentAccount.getExtraInfo());
+ if (log.isDebugEnabled())
+ log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
+ }
+
+ @Test
+ public void testCreateFasterPaymentsAccount(TestInfo testInfo) {
+ File emptyForm = getEmptyForm(testInfo, FASTER_PAYMENTS_ID);
+ verifyEmptyForm(emptyForm,
+ FASTER_PAYMENTS_ID,
+ PROPERTY_NAME_ACCOUNT_NR,
+ PROPERTY_NAME_SORT_CODE);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, FASTER_PAYMENTS_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Faster Payments Acct");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NR, "9999 8888 7777");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_SORT_CODE, "3127");
+ String jsonString = getCompletedFormAsJsonString();
+ FasterPaymentsAccount paymentAccount = (FasterPaymentsAccount) createPaymentAccount(alicedaemon, jsonString);
+ verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
+ verifyAccountSingleTradeCurrency("GBP", paymentAccount);
+ verifyCommonFormEntries(paymentAccount);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), paymentAccount.getAccountNr());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SORT_CODE), paymentAccount.getSortCode());
+ if (log.isDebugEnabled())
+ log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
+ }
+
+ @Test
+ public void testCreateHalCashAccount(TestInfo testInfo) {
+ File emptyForm = getEmptyForm(testInfo, HAL_CASH_ID);
+ verifyEmptyForm(emptyForm,
+ HAL_CASH_ID,
+ PROPERTY_NAME_MOBILE_NR);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, HAL_CASH_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Hal Cash Acct");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_MOBILE_NR, "798 123 456");
+ String jsonString = getCompletedFormAsJsonString();
+ HalCashAccount paymentAccount = (HalCashAccount) createPaymentAccount(alicedaemon, jsonString);
+ verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
+ verifyAccountSingleTradeCurrency("EUR", paymentAccount);
+ verifyCommonFormEntries(paymentAccount);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_MOBILE_NR), paymentAccount.getMobileNr());
+ if (log.isDebugEnabled())
+ log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
+ }
+
+ @Test
+ public void testCreateInteracETransferAccount(TestInfo testInfo) {
+ File emptyForm = getEmptyForm(testInfo, INTERAC_E_TRANSFER_ID);
+ verifyEmptyForm(emptyForm,
+ INTERAC_E_TRANSFER_ID,
+ PROPERTY_NAME_HOLDER_NAME,
+ PROPERTY_NAME_EMAIL,
+ PROPERTY_NAME_QUESTION,
+ PROPERTY_NAME_ANSWER);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, INTERAC_E_TRANSFER_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Interac Transfer Acct");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "John Doe");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "john@doe.info");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_QUESTION, "What is my dog's name?");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ANSWER, "Fido");
+ String jsonString = getCompletedFormAsJsonString();
+ InteracETransferAccount paymentAccount = (InteracETransferAccount) createPaymentAccount(alicedaemon, jsonString);
+ verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
+ verifyAccountSingleTradeCurrency("CAD", paymentAccount);
+ verifyCommonFormEntries(paymentAccount);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_QUESTION), paymentAccount.getQuestion());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ANSWER), paymentAccount.getAnswer());
+ if (log.isDebugEnabled())
+ log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
+ }
+
+ @Test
+ public void testCreateJapanBankAccount(TestInfo testInfo) {
+ File emptyForm = getEmptyForm(testInfo, JAPAN_BANK_ID);
+ verifyEmptyForm(emptyForm,
+ JAPAN_BANK_ID,
+ PROPERTY_NAME_BANK_NAME,
+ PROPERTY_NAME_BANK_CODE,
+ PROPERTY_NAME_BANK_BRANCH_CODE,
+ PROPERTY_NAME_BANK_BRANCH_NAME,
+ PROPERTY_NAME_BANK_ACCOUNT_NAME,
+ PROPERTY_NAME_BANK_ACCOUNT_TYPE,
+ PROPERTY_NAME_BANK_ACCOUNT_NUMBER);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, JAPAN_BANK_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Fukuoka Account");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_NAME, "Bank of Kyoto");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_CODE, "FKBKJPJT");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_BRANCH_CODE, "8100-8727");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_BRANCH_NAME, "Fukuoka Branch");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_ACCOUNT_NAME, "Fukuoka Account");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_ACCOUNT_TYPE, "Yen Account");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_ACCOUNT_NUMBER, "8100-8727-0000");
+ String jsonString = getCompletedFormAsJsonString();
+ JapanBankAccount paymentAccount = (JapanBankAccount) createPaymentAccount(alicedaemon, jsonString);
+ verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
+ verifyAccountSingleTradeCurrency("JPY", paymentAccount);
+ verifyCommonFormEntries(paymentAccount);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_CODE), paymentAccount.getBankCode());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_NAME), paymentAccount.getBankName());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_BRANCH_CODE), paymentAccount.getBankBranchCode());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_BRANCH_NAME), paymentAccount.getBankBranchName());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_ACCOUNT_NAME), paymentAccount.getBankAccountName());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_ACCOUNT_TYPE), paymentAccount.getBankAccountType());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_ACCOUNT_NUMBER), paymentAccount.getBankAccountNumber());
+ if (log.isDebugEnabled())
+ log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
+ }
+
+ @Test
+ public void testCreateMoneyBeamAccount(TestInfo testInfo) {
+ File emptyForm = getEmptyForm(testInfo, MONEY_BEAM_ID);
+ verifyEmptyForm(emptyForm,
+ MONEY_BEAM_ID,
+ PROPERTY_NAME_ACCOUNT_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, MONEY_BEAM_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Money Beam Acct");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_ID, "MB 0000 1111");
+ String jsonString = getCompletedFormAsJsonString();
+ MoneyBeamAccount paymentAccount = (MoneyBeamAccount) createPaymentAccount(alicedaemon, jsonString);
+ verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
+ verifyAccountSingleTradeCurrency("EUR", paymentAccount);
+ verifyCommonFormEntries(paymentAccount);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_ID), paymentAccount.getAccountId());
+ if (log.isDebugEnabled())
+ log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
+ }
+
+ @Test
+ public void testCreateMoneyGramAccount(TestInfo testInfo) {
+ File emptyForm = getEmptyForm(testInfo, MONEY_GRAM_ID);
+ verifyEmptyForm(emptyForm,
+ MONEY_GRAM_ID,
+ PROPERTY_NAME_HOLDER_NAME,
+ PROPERTY_NAME_EMAIL,
+ PROPERTY_NAME_COUNTRY,
+ PROPERTY_NAME_STATE);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, MONEY_GRAM_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Money Gram Acct");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "John Doe");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "john@doe.info");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_COUNTRY, "US");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_STATE, "NY");
+ String jsonString = getCompletedFormAsJsonString();
+ MoneyGramAccount paymentAccount = (MoneyGramAccount) createPaymentAccount(alicedaemon, jsonString);
+ verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
+ verifyAccountTradeCurrencies(getAllMoneyGramCurrencies(), paymentAccount);
+ verifyCommonFormEntries(paymentAccount);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getFullName());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
+ Objects.requireNonNull(paymentAccount.getCountry()).code);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_STATE), paymentAccount.getState());
+ if (log.isDebugEnabled())
+ log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
+ }
+
+ @Test
+ public void testCreatePerfectMoneyAccount(TestInfo testInfo) {
+ File emptyForm = getEmptyForm(testInfo, PERFECT_MONEY_ID);
+ verifyEmptyForm(emptyForm,
+ PERFECT_MONEY_ID,
+ PROPERTY_NAME_ACCOUNT_NR);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, PERFECT_MONEY_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Perfect Money Acct");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NR, "PM 0000 1111");
+ String jsonString = getCompletedFormAsJsonString();
+ PerfectMoneyAccount paymentAccount = (PerfectMoneyAccount) createPaymentAccount(alicedaemon, jsonString);
+ verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
+ verifyAccountSingleTradeCurrency("USD", paymentAccount);
+ verifyCommonFormEntries(paymentAccount);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), paymentAccount.getAccountNr());
+ if (log.isDebugEnabled())
+ log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
+ }
+
+ @Test
+ public void testCreatePopmoneyAccount(TestInfo testInfo) {
+ File emptyForm = getEmptyForm(testInfo, POPMONEY_ID);
+ verifyEmptyForm(emptyForm,
+ POPMONEY_ID,
+ PROPERTY_NAME_ACCOUNT_ID,
+ PROPERTY_NAME_HOLDER_NAME);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, POPMONEY_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Pop Money Acct");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_ID, "POPMONEY 0000 1111");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "Jane Doe");
+ String jsonString = getCompletedFormAsJsonString();
+ PopmoneyAccount paymentAccount = (PopmoneyAccount) createPaymentAccount(alicedaemon, jsonString);
+ verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
+ verifyAccountSingleTradeCurrency("USD", paymentAccount);
+ verifyCommonFormEntries(paymentAccount);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_ID), paymentAccount.getAccountId());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
+ if (log.isDebugEnabled())
+ log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
+ }
+
+ @Test
+ public void testCreatePromptPayAccount(TestInfo testInfo) {
+ File emptyForm = getEmptyForm(testInfo, PROMPT_PAY_ID);
+ verifyEmptyForm(emptyForm,
+ PROMPT_PAY_ID,
+ PROPERTY_NAME_PROMPT_PAY_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, PROMPT_PAY_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Prompt Pay Acct");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_PROMPT_PAY_ID, "PP 0000 1111");
+ String jsonString = getCompletedFormAsJsonString();
+ PromptPayAccount paymentAccount = (PromptPayAccount) createPaymentAccount(alicedaemon, jsonString);
+ verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
+ verifyAccountSingleTradeCurrency("THB", paymentAccount);
+ verifyCommonFormEntries(paymentAccount);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_PROMPT_PAY_ID), paymentAccount.getPromptPayId());
+ if (log.isDebugEnabled())
+ log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
+ }
+
+ @Test
+ public void testCreateRevolutAccount(TestInfo testInfo) {
+ File emptyForm = getEmptyForm(testInfo, REVOLUT_ID);
+ verifyEmptyForm(emptyForm,
+ REVOLUT_ID,
+ PROPERTY_NAME_USERNAME);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, REVOLUT_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Revolut Acct");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_USERNAME, "revolut123");
+ String jsonString = getCompletedFormAsJsonString();
+ RevolutAccount paymentAccount = (RevolutAccount) createPaymentAccount(alicedaemon, jsonString);
+ verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
+ verifyAccountTradeCurrencies(getAllRevolutCurrencies(), paymentAccount);
+ verifyCommonFormEntries(paymentAccount);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_USERNAME), paymentAccount.getUserName());
+ if (log.isDebugEnabled())
+ log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
+ }
+
+ @Test
+ public void testCreateSameBankAccount(TestInfo testInfo) {
+ File emptyForm = getEmptyForm(testInfo, SAME_BANK_ID);
+ verifyEmptyForm(emptyForm,
+ SAME_BANK_ID,
+ PROPERTY_NAME_ACCOUNT_NR,
+ PROPERTY_NAME_ACCOUNT_TYPE,
+ PROPERTY_NAME_BANK_NAME,
+ PROPERTY_NAME_BRANCH_ID,
+ PROPERTY_NAME_COUNTRY,
+ PROPERTY_NAME_HOLDER_NAME,
+ PROPERTY_NAME_HOLDER_TAX_ID,
+ PROPERTY_NAME_NATIONAL_ACCOUNT_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, SAME_BANK_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Same Bank Acct");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NR, "000 1 4567");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_TYPE, "Checking");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_NAME, "HSBC");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_BRANCH_ID, "111");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_COUNTRY, "GB");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "Jane Doe");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_TAX_ID, "123456789");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_NATIONAL_ACCOUNT_ID, "123456789");
+ String jsonString = getCompletedFormAsJsonString();
+ SameBankAccount paymentAccount = (SameBankAccount) createPaymentAccount(alicedaemon, jsonString);
+ verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
+ verifyAccountSingleTradeCurrency("GBP", paymentAccount);
+ verifyCommonFormEntries(paymentAccount);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
+ Objects.requireNonNull(paymentAccount.getCountry()).code);
+ SameBankAccountPayload payload = (SameBankAccountPayload) paymentAccount.getPaymentAccountPayload();
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), payload.getAccountNr());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_TYPE), payload.getAccountType());
+ // The bankId == bankName because bank id is not required in the UK.
+ assertEquals(payload.getBankId(), payload.getBankName());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_NAME), payload.getBankName());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BRANCH_ID), payload.getBranchId());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), payload.getHolderName());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_TAX_ID), payload.getHolderTaxId());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_NATIONAL_ACCOUNT_ID), payload.getNationalAccountId());
+ if (log.isDebugEnabled())
+ log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
+ }
+
+ @Test
+ public void testCreateSepaInstantAccount(TestInfo testInfo) {
+ File emptyForm = getEmptyForm(testInfo, SEPA_INSTANT_ID);
+ verifyEmptyForm(emptyForm,
+ SEPA_INSTANT_ID,
+ PROPERTY_NAME_COUNTRY,
+ PROPERTY_NAME_HOLDER_NAME,
+ PROPERTY_NAME_IBAN,
+ PROPERTY_NAME_BIC);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, SEPA_INSTANT_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Conta Sepa Instant");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_COUNTRY, "PT");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "Jose da Silva");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_IBAN, "909-909");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_BIC, "909");
+ String jsonString = getCompletedFormAsJsonString();
+ SepaInstantAccount paymentAccount = (SepaInstantAccount) createPaymentAccount(alicedaemon, jsonString);
+ verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
+ Objects.requireNonNull(paymentAccount.getCountry()).code);
+ verifyAccountSingleTradeCurrency("EUR", paymentAccount);
+ verifyCommonFormEntries(paymentAccount);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_IBAN), paymentAccount.getIban());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BIC), paymentAccount.getBic());
+ // bankId == bic
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BIC), paymentAccount.getBankId());
+ if (log.isDebugEnabled())
+ log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
+ }
+
+ @Test
+ public void testCreateSepaAccount(TestInfo testInfo) {
+ File emptyForm = getEmptyForm(testInfo, SEPA_ID);
+ verifyEmptyForm(emptyForm,
+ SEPA_ID,
+ PROPERTY_NAME_COUNTRY,
+ PROPERTY_NAME_HOLDER_NAME,
+ PROPERTY_NAME_IBAN,
+ PROPERTY_NAME_BIC);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, SEPA_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Conta Sepa");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_COUNTRY, "PT");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "Jose da Silva");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_IBAN, "909-909");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_BIC, "909");
+ String jsonString = getCompletedFormAsJsonString();
+ SepaAccount paymentAccount = (SepaAccount) createPaymentAccount(alicedaemon, jsonString);
+ verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
+ Objects.requireNonNull(paymentAccount.getCountry()).code);
+ verifyAccountSingleTradeCurrency("EUR", paymentAccount);
+ verifyCommonFormEntries(paymentAccount);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_IBAN), paymentAccount.getIban());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BIC), paymentAccount.getBic());
+ // bankId == bic
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BIC), paymentAccount.getBankId());
+ if (log.isDebugEnabled())
+ log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
+ }
+
+ @Test
+ public void testCreateSpecificBanksAccount(TestInfo testInfo) {
+ // TODO Supporting set of accepted banks may require some refactoring
+ // of the SpecificBanksAccount and SpecificBanksAccountPayload classes, i.e.,
+ // public void setAcceptedBanks(String... bankNames) { ... }
+ File emptyForm = getEmptyForm(testInfo, SPECIFIC_BANKS_ID);
+ verifyEmptyForm(emptyForm,
+ SPECIFIC_BANKS_ID,
+ PROPERTY_NAME_ACCOUNT_NR,
+ PROPERTY_NAME_ACCOUNT_TYPE,
+ PROPERTY_NAME_BANK_NAME,
+ PROPERTY_NAME_BRANCH_ID,
+ PROPERTY_NAME_COUNTRY,
+ PROPERTY_NAME_HOLDER_NAME,
+ PROPERTY_NAME_HOLDER_TAX_ID,
+ PROPERTY_NAME_NATIONAL_ACCOUNT_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, SPECIFIC_BANKS_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Specific Banks Acct");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NR, "000 1 4567");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_TYPE, "Checking");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_NAME, "HSBC");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_BRANCH_ID, "111");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_COUNTRY, "GB");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "Jane Doe");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_TAX_ID, "123456789");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_NATIONAL_ACCOUNT_ID, "123456789");
+ String jsonString = getCompletedFormAsJsonString();
+ SpecificBanksAccount paymentAccount = (SpecificBanksAccount) createPaymentAccount(alicedaemon, jsonString);
+ verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
+ verifyAccountSingleTradeCurrency("GBP", paymentAccount);
+ verifyCommonFormEntries(paymentAccount);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
+ Objects.requireNonNull(paymentAccount.getCountry()).code);
+ SpecificBanksAccountPayload payload = (SpecificBanksAccountPayload) paymentAccount.getPaymentAccountPayload();
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), payload.getAccountNr());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_TYPE), payload.getAccountType());
+ // The bankId == bankName because bank id is not required in the UK.
+ assertEquals(payload.getBankId(), payload.getBankName());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_NAME), payload.getBankName());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BRANCH_ID), payload.getBranchId());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), payload.getHolderName());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_TAX_ID), payload.getHolderTaxId());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_NATIONAL_ACCOUNT_ID), payload.getNationalAccountId());
+ if (log.isDebugEnabled())
+ log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
+ }
+
+ @Test
+ public void testCreateSwishAccount(TestInfo testInfo) {
+ File emptyForm = getEmptyForm(testInfo, SWISH_ID);
+ verifyEmptyForm(emptyForm,
+ SWISH_ID,
+ PROPERTY_NAME_MOBILE_NR,
+ PROPERTY_NAME_HOLDER_NAME);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, SWISH_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Swish Account");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_MOBILE_NR, "+46 7 6060 0101");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "Swish Account Holder");
+ String jsonString = getCompletedFormAsJsonString();
+ SwishAccount paymentAccount = (SwishAccount) createPaymentAccount(alicedaemon, jsonString);
+ verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
+ verifyAccountSingleTradeCurrency("SEK", paymentAccount);
+ verifyCommonFormEntries(paymentAccount);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_MOBILE_NR), paymentAccount.getMobileNr());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
+ if (log.isDebugEnabled())
+ log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
+ }
+
+ @Test
+ public void testCreateTransferwiseAccount(TestInfo testInfo) {
+ File emptyForm = getEmptyForm(testInfo, TRANSFERWISE_ID);
+ verifyEmptyForm(emptyForm,
+ TRANSFERWISE_ID,
+ PROPERTY_NAME_EMAIL);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, TRANSFERWISE_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Transferwise Acct");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "jan@doe.info");
+ String jsonString = getCompletedFormAsJsonString();
+ TransferwiseAccount paymentAccount = (TransferwiseAccount) createPaymentAccount(alicedaemon, jsonString);
+ verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
+ verifyAccountTradeCurrencies(getAllTransferwiseCurrencies(), paymentAccount);
+ verifyCommonFormEntries(paymentAccount);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
+ if (log.isDebugEnabled())
+ log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
+ }
+
+ @Test
+ public void testCreateUpholdAccount(TestInfo testInfo) {
+ File emptyForm = getEmptyForm(testInfo, UPHOLD_ID);
+ verifyEmptyForm(emptyForm,
+ UPHOLD_ID,
+ PROPERTY_NAME_ACCOUNT_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, UPHOLD_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Uphold Acct");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_ID, "UA 9876");
+ String jsonString = getCompletedFormAsJsonString();
+ UpholdAccount paymentAccount = (UpholdAccount) createPaymentAccount(alicedaemon, jsonString);
+ verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
+ verifyAccountTradeCurrencies(getAllUpholdCurrencies(), paymentAccount);
+ verifyCommonFormEntries(paymentAccount);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_ID), paymentAccount.getAccountId());
+ if (log.isDebugEnabled())
+ log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
+ }
+
+ @Test
+ public void testCreateUSPostalMoneyOrderAccount(TestInfo testInfo) {
+ File emptyForm = getEmptyForm(testInfo, US_POSTAL_MONEY_ORDER_ID);
+ verifyEmptyForm(emptyForm,
+ US_POSTAL_MONEY_ORDER_ID,
+ PROPERTY_NAME_HOLDER_NAME,
+ PROPERTY_NAME_POSTAL_ADDRESS);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, US_POSTAL_MONEY_ORDER_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Bubba's Acct");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "Bubba");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_POSTAL_ADDRESS, "000 Westwood Terrace Austin, TX 78700");
+ String jsonString = getCompletedFormAsJsonString();
+ USPostalMoneyOrderAccount paymentAccount = (USPostalMoneyOrderAccount) createPaymentAccount(alicedaemon, jsonString);
+ verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
+ verifyAccountSingleTradeCurrency("USD", paymentAccount);
+ verifyCommonFormEntries(paymentAccount);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_POSTAL_ADDRESS), paymentAccount.getPostalAddress());
+ if (log.isDebugEnabled())
+ log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
+ }
+
+ @Test
+ public void testCreateWeChatPayAccount(TestInfo testInfo) {
+ File emptyForm = getEmptyForm(testInfo, WECHAT_PAY_ID);
+ verifyEmptyForm(emptyForm,
+ WECHAT_PAY_ID,
+ PROPERTY_NAME_ACCOUNT_NR);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, WECHAT_PAY_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "WeChat Pay Acct");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NR, "WC 1234");
+ String jsonString = getCompletedFormAsJsonString();
+ WeChatPayAccount paymentAccount = (WeChatPayAccount) createPaymentAccount(alicedaemon, jsonString);
+ verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
+ verifyAccountSingleTradeCurrency("CNY", paymentAccount);
+ verifyCommonFormEntries(paymentAccount);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), paymentAccount.getAccountNr());
+ if (log.isDebugEnabled())
+ log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
+ }
+
+ @Test
+ public void testCreateWesternUnionAccount(TestInfo testInfo) {
+ File emptyForm = getEmptyForm(testInfo, WESTERN_UNION_ID);
+ verifyEmptyForm(emptyForm,
+ WESTERN_UNION_ID,
+ PROPERTY_NAME_HOLDER_NAME,
+ PROPERTY_NAME_CITY,
+ PROPERTY_NAME_STATE,
+ PROPERTY_NAME_COUNTRY,
+ PROPERTY_NAME_EMAIL);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, WESTERN_UNION_ID);
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Western Union Acct");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "Jane Doe");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_CITY, "Fargo");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_STATE, "North Dakota");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_COUNTRY, "US");
+ COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "jane@doe.info");
+ String jsonString = getCompletedFormAsJsonString();
+ WesternUnionAccount paymentAccount = (WesternUnionAccount) createPaymentAccount(alicedaemon, jsonString);
+ verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
+ verifyAccountSingleTradeCurrency("USD", paymentAccount);
+ verifyCommonFormEntries(paymentAccount);
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getFullName());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_CITY), paymentAccount.getCity());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_STATE), paymentAccount.getState());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
+ assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
+ Objects.requireNonNull(paymentAccount.getCountry()).code);
+ if (log.isDebugEnabled())
+ log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
+ }
+
+ @AfterAll
+ public static void tearDown() {
+ tearDownScaffold();
+ }
+}
diff --git a/apitest/src/test/java/bisq/apitest/method/payment/GetPaymentMethodsTest.java b/apitest/src/test/java/bisq/apitest/method/payment/GetPaymentMethodsTest.java
new file mode 100644
index 00000000000..52ff85b7f49
--- /dev/null
+++ b/apitest/src/test/java/bisq/apitest/method/payment/GetPaymentMethodsTest.java
@@ -0,0 +1,53 @@
+package bisq.apitest.method.payment;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+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.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+
+import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
+import static bisq.apitest.config.BisqAppConfig.alicedaemon;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+
+
+import bisq.apitest.method.MethodTest;
+
+@Disabled
+@Slf4j
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+public class GetPaymentMethodsTest extends MethodTest {
+
+ @BeforeAll
+ public static void setUp() {
+ try {
+ setUpScaffold(bitcoind, alicedaemon);
+ } catch (Exception ex) {
+ fail(ex);
+ }
+ }
+
+ @Test
+ @Order(1)
+ public void testGetPaymentMethods() {
+ List paymentMethodIds = getPaymentMethods(alicedaemon)
+ .stream()
+ .map(p -> p.getId())
+ .collect(Collectors.toList());
+ assertEquals(29, paymentMethodIds.size());
+ }
+
+ @AfterAll
+ public static void tearDown() {
+ tearDownScaffold();
+ }
+}
diff --git a/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java b/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java
index e8537206dbf..2b88e4f2700 100644
--- a/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java
+++ b/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java
@@ -27,13 +27,25 @@ public static void initStaticFixtures() {
EXPECTED_PROTOCOL_STATUS.init();
}
- protected final TradeInfo takeAlicesOffer(String offerId, String paymentAccountId) {
- return bobStubs.tradesService.takeOffer(createTakeOfferRequest(offerId, paymentAccountId)).getTrade();
+ protected final TradeInfo takeAlicesOffer(String offerId,
+ String paymentAccountId,
+ String takerFeeCurrencyCode) {
+ return bobStubs.tradesService.takeOffer(
+ createTakeOfferRequest(offerId,
+ paymentAccountId,
+ takerFeeCurrencyCode))
+ .getTrade();
}
@SuppressWarnings("unused")
- protected final TradeInfo takeBobsOffer(String offerId, String paymentAccountId) {
- return aliceStubs.tradesService.takeOffer(createTakeOfferRequest(offerId, paymentAccountId)).getTrade();
+ protected final TradeInfo takeBobsOffer(String offerId,
+ String paymentAccountId,
+ String takerFeeCurrencyCode) {
+ return aliceStubs.tradesService.takeOffer(
+ createTakeOfferRequest(offerId,
+ paymentAccountId,
+ takerFeeCurrencyCode))
+ .getTrade();
}
protected final void verifyExpectedProtocolStatus(TradeInfo trade) {
diff --git a/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java b/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java
index 3561787c454..ffbf75ffee6 100644
--- a/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java
+++ b/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java
@@ -17,6 +17,8 @@
package bisq.apitest.method.trade;
+import bisq.proto.grpc.BtcBalanceInfo;
+
import io.grpc.StatusRuntimeException;
import lombok.extern.slf4j.Slf4j;
@@ -37,6 +39,7 @@
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
import static bisq.core.trade.Trade.State.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
import static protobuf.Offer.State.OFFER_FEE_PAID;
@@ -49,6 +52,9 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
// Alice is buyer, Bob is seller.
+ // Maker and Taker fees are in BSQ.
+ private static final String TRADE_FEE_CURRENCY_CODE = "bsq";
+
@Test
@Order(1)
public void testTakeAlicesBuyOffer(final TestInfo testInfo) {
@@ -56,17 +62,20 @@ public void testTakeAlicesBuyOffer(final TestInfo testInfo) {
var alicesOffer = createAliceOffer(alicesDummyAcct,
"buy",
"usd",
- 12500000);
+ 12500000,
+ TRADE_FEE_CURRENCY_CODE);
var offerId = alicesOffer.getId();
+ assertFalse(alicesOffer.getIsCurrencyForMakerFeeBtc());
// Wait for Alice's AddToOfferBook task.
// Wait times vary; my logs show >= 2 second delay.
- sleep(3000);
+ sleep(3000); // TODO loop instead of hard code wait time
assertEquals(1, getOpenOffersCount(aliceStubs, "buy", "usd"));
- var trade = takeAlicesOffer(offerId, bobsDummyAcct.getId());
+ var trade = takeAlicesOffer(offerId, bobsDummyAcct.getId(), TRADE_FEE_CURRENCY_CODE);
assertNotNull(trade);
assertEquals(offerId, trade.getTradeId());
+ assertFalse(trade.getIsCurrencyForTakerFeeBtc());
// Cache the trade id for the other tests.
tradeId = trade.getTradeId();
@@ -147,8 +156,9 @@ public void testAlicesKeepFunds(final TestInfo testInfo) {
.setPhase(PAYOUT_PUBLISHED);
verifyExpectedProtocolStatus(trade);
logTrade(log, testInfo, "Alice's view after keeping funds", trade);
+ BtcBalanceInfo currentBalance = getBtcBalances(bobdaemon);
log.info("{} Alice's current available balance: {} BTC",
testName(testInfo),
- formatSatoshis(getBalance(alicedaemon)));
+ formatSatoshis(currentBalance.getAvailableBalance()));
}
}
diff --git a/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBTCOfferTest.java b/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBTCOfferTest.java
index 35e13c002fa..2278ce315cd 100644
--- a/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBTCOfferTest.java
+++ b/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBTCOfferTest.java
@@ -17,6 +17,8 @@
package bisq.apitest.method.trade;
+import bisq.proto.grpc.BtcBalanceInfo;
+
import io.grpc.StatusRuntimeException;
import lombok.extern.slf4j.Slf4j;
@@ -35,6 +37,7 @@
import static bisq.core.trade.Trade.State.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static protobuf.Offer.State.OFFER_FEE_PAID;
import static protobuf.OpenOffer.State.AVAILABLE;
@@ -46,6 +49,9 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
// Alice is seller, Bob is buyer.
+ // Maker and Taker fees are in BTC.
+ private static final String TRADE_FEE_CURRENCY_CODE = "btc";
+
@Test
@Order(1)
public void testTakeAlicesSellOffer(final TestInfo testInfo) {
@@ -53,18 +59,21 @@ public void testTakeAlicesSellOffer(final TestInfo testInfo) {
var alicesOffer = createAliceOffer(alicesDummyAcct,
"sell",
"usd",
- 12500000);
+ 12500000,
+ TRADE_FEE_CURRENCY_CODE);
var offerId = alicesOffer.getId();
+ assertTrue(alicesOffer.getIsCurrencyForMakerFeeBtc());
// Wait for Alice's AddToOfferBook task.
// Wait times vary; my logs show >= 2 second delay, but taking sell offers
// seems to require more time to prepare.
- sleep(3000);
+ sleep(3000); // TODO loop instead of hard code wait time
assertEquals(1, getOpenOffersCount(bobStubs, "sell", "usd"));
- var trade = takeAlicesOffer(offerId, bobsDummyAcct.getId());
+ var trade = takeAlicesOffer(offerId, bobsDummyAcct.getId(), TRADE_FEE_CURRENCY_CODE);
assertNotNull(trade);
assertEquals(offerId, trade.getTradeId());
+ assertTrue(trade.getIsCurrencyForTakerFeeBtc());
// Cache the trade id for the other tests.
tradeId = trade.getTradeId();
@@ -148,8 +157,9 @@ public void testBobsBtcWithdrawalToExternalAddress(final TestInfo testInfo) {
.setWithdrawn(true);
verifyExpectedProtocolStatus(trade);
logTrade(log, testInfo, "Bob's view after withdrawing funds to external wallet", trade);
+ BtcBalanceInfo currentBalance = getBtcBalances(bobdaemon);
log.info("{} Bob's current available balance: {} BTC",
testName(testInfo),
- formatSatoshis(getBalance(bobdaemon)));
+ formatSatoshis(currentBalance.getAvailableBalance()));
}
}
diff --git a/apitest/src/test/java/bisq/apitest/method/wallet/BsqWalletTest.java b/apitest/src/test/java/bisq/apitest/method/wallet/BsqWalletTest.java
new file mode 100644
index 00000000000..d074793e5d4
--- /dev/null
+++ b/apitest/src/test/java/bisq/apitest/method/wallet/BsqWalletTest.java
@@ -0,0 +1,244 @@
+package bisq.apitest.method.wallet;
+
+import bisq.proto.grpc.BsqBalanceInfo;
+
+import org.bitcoinj.core.LegacyAddress;
+import org.bitcoinj.core.NetworkParameters;
+
+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.TestInfo;
+import org.junit.jupiter.api.TestMethodOrder;
+
+import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
+import static bisq.apitest.config.BisqAppConfig.alicedaemon;
+import static bisq.apitest.config.BisqAppConfig.arbdaemon;
+import static bisq.apitest.config.BisqAppConfig.bobdaemon;
+import static bisq.apitest.config.BisqAppConfig.seednode;
+import static bisq.cli.TableFormat.formatBsqBalanceInfoTbl;
+import static org.bitcoinj.core.NetworkParameters.PAYMENT_PROTOCOL_ID_MAINNET;
+import static org.bitcoinj.core.NetworkParameters.PAYMENT_PROTOCOL_ID_REGTEST;
+import static org.bitcoinj.core.NetworkParameters.PAYMENT_PROTOCOL_ID_TESTNET;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
+
+
+
+import bisq.apitest.config.BisqAppConfig;
+import bisq.apitest.method.MethodTest;
+
+@Disabled
+@Slf4j
+@TestMethodOrder(OrderAnnotation.class)
+public class BsqWalletTest extends MethodTest {
+
+ // Alice's regtest BSQ wallet is initialized with 1,000,000 BSQ.
+ private static final bisq.core.api.model.BsqBalanceInfo ALICES_INITIAL_BSQ_BALANCES =
+ expectedBsqBalanceModel(100000000,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0);
+
+ // Bob's regtest BSQ wallet is initialized with 1,500,000 BSQ.
+ private static final bisq.core.api.model.BsqBalanceInfo BOBS_INITIAL_BSQ_BALANCES =
+ expectedBsqBalanceModel(150000000,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0);
+
+ private static final double SEND_BSQ_AMOUNT = 25000.50;
+
+ @BeforeAll
+ public static void setUp() {
+ startSupportingApps(false,
+ true,
+ bitcoind,
+ seednode,
+ arbdaemon,
+ alicedaemon,
+ bobdaemon);
+ }
+
+ @Test
+ @Order(1)
+ public void testGetUnusedBsqAddress() {
+ var request = createGetUnusedBsqAddressRequest();
+
+ String address = grpcStubs(alicedaemon).walletsService.getUnusedBsqAddress(request).getAddress();
+ assertFalse(address.isEmpty());
+ assertTrue(address.startsWith("B"));
+
+ NetworkParameters networkParameters = LegacyAddress.getParametersFromAddress(address.substring(1));
+ String addressNetwork = networkParameters.getPaymentProtocolId();
+ assertNotEquals(PAYMENT_PROTOCOL_ID_MAINNET, addressNetwork);
+ // TODO Fix bug causing the regtest bsq address network to be evaluated as 'testnet' here.
+ assertTrue(addressNetwork.equals(PAYMENT_PROTOCOL_ID_TESTNET)
+ || addressNetwork.equals(PAYMENT_PROTOCOL_ID_REGTEST));
+ }
+
+ @Test
+ @Order(2)
+ public void testInitialBsqBalances(final TestInfo testInfo) {
+ BsqBalanceInfo alicesBsqBalances = getBsqBalances(alicedaemon);
+ log.info("{} -> Alice's BSQ Initial Balances -> \n{}",
+ testName(testInfo),
+ formatBsqBalanceInfoTbl(alicesBsqBalances));
+ verifyBsqBalances(ALICES_INITIAL_BSQ_BALANCES, alicesBsqBalances);
+
+ BsqBalanceInfo bobsBsqBalances = getBsqBalances(bobdaemon);
+ log.info("{} -> Bob's BSQ Initial Balances -> \n{}",
+ testName(testInfo),
+ formatBsqBalanceInfoTbl(bobsBsqBalances));
+ verifyBsqBalances(BOBS_INITIAL_BSQ_BALANCES, bobsBsqBalances);
+ }
+
+ @Test
+ @Order(3)
+ public void testSendBsqAndCheckBalancesBeforeGeneratingBtcBlock(final TestInfo testInfo) {
+ String bobsBsqAddress = getUnusedBsqAddress(bobdaemon);
+ sendBsq(alicedaemon, bobsBsqAddress, SEND_BSQ_AMOUNT);
+ sleep(2000);
+
+ BsqBalanceInfo alicesBsqBalances = getBsqBalances(alicedaemon);
+ BsqBalanceInfo bobsBsqBalances = waitForNonZeroUnverifiedBalance(bobdaemon);
+
+ log.info("BSQ Balances Before BTC Block Gen...");
+ printBobAndAliceBsqBalances(testInfo,
+ bobsBsqBalances,
+ alicesBsqBalances,
+ alicedaemon);
+
+ verifyBsqBalances(expectedBsqBalanceModel(150000000,
+ 2500050,
+ 0,
+ 0,
+ 0,
+ 0),
+ bobsBsqBalances);
+
+ verifyBsqBalances(expectedBsqBalanceModel(97499950,
+ 97499950,
+ 97499950,
+ 0,
+ 0,
+ 0),
+ alicesBsqBalances);
+ }
+
+ @Test
+ @Order(4)
+ public void testBalancesAfterSendingBsqAndGeneratingBtcBlock(final TestInfo testInfo) {
+ // There is a wallet persist delay; we have to
+ // wait for both wallets to be saved to disk.
+ genBtcBlocksThenWait(1, 4000);
+
+ BsqBalanceInfo alicesBsqBalances = getBsqBalances(alicedaemon);
+ BsqBalanceInfo bobsBsqBalances = waitForNewAvailableConfirmedBalance(bobdaemon, 150000000);
+
+ log.info("See Available Confirmed BSQ Balances...");
+ printBobAndAliceBsqBalances(testInfo,
+ bobsBsqBalances,
+ alicesBsqBalances,
+ alicedaemon);
+
+ verifyBsqBalances(expectedBsqBalanceModel(152500050,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0),
+ bobsBsqBalances);
+
+ verifyBsqBalances(expectedBsqBalanceModel(97499950,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0),
+ alicesBsqBalances);
+ }
+
+ @AfterAll
+ public static void tearDown() {
+ tearDownScaffold();
+ }
+
+ private void verifyBsqBalances(bisq.core.api.model.BsqBalanceInfo expected,
+ BsqBalanceInfo actual) {
+ assertEquals(expected.getAvailableConfirmedBalance(), actual.getAvailableConfirmedBalance());
+ assertEquals(expected.getUnverifiedBalance(), actual.getUnverifiedBalance());
+ assertEquals(expected.getUnconfirmedChangeBalance(), actual.getUnconfirmedChangeBalance());
+ assertEquals(expected.getLockedForVotingBalance(), actual.getLockedForVotingBalance());
+ assertEquals(expected.getLockupBondsBalance(), actual.getLockupBondsBalance());
+ assertEquals(expected.getUnlockingBondsBalance(), actual.getUnlockingBondsBalance());
+ }
+
+ private BsqBalanceInfo waitForNonZeroUnverifiedBalance(BisqAppConfig daemon) {
+ // A BSQ recipient needs to wait for her daemon to detect a new tx.
+ // Loop here until her unverifiedBalance != 0, or give up after 15 seconds.
+ // A slow test is preferred over a flaky test.
+ BsqBalanceInfo bsqBalance = getBsqBalances(daemon);
+ for (int numRequests = 1; numRequests <= 15 && bsqBalance.getUnverifiedBalance() == 0; numRequests++) {
+ sleep(1000);
+ bsqBalance = getBsqBalances(daemon);
+ }
+ return bsqBalance;
+ }
+
+ private BsqBalanceInfo waitForNewAvailableConfirmedBalance(BisqAppConfig daemon,
+ long staleBalance) {
+ BsqBalanceInfo bsqBalance = getBsqBalances(daemon);
+ for (int numRequests = 1;
+ numRequests <= 15 && bsqBalance.getAvailableConfirmedBalance() == staleBalance;
+ numRequests++) {
+ sleep(1000);
+ bsqBalance = getBsqBalances(daemon);
+ }
+ return bsqBalance;
+ }
+
+ @SuppressWarnings("SameParameterValue")
+ private void printBobAndAliceBsqBalances(final TestInfo testInfo,
+ BsqBalanceInfo bobsBsqBalances,
+ BsqBalanceInfo alicesBsqBalances,
+ BisqAppConfig senderApp) {
+ log.info("{} -> Bob's BSQ Balances After {} {} BSQ-> \n{}",
+ testName(testInfo),
+ senderApp.equals(bobdaemon) ? "Sending" : "Receiving",
+ SEND_BSQ_AMOUNT,
+ formatBsqBalanceInfoTbl(bobsBsqBalances));
+
+ log.info("{} -> Alice's Balances After {} {} BSQ-> \n{}",
+ testName(testInfo),
+ senderApp.equals(alicedaemon) ? "Sending" : "Receiving",
+ SEND_BSQ_AMOUNT,
+ formatBsqBalanceInfoTbl(alicesBsqBalances));
+ }
+
+ @SuppressWarnings("SameParameterValue")
+ private static bisq.core.api.model.BsqBalanceInfo expectedBsqBalanceModel(long availableConfirmedBalance,
+ long unverifiedBalance,
+ long unconfirmedChangeBalance,
+ long lockedForVotingBalance,
+ long lockupBondsBalance,
+ long unlockingBondsBalance) {
+ return bisq.core.api.model.BsqBalanceInfo.valueOf(availableConfirmedBalance,
+ unverifiedBalance,
+ unconfirmedChangeBalance,
+ lockedForVotingBalance,
+ lockupBondsBalance,
+ unlockingBondsBalance);
+ }
+}
diff --git a/apitest/src/test/java/bisq/apitest/method/wallet/BtcWalletTest.java b/apitest/src/test/java/bisq/apitest/method/wallet/BtcWalletTest.java
new file mode 100644
index 00000000000..daee479b89a
--- /dev/null
+++ b/apitest/src/test/java/bisq/apitest/method/wallet/BtcWalletTest.java
@@ -0,0 +1,107 @@
+package bisq.apitest.method.wallet;
+
+import bisq.proto.grpc.BtcBalanceInfo;
+
+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.TestInfo;
+import org.junit.jupiter.api.TestMethodOrder;
+
+import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
+import static bisq.apitest.config.BisqAppConfig.alicedaemon;
+import static bisq.apitest.config.BisqAppConfig.bobdaemon;
+import static bisq.apitest.config.BisqAppConfig.seednode;
+import static bisq.cli.TableFormat.formatAddressBalanceTbl;
+import static bisq.cli.TableFormat.formatBtcBalanceInfoTbl;
+import static java.util.Collections.singletonList;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
+
+
+
+import bisq.apitest.method.MethodTest;
+
+@Disabled
+@Slf4j
+@TestMethodOrder(OrderAnnotation.class)
+public class BtcWalletTest extends MethodTest {
+
+ // All api tests depend on the DAO / regtest environment, and Bob & Alice's wallets
+ // are initialized with 10 BTC during the scaffolding setup.
+ private static final bisq.core.api.model.BtcBalanceInfo INITIAL_BTC_BALANCES =
+ bisq.core.api.model.BtcBalanceInfo.valueOf(1000000000,
+ 0,
+ 1000000000,
+ 0);
+
+ @BeforeAll
+ public static void setUp() {
+ startSupportingApps(false,
+ true,
+ bitcoind,
+ seednode,
+ alicedaemon,
+ bobdaemon);
+ }
+
+ @Test
+ @Order(1)
+ public void testInitialBtcBalances(final TestInfo testInfo) {
+ // Bob & Alice's regtest Bisq wallets were initialized with 10 BTC.
+
+ BtcBalanceInfo alicesBalances = getBtcBalances(alicedaemon);
+ log.info("{} Alice's BTC Balances:\n{}", testName(testInfo), formatBtcBalanceInfoTbl(alicesBalances));
+
+ BtcBalanceInfo bobsBalances = getBtcBalances(bobdaemon);
+ log.info("{} Bob's BTC Balances:\n{}", testName(testInfo), formatBtcBalanceInfoTbl(bobsBalances));
+
+ assertEquals(INITIAL_BTC_BALANCES.getAvailableBalance(), alicesBalances.getAvailableBalance());
+ assertEquals(INITIAL_BTC_BALANCES.getAvailableBalance(), bobsBalances.getAvailableBalance());
+ }
+
+ @Test
+ @Order(2)
+ public void testFundAlicesBtcWallet(final TestInfo testInfo) {
+ String newAddress = getUnusedBtcAddress(alicedaemon);
+ bitcoinCli.sendToAddress(newAddress, "2.5");
+ genBtcBlocksThenWait(1, 1500);
+
+ BtcBalanceInfo btcBalanceInfo = getBtcBalances(alicedaemon);
+ // New balance is 12.5 BTC
+ assertEquals(1250000000, btcBalanceInfo.getAvailableBalance());
+
+ log.info("{} -> Alice's Funded Address Balance -> \n{}",
+ testName(testInfo),
+ formatAddressBalanceTbl(singletonList(getAddressBalance(alicedaemon, newAddress))));
+
+ // New balance is 12.5 BTC
+ btcBalanceInfo = getBtcBalances(alicedaemon);
+ bisq.core.api.model.BtcBalanceInfo alicesExpectedBalances =
+ bisq.core.api.model.BtcBalanceInfo.valueOf(1250000000,
+ 0,
+ 1250000000,
+ 0);
+ verifyBtcBalances(alicesExpectedBalances, btcBalanceInfo);
+ log.info("{} -> Alice's BTC Balances After Sending 2.5 BTC -> \n{}",
+ testName(testInfo),
+ formatBtcBalanceInfoTbl(btcBalanceInfo));
+ }
+
+ @AfterAll
+ public static void tearDown() {
+ tearDownScaffold();
+ }
+
+ private void verifyBtcBalances(bisq.core.api.model.BtcBalanceInfo expected,
+ BtcBalanceInfo actual) {
+ assertEquals(expected.getAvailableBalance(), actual.getAvailableBalance());
+ assertEquals(expected.getReservedBalance(), actual.getReservedBalance());
+ assertEquals(expected.getTotalAvailableBalance(), actual.getTotalAvailableBalance());
+ assertEquals(expected.getLockedBalance(), actual.getLockedBalance());
+ }
+}
diff --git a/apitest/src/test/java/bisq/apitest/method/WalletProtectionTest.java b/apitest/src/test/java/bisq/apitest/method/wallet/WalletProtectionTest.java
similarity index 89%
rename from apitest/src/test/java/bisq/apitest/method/WalletProtectionTest.java
rename to apitest/src/test/java/bisq/apitest/method/wallet/WalletProtectionTest.java
index 08547e9ebb9..f5dabd90593 100644
--- a/apitest/src/test/java/bisq/apitest/method/WalletProtectionTest.java
+++ b/apitest/src/test/java/bisq/apitest/method/wallet/WalletProtectionTest.java
@@ -1,4 +1,4 @@
-package bisq.apitest.method;
+package bisq.apitest.method.wallet;
import io.grpc.StatusRuntimeException;
@@ -18,6 +18,10 @@
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
+
+
+import bisq.apitest.method.MethodTest;
+
@SuppressWarnings("ResultOfMethodCallIgnored")
@Disabled
@Slf4j
@@ -44,7 +48,7 @@ public void testSetWalletPassword() {
@Test
@Order(2)
public void testGetBalanceOnEncryptedWalletShouldThrowException() {
- Throwable exception = assertThrows(StatusRuntimeException.class, () -> getBalance(alicedaemon));
+ Throwable exception = assertThrows(StatusRuntimeException.class, () -> getBtcBalances(alicedaemon));
assertEquals("UNKNOWN: wallet is locked", exception.getMessage());
}
@@ -53,9 +57,9 @@ public void testGetBalanceOnEncryptedWalletShouldThrowException() {
public void testUnlockWalletFor4Seconds() {
var request = createUnlockWalletRequest("first-password", 4);
grpcStubs(alicedaemon).walletsService.unlockWallet(request);
- getBalance(alicedaemon); // should not throw 'wallet locked' exception
+ getBtcBalances(alicedaemon); // should not throw 'wallet locked' exception
sleep(4500); // let unlock timeout expire
- Throwable exception = assertThrows(StatusRuntimeException.class, () -> getBalance(alicedaemon));
+ Throwable exception = assertThrows(StatusRuntimeException.class, () -> getBtcBalances(alicedaemon));
assertEquals("UNKNOWN: wallet is locked", exception.getMessage());
}
@@ -65,7 +69,7 @@ public void testGetBalanceAfterUnlockTimeExpiryShouldThrowException() {
var request = createUnlockWalletRequest("first-password", 3);
grpcStubs(alicedaemon).walletsService.unlockWallet(request);
sleep(4000); // let unlock timeout expire
- Throwable exception = assertThrows(StatusRuntimeException.class, () -> getBalance(alicedaemon));
+ Throwable exception = assertThrows(StatusRuntimeException.class, () -> getBtcBalances(alicedaemon));
assertEquals("UNKNOWN: wallet is locked", exception.getMessage());
}
@@ -75,7 +79,7 @@ public void testLockWalletBeforeUnlockTimeoutExpiry() {
unlockWallet(alicedaemon, "first-password", 60);
var request = createLockWalletRequest();
grpcStubs(alicedaemon).walletsService.lockWallet(request);
- Throwable exception = assertThrows(StatusRuntimeException.class, () -> getBalance(alicedaemon));
+ Throwable exception = assertThrows(StatusRuntimeException.class, () -> getBtcBalances(alicedaemon));
assertEquals("UNKNOWN: wallet is locked", exception.getMessage());
}
@@ -95,7 +99,7 @@ public void testUnlockWalletTimeoutOverride() {
sleep(500); // override unlock timeout after 0.5s
unlockWallet(alicedaemon, "first-password", 6);
sleep(5000);
- getBalance(alicedaemon); // getbalance 5s after resetting unlock timeout to 6s
+ getBtcBalances(alicedaemon); // getbalance 5s after overriding timeout to 6s
}
@Test
@@ -105,7 +109,7 @@ public void testSetNewWalletPassword() {
"first-password", "second-password");
grpcStubs(alicedaemon).walletsService.setWalletPassword(request);
unlockWallet(alicedaemon, "second-password", 2);
- getBalance(alicedaemon);
+ getBtcBalances(alicedaemon);
sleep(2500); // allow time for wallet save
}
@@ -124,7 +128,7 @@ public void testSetNewWalletPasswordWithIncorrectNewPasswordShouldThrowException
public void testRemoveNewWalletPassword() {
var request = createRemoveWalletPasswordRequest("second-password");
grpcStubs(alicedaemon).walletsService.removeWalletPassword(request);
- getBalance(alicedaemon); // should not throw 'wallet locked' exception
+ getBtcBalances(alicedaemon); // should not throw 'wallet locked' exception
}
@AfterAll
diff --git a/apitest/src/test/java/bisq/apitest/scenario/FundWalletScenarioTest.java b/apitest/src/test/java/bisq/apitest/scenario/FundWalletScenarioTest.java
deleted file mode 100644
index 4b7d40f516c..00000000000
--- a/apitest/src/test/java/bisq/apitest/scenario/FundWalletScenarioTest.java
+++ /dev/null
@@ -1,76 +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.apitest.scenario;
-
-import lombok.extern.slf4j.Slf4j;
-
-import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.MethodOrderer;
-import org.junit.jupiter.api.Order;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.TestMethodOrder;
-
-import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
-import static bisq.apitest.config.BisqAppConfig.alicedaemon;
-import static bisq.apitest.config.BisqAppConfig.seednode;
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.fail;
-
-
-
-import bisq.apitest.method.MethodTest;
-
-@Slf4j
-@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
-public class FundWalletScenarioTest extends MethodTest {
-
- @BeforeAll
- public static void setUp() {
- try {
- setUpScaffold(bitcoind, seednode, alicedaemon);
- bitcoinCli.generateBlocks(1);
- MILLISECONDS.sleep(1500);
- } catch (Exception ex) {
- fail(ex);
- }
- }
-
- @Test
- @Order(1)
- public void testFundWallet() {
- // bisq wallet was initialized with 10 btc
- long balance = getBalance(alicedaemon);
- assertEquals(1000000000, balance);
-
- String unusedAddress = getUnusedBtcAddress(alicedaemon);
- bitcoinCli.sendToAddress(unusedAddress, "2.5");
-
- bitcoinCli.generateBlocks(1);
- sleep(1500);
-
- balance = getBalance(alicedaemon);
- assertEquals(1250000000L, balance); // new balance is 12.5 btc
- }
-
- @AfterAll
- public static void tearDown() {
- tearDownScaffold();
- }
-}
diff --git a/apitest/src/test/java/bisq/apitest/scenario/PaymentAccountTest.java b/apitest/src/test/java/bisq/apitest/scenario/PaymentAccountTest.java
new file mode 100644
index 00000000000..897e8bea3d0
--- /dev/null
+++ b/apitest/src/test/java/bisq/apitest/scenario/PaymentAccountTest.java
@@ -0,0 +1,90 @@
+package bisq.apitest.scenario;
+
+import lombok.extern.slf4j.Slf4j;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import org.junit.jupiter.api.TestMethodOrder;
+
+import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
+import static bisq.apitest.config.BisqAppConfig.alicedaemon;
+import static bisq.apitest.config.BisqAppConfig.seednode;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+
+
+import bisq.apitest.method.payment.AbstractPaymentAccountTest;
+import bisq.apitest.method.payment.CreatePaymentAccountTest;
+import bisq.apitest.method.payment.GetPaymentMethodsTest;
+
+@Slf4j
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+public class PaymentAccountTest extends AbstractPaymentAccountTest {
+
+ // Two dummy (usd +eth) accounts are set up as defaults in regtest / dao mode,
+ // then we add 28 more payment accounts in testCreatePaymentAccount().
+ private static final int EXPECTED_NUM_PAYMENT_ACCOUNTS = 2 + 28;
+
+ @BeforeAll
+ public static void setUp() {
+ try {
+ setUpScaffold(bitcoind, seednode, alicedaemon);
+ } catch (Exception ex) {
+ fail(ex);
+ }
+ }
+
+ @Test
+ @Order(1)
+ public void testGetPaymentMethods() {
+ GetPaymentMethodsTest test = new GetPaymentMethodsTest();
+ test.testGetPaymentMethods();
+ }
+
+ @Test
+ @Order(2)
+ public void testCreatePaymentAccount(TestInfo testInfo) {
+ CreatePaymentAccountTest test = new CreatePaymentAccountTest();
+
+ test.testCreateAdvancedCashAccount(testInfo);
+ test.testCreateAliPayAccount(testInfo);
+ test.testCreateAustraliaPayidAccount(testInfo);
+ test.testCreateCashDepositAccount(testInfo);
+ test.testCreateBrazilNationalBankAccount(testInfo);
+ test.testCreateChaseQuickPayAccount(testInfo);
+ test.testCreateClearXChangeAccount(testInfo);
+ test.testCreateF2FAccount(testInfo);
+ test.testCreateFasterPaymentsAccount(testInfo);
+ test.testCreateHalCashAccount(testInfo);
+ test.testCreateInteracETransferAccount(testInfo);
+ test.testCreateJapanBankAccount(testInfo);
+ test.testCreateMoneyBeamAccount(testInfo);
+ test.testCreateMoneyGramAccount(testInfo);
+ test.testCreatePerfectMoneyAccount(testInfo);
+ test.testCreatePopmoneyAccount(testInfo);
+ test.testCreatePromptPayAccount(testInfo);
+ test.testCreateRevolutAccount(testInfo);
+ test.testCreateSameBankAccount(testInfo);
+ test.testCreateSepaInstantAccount(testInfo);
+ test.testCreateSepaAccount(testInfo);
+ test.testCreateSpecificBanksAccount(testInfo);
+ test.testCreateSwishAccount(testInfo);
+ test.testCreateTransferwiseAccount(testInfo);
+ test.testCreateUpholdAccount(testInfo);
+ test.testCreateUSPostalMoneyOrderAccount(testInfo);
+ test.testCreateWeChatPayAccount(testInfo);
+ test.testCreateWesternUnionAccount(testInfo);
+
+ assertEquals(EXPECTED_NUM_PAYMENT_ACCOUNTS, getPaymentAccounts(alicedaemon).size());
+ }
+
+ @AfterAll
+ public static void tearDown() {
+ tearDownScaffold();
+ }
+}
diff --git a/apitest/src/test/java/bisq/apitest/scenario/StartupTest.java b/apitest/src/test/java/bisq/apitest/scenario/StartupTest.java
index fa81ddff6b9..26a95f3c773 100644
--- a/apitest/src/test/java/bisq/apitest/scenario/StartupTest.java
+++ b/apitest/src/test/java/bisq/apitest/scenario/StartupTest.java
@@ -34,7 +34,6 @@
-import bisq.apitest.method.CreatePaymentAccountTest;
import bisq.apitest.method.GetVersionTest;
import bisq.apitest.method.MethodTest;
import bisq.apitest.method.RegisterDisputeAgentsTest;
@@ -71,13 +70,6 @@ public void testRegisterDisputeAgents() {
test.testRegisterRefundAgent();
}
- @Test
- @Order(3)
- public void testCreatePaymentAccount() {
- CreatePaymentAccountTest test = new CreatePaymentAccountTest();
- test.testCreatePerfectMoneyUSDPaymentAccount();
- }
-
@AfterAll
public static void tearDown() {
tearDownScaffold();
diff --git a/apitest/src/test/java/bisq/apitest/scenario/WalletTest.java b/apitest/src/test/java/bisq/apitest/scenario/WalletTest.java
index ecd38dc2295..0ef678f9bc9 100644
--- a/apitest/src/test/java/bisq/apitest/scenario/WalletTest.java
+++ b/apitest/src/test/java/bisq/apitest/scenario/WalletTest.java
@@ -24,55 +24,59 @@
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
+import static bisq.apitest.config.BisqAppConfig.arbdaemon;
+import static bisq.apitest.config.BisqAppConfig.bobdaemon;
import static bisq.apitest.config.BisqAppConfig.seednode;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.fail;
import bisq.apitest.method.MethodTest;
-import bisq.apitest.method.WalletProtectionTest;
+import bisq.apitest.method.wallet.BsqWalletTest;
+import bisq.apitest.method.wallet.BtcWalletTest;
+import bisq.apitest.method.wallet.WalletProtectionTest;
@Slf4j
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class WalletTest extends MethodTest {
- // All tests depend on the DAO / regtest environment, and Alice's wallet is
- // initialized with 10 BTC during the scaffolding setup.
-
@BeforeAll
public static void setUp() {
- try {
- setUpScaffold(bitcoind, seednode, alicedaemon);
- genBtcBlocksThenWait(1, 1500);
- } catch (Exception ex) {
- fail(ex);
- }
+ startSupportingApps(true,
+ true,
+ bitcoind,
+ seednode,
+ arbdaemon,
+ alicedaemon,
+ bobdaemon);
}
@Test
@Order(1)
- public void testFundWallet() {
- // The regtest Bisq wallet was initialized with 10 BTC.
- long balance = getBalance(alicedaemon);
- assertEquals(1000000000, balance);
+ public void testBtcWalletFunding(final TestInfo testInfo) {
+ BtcWalletTest btcWalletTest = new BtcWalletTest();
- String unusedAddress = getUnusedBtcAddress(alicedaemon);
- bitcoinCli.sendToAddress(unusedAddress, "2.5");
+ btcWalletTest.testInitialBtcBalances(testInfo);
+ btcWalletTest.testFundAlicesBtcWallet(testInfo);
+ }
- bitcoinCli.generateBlocks(1);
- sleep(1500);
+ @Test
+ @Order(2)
+ public void testBsqWalletFunding(final TestInfo testInfo) {
+ BsqWalletTest bsqWalletTest = new BsqWalletTest();
- balance = getBalance(alicedaemon);
- assertEquals(1250000000L, balance); // new balance is 12.5 btc
+ bsqWalletTest.testGetUnusedBsqAddress();
+ bsqWalletTest.testInitialBsqBalances(testInfo);
+ bsqWalletTest.testSendBsqAndCheckBalancesBeforeGeneratingBtcBlock(testInfo);
+ bsqWalletTest.testBalancesAfterSendingBsqAndGeneratingBtcBlock(testInfo);
}
@Test
- @Order(2)
+ @Order(3)
public void testWalletProtection() {
// Batching all wallet tests in this test case reduces scaffold setup
// time. Here, we create a method WalletProtectionTest instance and run each
diff --git a/cli/src/main/java/bisq/cli/CliMain.java b/cli/src/main/java/bisq/cli/CliMain.java
index ec0e5e71bb6..c6a420253cc 100644
--- a/cli/src/main/java/bisq/cli/CliMain.java
+++ b/cli/src/main/java/bisq/cli/CliMain.java
@@ -23,42 +23,54 @@
import bisq.proto.grpc.CreateOfferRequest;
import bisq.proto.grpc.CreatePaymentAccountRequest;
import bisq.proto.grpc.GetAddressBalanceRequest;
-import bisq.proto.grpc.GetBalanceRequest;
+import bisq.proto.grpc.GetBalancesRequest;
import bisq.proto.grpc.GetFundingAddressesRequest;
import bisq.proto.grpc.GetOfferRequest;
import bisq.proto.grpc.GetOffersRequest;
+import bisq.proto.grpc.GetPaymentAccountFormRequest;
import bisq.proto.grpc.GetPaymentAccountsRequest;
+import bisq.proto.grpc.GetPaymentMethodsRequest;
import bisq.proto.grpc.GetTradeRequest;
+import bisq.proto.grpc.GetUnusedBsqAddressRequest;
import bisq.proto.grpc.GetVersionRequest;
import bisq.proto.grpc.KeepFundsRequest;
import bisq.proto.grpc.LockWalletRequest;
+import bisq.proto.grpc.OfferInfo;
import bisq.proto.grpc.RegisterDisputeAgentRequest;
import bisq.proto.grpc.RemoveWalletPasswordRequest;
+import bisq.proto.grpc.SendBsqRequest;
import bisq.proto.grpc.SetWalletPasswordRequest;
import bisq.proto.grpc.TakeOfferRequest;
import bisq.proto.grpc.UnlockWalletRequest;
import bisq.proto.grpc.WithdrawFundsRequest;
+import protobuf.PaymentAccount;
+
import io.grpc.StatusRuntimeException;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import java.io.File;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
+import java.io.PrintWriter;
import java.math.BigDecimal;
+import java.util.Date;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
-import static bisq.cli.CurrencyFormat.formatSatoshis;
import static bisq.cli.CurrencyFormat.toSatoshis;
import static bisq.cli.NegativeNumberOptions.hasNegativeNumberOptions;
-import static bisq.cli.TableFormat.formatAddressBalanceTbl;
-import static bisq.cli.TableFormat.formatOfferTable;
-import static bisq.cli.TableFormat.formatPaymentAcctTbl;
+import static bisq.cli.TableFormat.*;
import static java.lang.String.format;
import static java.lang.System.err;
import static java.lang.System.exit;
@@ -84,12 +96,16 @@ private enum Method {
confirmpaymentreceived,
keepfunds,
withdrawfunds,
+ getpaymentmethods,
+ getpaymentacctform,
createpaymentacct,
getpaymentaccts,
getversion,
getbalance,
getaddressbalance,
getfundingaddresses,
+ getunusedbsqaddress,
+ sendbsq,
lockwallet,
unlockwallet,
removewalletpassword,
@@ -183,10 +199,25 @@ public static void run(String[] args) {
return;
}
case getbalance: {
- var request = GetBalanceRequest.newBuilder().build();
- var reply = walletsService.getBalance(request);
- var btcBalance = formatSatoshis(reply.getBalance());
- out.println(btcBalance);
+ var currencyCode = nonOptionArgs.size() == 2
+ ? nonOptionArgs.get(1)
+ : "";
+ var request = GetBalancesRequest.newBuilder()
+ .setCurrencyCode(currencyCode)
+ .build();
+ var reply = walletsService.getBalances(request);
+ switch (currencyCode.toUpperCase()) {
+ case "BSQ":
+ out.println(formatBsqBalanceInfoTbl(reply.getBalances().getBsq()));
+ break;
+ case "BTC":
+ out.println(formatBtcBalanceInfoTbl(reply.getBalances().getBtc()));
+ break;
+ case "":
+ default:
+ out.println(formatBalancesTbls(reply.getBalances()));
+ break;
+ }
return;
}
case getaddressbalance: {
@@ -205,11 +236,42 @@ public static void run(String[] args) {
out.println(formatAddressBalanceTbl(reply.getAddressBalanceInfoList()));
return;
}
+ case getunusedbsqaddress: {
+ 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");
+
+ double amount;
+ try {
+ amount = Double.parseDouble(nonOptionArgs.get(2));
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException(format("'%s' is not a number", nonOptionArgs.get(2)));
+ }
+
+ var request = SendBsqRequest.newBuilder()
+ .setAddress(address)
+ .setAmount(amount)
+ .build();
+ walletsService.sendBsq(request);
+ out.printf("%.2f BSQ sent to %s%n", amount, address);
+ 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");
+ + " 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);
@@ -223,7 +285,11 @@ public static void run(String[] args) {
marketPriceMargin = new BigDecimal(nonOptionArgs.get(7));
else
fixedPrice = nonOptionArgs.get(7);
+
var securityDeposit = new BigDecimal(nonOptionArgs.get(8));
+ var makerFeeCurrencyCode = nonOptionArgs.size() == 10
+ ? nonOptionArgs.get(9)
+ : "btc";
var request = CreateOfferRequest.newBuilder()
.setDirection(direction)
@@ -235,6 +301,7 @@ public static void run(String[] args) {
.setMarketPriceMargin(marketPriceMargin.doubleValue())
.setBuyerSecurityDeposit(securityDeposit.doubleValue())
.setPaymentAccountId(paymentAcctId)
+ .setMakerFeeCurrencyCode(makerFeeCurrencyCode)
.build();
var reply = offersService.createOffer(request);
out.println(formatOfferTable(singletonList(reply.getOffer()), currencyCode));
@@ -278,18 +345,30 @@ public static void run(String[] args) {
.setCurrencyCode(currencyCode)
.build();
var reply = offersService.getOffers(request);
- out.println(formatOfferTable(reply.getOffersList(), currencyCode));
+
+ List offers = reply.getOffersList();
+ if (offers.isEmpty())
+ out.printf("no %s %s offers found\n", direction, currencyCode);
+ else
+ out.println(formatOfferTable(reply.getOffersList(), currencyCode));
+
return;
}
case takeoffer: {
if (nonOptionArgs.size() < 3)
- throw new IllegalArgumentException("incorrect parameter count, expecting offer id, payment acct id");
+ 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 request = TakeOfferRequest.newBuilder()
.setOfferId(offerId)
.setPaymentAccountId(paymentAccountId)
+ .setTakerFeeCurrencyCode(takerFeeCurrencyCode)
.build();
var reply = tradesService.takeOffer(request);
out.printf("trade '%s' successfully taken", reply.getTrade().getShortId());
@@ -297,7 +376,8 @@ public static void run(String[] args) {
}
case gettrade: {
if (nonOptionArgs.size() < 2)
- throw new IllegalArgumentException("incorrect parameter count, expecting trade id, [,showcontract = true|false]");
+ throw new IllegalArgumentException("incorrect parameter count, "
+ + " expecting trade id [,showcontract = true|false]");
var tradeId = nonOptionArgs.get(1);
var showContract = false;
@@ -352,7 +432,8 @@ public static void run(String[] args) {
}
case withdrawfunds: {
if (nonOptionArgs.size() < 3)
- throw new IllegalArgumentException("incorrect parameter count, expecting trade id, bitcoin wallet address");
+ throw new IllegalArgumentException("incorrect parameter count, "
+ + " expecting trade id, bitcoin wallet address");
var tradeId = nonOptionArgs.get(1);
var address = nonOptionArgs.get(2);
@@ -364,30 +445,67 @@ public static void run(String[] args) {
out.printf("funds from trade '%s' sent to btc address '%s'", tradeId, address);
return;
}
+ case getpaymentmethods: {
+ 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 request = GetPaymentAccountFormRequest.newBuilder()
+ .setPaymentMethodId(paymentMethodId)
+ .build();
+ String jsonString = paymentAccountsService.getPaymentAccountForm(request)
+ .getPaymentAccountFormJson();
+ File jsonFile = saveFileToDisk(paymentMethodId.toLowerCase(),
+ ".json",
+ jsonString);
+ out.printf("Payment account form %s%nsaved to %s%n",
+ jsonString, jsonFile.getAbsolutePath());
+ out.println("Edit the file, and use as the argument to a 'createpaymentacct' command.");
+ return;
+ }
case createpaymentacct: {
- if (nonOptionArgs.size() < 5)
+ if (nonOptionArgs.size() < 2)
throw new IllegalArgumentException(
- "incorrect parameter count, expecting payment method id,"
- + " account name, account number, currency code");
+ "incorrect parameter count, expecting path to payment account form");
- var paymentMethodId = nonOptionArgs.get(1);
- var accountName = nonOptionArgs.get(2);
- var accountNumber = nonOptionArgs.get(3);
- var currencyCode = nonOptionArgs.get(4);
+ 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()));
+
+ String jsonString;
+ try {
+ jsonString = new String(Files.readAllBytes(paymentAccountFormPath));
+ } catch (IOException e) {
+ throw new IllegalStateException(
+ format("could not read %s", paymentAccountFormPath.toString()));
+ }
var request = CreatePaymentAccountRequest.newBuilder()
- .setPaymentMethodId(paymentMethodId)
- .setAccountName(accountName)
- .setAccountNumber(accountNumber)
- .setCurrencyCode(currencyCode).build();
- paymentAccountsService.createPaymentAccount(request);
- out.printf("payment account %s saved", accountName);
+ .setPaymentAccountForm(jsonString)
+ .build();
+ var reply = paymentAccountsService.createPaymentAccount(request);
+ out.println("payment account saved");
+ out.println(formatPaymentAcctTbl(singletonList(reply.getPaymentAccount())));
return;
}
case getpaymentaccts: {
var request = GetPaymentAccountsRequest.newBuilder().build();
var reply = paymentAccountsService.getPaymentAccounts(request);
- out.println(formatPaymentAcctTbl(reply.getPaymentAccountsList()));
+
+ List paymentAccounts = reply.getPaymentAccountsList();
+ if (paymentAccounts.size() > 0)
+ out.println(formatPaymentAcctTbl(paymentAccounts));
+ else
+ out.println("no payment accounts are saved");
+
return;
}
case lockwallet: {
@@ -470,6 +588,26 @@ private static Method getMethodFromCmd(String methodName) {
return Method.valueOf(methodName.toLowerCase());
}
+ private static File saveFileToDisk(String prefix,
+ @SuppressWarnings("SameParameterValue") String suffix,
+ String text) {
+ String timestamp = Long.toUnsignedString(new Date().getTime());
+ String relativeFileName = prefix + "_" + timestamp + suffix;
+ try {
+ Path path = Paths.get(relativeFileName);
+ if (!Files.exists(path)) {
+ try (PrintWriter out = new PrintWriter(path.toString())) {
+ out.println(text);
+ }
+ return path.toAbsolutePath().toFile();
+ } else {
+ throw new IllegalStateException(format("could not overwrite existing file '%s'", relativeFileName));
+ }
+ } catch (FileNotFoundException e) {
+ throw new IllegalStateException(format("could not create file '%s'", relativeFileName));
+ }
+ }
+
private static void printHelp(OptionParser parser, PrintStream stream) {
try {
stream.println("Bisq RPC Client");
@@ -482,23 +620,27 @@ private static void printHelp(OptionParser parser, PrintStream stream) {
stream.format(rowFormat, "Method", "Params", "Description");
stream.format(rowFormat, "------", "------", "------------");
stream.format(rowFormat, "getversion", "", "Get server version");
- stream.format(rowFormat, "getbalance", "", "Get server wallet balance");
+ 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", "Send BSQ");
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 (%), \\", "");
- stream.format(rowFormat, "", "security deposit (%)", "");
+ 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, "getoffers", "buy | sell, currency code", "Get current offers");
- stream.format(rowFormat, "takeoffer", "offer id", "Take offer with id");
- stream.format(rowFormat, "gettrade", "trade id [,showcontract]", "Get trade summary or full contract");
+ 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", "Withdraw received funds to external wallet address");
- stream.format(rowFormat, "createpaymentacct", "account name, account number, currency code", "Create PerfectMoney dummy account");
+ 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",
diff --git a/cli/src/main/java/bisq/cli/ColumnHeaderConstants.java b/cli/src/main/java/bisq/cli/ColumnHeaderConstants.java
index 03500e4f47a..59b6230a2eb 100644
--- a/cli/src/main/java/bisq/cli/ColumnHeaderConstants.java
+++ b/cli/src/main/java/bisq/cli/ColumnHeaderConstants.java
@@ -29,9 +29,18 @@ class ColumnHeaderConstants {
// such as COL_HEADER_CREATION_DATE, COL_HEADER_VOLUME and COL_HEADER_UUID, the
// expected max data string length is accounted for. In others, the column header length
// are expected to be greater than any column value length.
- static final String COL_HEADER_ADDRESS = padEnd("Address", 34, ' ');
+ static final String COL_HEADER_ADDRESS = padEnd("%-3s Address", 52, ' ');
static final String COL_HEADER_AMOUNT = padEnd("BTC(min - max)", 24, ' ');
- static final String COL_HEADER_BALANCE = padStart("Balance", 12, ' ');
+ static final String COL_HEADER_AVAILABLE_BALANCE = "Available Balance";
+ static final String COL_HEADER_AVAILABLE_CONFIRMED_BALANCE = "Available Confirmed Balance";
+ static final String COL_HEADER_UNCONFIRMED_CHANGE_BALANCE = "Unconfirmed Change Balance";
+ static final String COL_HEADER_RESERVED_BALANCE = "Reserved Balance";
+ static final String COL_HEADER_TOTAL_AVAILABLE_BALANCE = "Total Available Balance";
+ static final String COL_HEADER_LOCKED_BALANCE = "Locked Balance";
+ static final String COL_HEADER_LOCKED_FOR_VOTING_BALANCE = "Locked For Voting Balance";
+ static final String COL_HEADER_LOCKUP_BONDS_BALANCE = "Lockup Bonds Balance";
+ static final String COL_HEADER_UNLOCKING_BONDS_BALANCE = "Unlocking Bonds Balance";
+ static final String COL_HEADER_UNVERIFIED_BALANCE = "Unverified Balance";
static final String COL_HEADER_CONFIRMATIONS = "Confirmations";
static final String COL_HEADER_CREATION_DATE = padEnd("Creation Date (UTC)", 20, ' ');
static final String COL_HEADER_CURRENCY = "Currency";
diff --git a/cli/src/main/java/bisq/cli/CurrencyFormat.java b/cli/src/main/java/bisq/cli/CurrencyFormat.java
index e4d8f89c6c7..a4766690eff 100644
--- a/cli/src/main/java/bisq/cli/CurrencyFormat.java
+++ b/cli/src/main/java/bisq/cli/CurrencyFormat.java
@@ -37,12 +37,19 @@ public class CurrencyFormat {
static final BigDecimal SATOSHI_DIVISOR = new BigDecimal(100000000);
static final DecimalFormat BTC_FORMAT = new DecimalFormat("###,##0.00000000");
- @VisibleForTesting
+ static final BigDecimal BSQ_SATOSHI_DIVISOR = new BigDecimal(100);
+ static final DecimalFormat BSQ_FORMAT = new DecimalFormat("###,###,###,##0.00");
+
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
public static String formatSatoshis(long sats) {
return BTC_FORMAT.format(BigDecimal.valueOf(sats).divide(SATOSHI_DIVISOR));
}
+ @SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
+ public static String formatBsq(long sats) {
+ return BSQ_FORMAT.format(BigDecimal.valueOf(sats).divide(BSQ_SATOSHI_DIVISOR));
+ }
+
static String formatAmountRange(long minAmount, long amount) {
return minAmount != amount
? formatSatoshis(minAmount) + " - " + formatSatoshis(amount)
diff --git a/cli/src/main/java/bisq/cli/TableFormat.java b/cli/src/main/java/bisq/cli/TableFormat.java
index 8336fff9ba1..a23b7a022d8 100644
--- a/cli/src/main/java/bisq/cli/TableFormat.java
+++ b/cli/src/main/java/bisq/cli/TableFormat.java
@@ -18,10 +18,15 @@
package bisq.cli;
import bisq.proto.grpc.AddressBalanceInfo;
+import bisq.proto.grpc.BalancesInfo;
+import bisq.proto.grpc.BsqBalanceInfo;
+import bisq.proto.grpc.BtcBalanceInfo;
import bisq.proto.grpc.OfferInfo;
import protobuf.PaymentAccount;
+import com.google.common.annotations.VisibleForTesting;
+
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -30,28 +35,28 @@
import java.util.stream.Collectors;
import static bisq.cli.ColumnHeaderConstants.*;
-import static bisq.cli.CurrencyFormat.formatAmountRange;
-import static bisq.cli.CurrencyFormat.formatOfferPrice;
-import static bisq.cli.CurrencyFormat.formatSatoshis;
-import static bisq.cli.CurrencyFormat.formatVolumeRange;
+import static bisq.cli.CurrencyFormat.*;
import static com.google.common.base.Strings.padEnd;
import static java.lang.String.format;
import static java.util.Collections.max;
import static java.util.Comparator.comparing;
import static java.util.TimeZone.getTimeZone;
-class TableFormat {
+@VisibleForTesting
+public class TableFormat {
static final TimeZone TZ_UTC = getTimeZone("UTC");
static final SimpleDateFormat DATE_FORMAT_ISO_8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
- static String formatAddressBalanceTbl(List addressBalanceInfo) {
- String headerLine = (COL_HEADER_ADDRESS + COL_HEADER_DELIMITER
- + COL_HEADER_BALANCE + COL_HEADER_DELIMITER
- + COL_HEADER_CONFIRMATIONS + COL_HEADER_DELIMITER + "\n");
- String colDataFormat = "%-" + COL_HEADER_ADDRESS.length() + "s" // left justify
- + " %" + COL_HEADER_BALANCE.length() + "s" // right justify
- + " %" + COL_HEADER_CONFIRMATIONS.length() + "d"; // right justify
+ public static String formatAddressBalanceTbl(List addressBalanceInfo) {
+ String headerFormatString = COL_HEADER_ADDRESS + COL_HEADER_DELIMITER
+ + COL_HEADER_AVAILABLE_BALANCE + COL_HEADER_DELIMITER
+ + COL_HEADER_CONFIRMATIONS + COL_HEADER_DELIMITER + "\n";
+ String headerLine = format(headerFormatString, "BTC");
+
+ String colDataFormat = "%-" + COL_HEADER_ADDRESS.length() + "s" // lt justify
+ + " %" + (COL_HEADER_AVAILABLE_BALANCE.length() - 1) + "s" // rt justify
+ + " %" + COL_HEADER_CONFIRMATIONS.length() + "d"; // lt justify
return headerLine
+ addressBalanceInfo.stream()
.map(info -> format(colDataFormat,
@@ -61,15 +66,58 @@ static String formatAddressBalanceTbl(List addressBalanceInf
.collect(Collectors.joining("\n"));
}
- static String formatOfferTable(List offerInfo, String fiatCurrency) {
+ public static String formatBalancesTbls(BalancesInfo balancesInfo) {
+ return "BTC" + "\n"
+ + formatBtcBalanceInfoTbl(balancesInfo.getBtc()) + "\n"
+ + "BSQ" + "\n"
+ + formatBsqBalanceInfoTbl(balancesInfo.getBsq());
+ }
+
+ public static String formatBsqBalanceInfoTbl(BsqBalanceInfo bsqBalanceInfo) {
+ String headerLine = COL_HEADER_AVAILABLE_CONFIRMED_BALANCE + COL_HEADER_DELIMITER
+ + COL_HEADER_UNVERIFIED_BALANCE + COL_HEADER_DELIMITER
+ + COL_HEADER_UNCONFIRMED_CHANGE_BALANCE + COL_HEADER_DELIMITER
+ + COL_HEADER_LOCKED_FOR_VOTING_BALANCE + COL_HEADER_DELIMITER
+ + COL_HEADER_LOCKUP_BONDS_BALANCE + COL_HEADER_DELIMITER
+ + COL_HEADER_UNLOCKING_BONDS_BALANCE + COL_HEADER_DELIMITER + "\n";
+ String colDataFormat = "%" + COL_HEADER_AVAILABLE_CONFIRMED_BALANCE.length() + "s" // rt justify
+ + " %" + (COL_HEADER_UNVERIFIED_BALANCE.length() + 1) + "s" // rt justify
+ + " %" + (COL_HEADER_UNCONFIRMED_CHANGE_BALANCE.length() + 1) + "s" // rt justify
+ + " %" + (COL_HEADER_LOCKED_FOR_VOTING_BALANCE.length() + 1) + "s" // rt justify
+ + " %" + (COL_HEADER_LOCKUP_BONDS_BALANCE.length() + 1) + "s" // rt justify
+ + " %" + (COL_HEADER_UNLOCKING_BONDS_BALANCE.length() + 1) + "s"; // rt justify
+ return headerLine + format(colDataFormat,
+ formatBsq(bsqBalanceInfo.getAvailableConfirmedBalance()),
+ formatBsq(bsqBalanceInfo.getUnverifiedBalance()),
+ formatBsq(bsqBalanceInfo.getUnconfirmedChangeBalance()),
+ formatBsq(bsqBalanceInfo.getLockedForVotingBalance()),
+ formatBsq(bsqBalanceInfo.getLockupBondsBalance()),
+ formatBsq(bsqBalanceInfo.getUnlockingBondsBalance()));
+ }
+
+ public static String formatBtcBalanceInfoTbl(BtcBalanceInfo btcBalanceInfo) {
+ String headerLine = COL_HEADER_AVAILABLE_BALANCE + COL_HEADER_DELIMITER
+ + COL_HEADER_RESERVED_BALANCE + COL_HEADER_DELIMITER
+ + COL_HEADER_TOTAL_AVAILABLE_BALANCE + COL_HEADER_DELIMITER
+ + COL_HEADER_LOCKED_BALANCE + COL_HEADER_DELIMITER + "\n";
+ String colDataFormat = "%" + COL_HEADER_AVAILABLE_BALANCE.length() + "s" // rt justify
+ + " %" + (COL_HEADER_RESERVED_BALANCE.length() + 1) + "s" // rt justify
+ + " %" + (COL_HEADER_TOTAL_AVAILABLE_BALANCE.length() + 1) + "s" // rt justify
+ + " %" + (COL_HEADER_LOCKED_BALANCE.length() + 1) + "s"; // rt justify
+ return headerLine + format(colDataFormat,
+ formatSatoshis(btcBalanceInfo.getAvailableBalance()),
+ formatSatoshis(btcBalanceInfo.getReservedBalance()),
+ formatSatoshis(btcBalanceInfo.getTotalAvailableBalance()),
+ formatSatoshis(btcBalanceInfo.getLockedBalance()));
+ }
+ static String formatOfferTable(List offerInfo, String fiatCurrency) {
// Some column values might be longer than header, so we need to calculate them.
int paymentMethodColWidth = getLengthOfLongestColumn(
COL_HEADER_PAYMENT_METHOD.length(),
offerInfo.stream()
.map(OfferInfo::getPaymentMethodShortName)
.collect(Collectors.toList()));
-
String headersFormat = COL_HEADER_DIRECTION + COL_HEADER_DELIMITER
+ COL_HEADER_PRICE + COL_HEADER_DELIMITER // includes %s -> fiatCurrency
+ COL_HEADER_AMOUNT + COL_HEADER_DELIMITER
diff --git a/common/src/main/java/bisq/common/util/ReflectionUtils.java b/common/src/main/java/bisq/common/util/ReflectionUtils.java
new file mode 100644
index 00000000000..e70162ecb23
--- /dev/null
+++ b/common/src/main/java/bisq/common/util/ReflectionUtils.java
@@ -0,0 +1,108 @@
+/*
+ * 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.common.util;
+
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import static java.util.Arrays.stream;
+import static org.apache.commons.lang3.StringUtils.capitalize;
+
+public class ReflectionUtils {
+
+ /**
+ * Recursively loads a list of fields for a given class and its superclasses,
+ * using a filter predicate to exclude any unwanted fields.
+ *
+ * @param fields The list of fields being loaded for a class hierarchy.
+ * @param clazz The lowest level class in a hierarchy; excluding Object.class.
+ * @param isExcludedField The field exclusion predicate.
+ */
+ public static void loadFieldListForClassHierarchy(List fields,
+ Class> clazz,
+ Predicate isExcludedField) {
+ fields.addAll(stream(clazz.getDeclaredFields())
+ .filter(f -> !isExcludedField.test(f))
+ .collect(Collectors.toList()));
+
+ Class> superclass = clazz.getSuperclass();
+ if (!Objects.equals(superclass, Object.class))
+ loadFieldListForClassHierarchy(fields,
+ superclass,
+ isExcludedField);
+ }
+
+ /**
+ * Returns an Optional of a setter method for a given field and a class hierarchy,
+ * or Optional.empty() if it does not exist.
+ *
+ * @param field The field used to find a setter method.
+ * @param clazz The lowest level class in a hierarchy; excluding Object.class.
+ * @return Optional of the setter method for a field in the class hierarchy,
+ * or Optional.empty() if it does not exist.
+ */
+ public static Optional getSetterMethodForFieldInClassHierarchy(Field field,
+ Class> clazz) {
+ Optional setter = stream(clazz.getDeclaredMethods())
+ .filter((m) -> isSetterForField(m, field))
+ .findFirst();
+
+ if (setter.isPresent())
+ return setter;
+
+ Class> superclass = clazz.getSuperclass();
+ if (!Objects.equals(superclass, Object.class)) {
+ setter = getSetterMethodForFieldInClassHierarchy(field, superclass);
+ if (setter.isPresent())
+ return setter;
+ }
+
+ return Optional.empty();
+ }
+
+ public static boolean isSetterForField(Method m, Field f) {
+ return m.getName().startsWith("set")
+ && m.getName().endsWith(capitalize(f.getName()))
+ && m.getReturnType().getName().equals("void")
+ && m.getParameterCount() == 1
+ && m.getParameterTypes()[0].getName().equals(f.getType().getName());
+ }
+
+ public static boolean isSetterOnClass(Method setter, Class> clazz) {
+ return clazz.equals(setter.getDeclaringClass());
+ }
+
+ public static String getVisibilityModifierAsString(Field field) {
+ if (Modifier.isPrivate(field.getModifiers()))
+ return "private";
+ else if (Modifier.isProtected(field.getModifiers()))
+ return "protected";
+ else if (Modifier.isPublic(field.getModifiers()))
+ return "public";
+ else
+ return "";
+ }
+}
diff --git a/core/src/main/java/bisq/core/api/CoreApi.java b/core/src/main/java/bisq/core/api/CoreApi.java
index 7cfcc5ce152..96e2d3055d2 100644
--- a/core/src/main/java/bisq/core/api/CoreApi.java
+++ b/core/src/main/java/bisq/core/api/CoreApi.java
@@ -18,10 +18,13 @@
package bisq.core.api;
import bisq.core.api.model.AddressBalanceInfo;
+import bisq.core.api.model.BalancesInfo;
+import bisq.core.btc.wallet.TxBroadcaster;
import bisq.core.monetary.Price;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferPayload;
import bisq.core.payment.PaymentAccount;
+import bisq.core.payment.payload.PaymentMethod;
import bisq.core.trade.Trade;
import bisq.core.trade.statistics.TradeStatistics3;
import bisq.core.trade.statistics.TradeStatisticsManager;
@@ -107,6 +110,7 @@ public void createAnPlaceOffer(String currencyCode,
long minAmountAsLong,
double buyerSecurityDeposit,
String paymentAccountId,
+ String makerFeeCurrencyCode,
Consumer resultHandler) {
coreOffersService.createAndPlaceOffer(currencyCode,
directionAsString,
@@ -117,6 +121,7 @@ public void createAnPlaceOffer(String currencyCode,
minAmountAsLong,
buyerSecurityDeposit,
paymentAccountId,
+ makerFeeCurrencyCode,
resultHandler);
}
@@ -150,20 +155,22 @@ public void cancelOffer(String id) {
// PaymentAccounts
///////////////////////////////////////////////////////////////////////////////////////////
- public void createPaymentAccount(String paymentMethodId,
- String accountName,
- String accountNumber,
- String currencyCode) {
- paymentAccountsService.createPaymentAccount(paymentMethodId,
- accountName,
- accountNumber,
- currencyCode);
+ public PaymentAccount createPaymentAccount(String jsonString) {
+ return paymentAccountsService.createPaymentAccount(jsonString);
}
public Set getPaymentAccounts() {
return paymentAccountsService.getPaymentAccounts();
}
+ public List getFiatPaymentMethods() {
+ return paymentAccountsService.getFiatPaymentMethods();
+ }
+
+ public String getPaymentAccountForm(String paymentMethodId) {
+ return paymentAccountsService.getPaymentAccountFormAsString(paymentMethodId);
+ }
+
///////////////////////////////////////////////////////////////////////////////////////////
// Prices
///////////////////////////////////////////////////////////////////////////////////////////
@@ -178,10 +185,12 @@ public double getMarketPrice(String currencyCode) {
public void takeOffer(String offerId,
String paymentAccountId,
+ String takerFeeCurrencyCode,
Consumer resultHandler) {
Offer offer = coreOffersService.getOffer(offerId);
coreTradesService.takeOffer(offer,
paymentAccountId,
+ takerFeeCurrencyCode,
resultHandler);
}
@@ -213,8 +222,8 @@ public String getTradeRole(String tradeId) {
// Wallets
///////////////////////////////////////////////////////////////////////////////////////////
- public long getAvailableBalance() {
- return walletsService.getAvailableBalance();
+ public BalancesInfo getBalances(String currencyCode) {
+ return walletsService.getBalances(currencyCode);
}
public long getAddressBalance(String addressString) {
@@ -229,6 +238,14 @@ public List getFundingAddresses() {
return walletsService.getFundingAddresses();
}
+ public String getUnusedBsqAddress() {
+ return walletsService.getUnusedBsqAddress();
+ }
+
+ public void sendBsq(String address, double amount, TxBroadcaster.Callback callback) {
+ walletsService.sendBsq(address, amount, callback);
+ }
+
public void setWalletPassword(String password, String newPassword) {
walletsService.setWalletPassword(password, newPassword);
}
diff --git a/core/src/main/java/bisq/core/api/CoreOffersService.java b/core/src/main/java/bisq/core/api/CoreOffersService.java
index 6d8641c0579..bca9dc7cbf9 100644
--- a/core/src/main/java/bisq/core/api/CoreOffersService.java
+++ b/core/src/main/java/bisq/core/api/CoreOffersService.java
@@ -22,6 +22,7 @@
import bisq.core.offer.CreateOfferService;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferBookService;
+import bisq.core.offer.OfferUtil;
import bisq.core.offer.OpenOfferManager;
import bisq.core.payment.PaymentAccount;
import bisq.core.user.User;
@@ -55,16 +56,19 @@ class CoreOffersService {
private final CreateOfferService createOfferService;
private final OfferBookService offerBookService;
private final OpenOfferManager openOfferManager;
+ private final OfferUtil offerUtil;
private final User user;
@Inject
public CoreOffersService(CreateOfferService createOfferService,
OfferBookService offerBookService,
OpenOfferManager openOfferManager,
+ OfferUtil offerUtil,
User user) {
this.createOfferService = createOfferService;
this.offerBookService = offerBookService;
this.openOfferManager = openOfferManager;
+ this.offerUtil = offerUtil;
this.user = user;
}
@@ -105,7 +109,11 @@ void createAndPlaceOffer(String currencyCode,
long minAmountAsLong,
double buyerSecurityDeposit,
String paymentAccountId,
+ String makerFeeCurrencyCode,
Consumer resultHandler) {
+
+ offerUtil.maybeSetFeePaymentCurrencyPreference(makerFeeCurrencyCode);
+
String upperCaseCurrencyCode = currencyCode.toUpperCase();
String offerId = createOfferService.getRandomOfferId();
Direction direction = Direction.valueOf(directionAsString.toUpperCase());
diff --git a/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java b/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java
index a202b0dbdb4..dacb79567fd 100644
--- a/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java
+++ b/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java
@@ -18,117 +18,65 @@
package bisq.core.api;
import bisq.core.account.witness.AccountAgeWitnessService;
-import bisq.core.locale.FiatCurrency;
+import bisq.core.api.model.PaymentAccountForm;
import bisq.core.payment.PaymentAccount;
-import bisq.core.payment.PaymentAccountFactory;
-import bisq.core.payment.PerfectMoneyAccount;
import bisq.core.payment.payload.PaymentMethod;
import bisq.core.user.User;
-import bisq.common.config.Config;
-
import javax.inject.Inject;
+import java.io.File;
+
+import java.util.Comparator;
+import java.util.List;
import java.util.Set;
+import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
-import static bisq.core.payment.payload.PaymentMethod.*;
-import static com.google.common.base.Preconditions.checkNotNull;
-
@Slf4j
class CorePaymentAccountsService {
- private final Config config;
private final AccountAgeWitnessService accountAgeWitnessService;
+ private final PaymentAccountForm paymentAccountForm;
private final User user;
@Inject
- public CorePaymentAccountsService(Config config,
- AccountAgeWitnessService accountAgeWitnessService,
+ public CorePaymentAccountsService(AccountAgeWitnessService accountAgeWitnessService,
+ PaymentAccountForm paymentAccountForm,
User user) {
- this.config = config;
this.accountAgeWitnessService = accountAgeWitnessService;
+ this.paymentAccountForm = paymentAccountForm;
this.user = user;
}
- void createPaymentAccount(String paymentMethodId,
- String accountName,
- String accountNumber,
- String currencyCode) {
-
- PaymentAccount paymentAccount = getNewPaymentAccount(paymentMethodId,
- accountName,
- accountNumber,
- currencyCode);
-
+ PaymentAccount createPaymentAccount(String jsonString) {
+ PaymentAccount paymentAccount = paymentAccountForm.toPaymentAccount(jsonString);
user.addPaymentAccountIfNotExists(paymentAccount);
-
- // Don't do this on mainnet until thoroughly tested.
- if (config.baseCurrencyNetwork.isRegtest())
- accountAgeWitnessService.publishMyAccountAgeWitness(paymentAccount.getPaymentAccountPayload());
-
- log.info("Payment account {} saved", paymentAccount.getId());
+ accountAgeWitnessService.publishMyAccountAgeWitness(paymentAccount.getPaymentAccountPayload());
+ log.info("Saved payment account with id {} and payment method {}.",
+ paymentAccount.getId(),
+ paymentAccount.getPaymentAccountPayload().getPaymentMethodId());
+ return paymentAccount;
}
Set getPaymentAccounts() {
return user.getPaymentAccounts();
}
- private PaymentAccount getNewPaymentAccount(String paymentMethodId,
- String accountName,
- String accountNumber,
- String currencyCode) {
- PaymentAccount paymentAccount = null;
- PaymentMethod paymentMethod = getPaymentMethodById(paymentMethodId);
+ List getFiatPaymentMethods() {
+ return PaymentMethod.getPaymentMethods().stream()
+ .filter(paymentMethod -> !paymentMethod.isAsset())
+ .sorted(Comparator.comparing(PaymentMethod::getId))
+ .collect(Collectors.toList());
+ }
- switch (paymentMethod.getId()) {
- case UPHOLD_ID:
- case MONEY_BEAM_ID:
- case POPMONEY_ID:
- case REVOLUT_ID:
- //noinspection DuplicateBranchesInSwitch
- log.error("PaymentMethod {} not supported yet.", paymentMethod);
- break;
- case PERFECT_MONEY_ID:
- // Create and persist a PerfectMoney dummy payment account. There is no
- // guard against creating accounts with duplicate names & numbers, only
- // the uuid and creation date are unique.
- paymentAccount = PaymentAccountFactory.getPaymentAccount(paymentMethod);
- paymentAccount.init();
- paymentAccount.setAccountName(accountName);
- ((PerfectMoneyAccount) paymentAccount).setAccountNr(accountNumber);
- paymentAccount.setSingleTradeCurrency(new FiatCurrency(currencyCode));
- break;
- case SEPA_ID:
- case SEPA_INSTANT_ID:
- case FASTER_PAYMENTS_ID:
- case NATIONAL_BANK_ID:
- case SAME_BANK_ID:
- case SPECIFIC_BANKS_ID:
- case JAPAN_BANK_ID:
- case ALI_PAY_ID:
- case WECHAT_PAY_ID:
- case SWISH_ID:
- case CLEAR_X_CHANGE_ID:
- case CHASE_QUICK_PAY_ID:
- case INTERAC_E_TRANSFER_ID:
- case US_POSTAL_MONEY_ORDER_ID:
- case MONEY_GRAM_ID:
- case WESTERN_UNION_ID:
- case CASH_DEPOSIT_ID:
- case HAL_CASH_ID:
- case F2F_ID:
- case PROMPT_PAY_ID:
- case ADVANCED_CASH_ID:
- default:
- log.error("PaymentMethod {} not supported yet.", paymentMethod);
- break;
- }
+ String getPaymentAccountFormAsString(String paymentMethodId) {
+ File jsonForm = getPaymentAccountForm(paymentMethodId);
+ return paymentAccountForm.toJsonString(jsonForm);
+ }
- checkNotNull(paymentAccount,
- "Could not create payment account with paymentMethodId "
- + paymentMethodId + ".");
- return paymentAccount;
+ File getPaymentAccountForm(String paymentMethodId) {
+ return paymentAccountForm.getPaymentAccountForm(paymentMethodId);
}
}
diff --git a/core/src/main/java/bisq/core/api/CoreTradesService.java b/core/src/main/java/bisq/core/api/CoreTradesService.java
index dbc6927f452..4bc678d9263 100644
--- a/core/src/main/java/bisq/core/api/CoreTradesService.java
+++ b/core/src/main/java/bisq/core/api/CoreTradesService.java
@@ -20,6 +20,7 @@
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.offer.Offer;
+import bisq.core.offer.OfferUtil;
import bisq.core.offer.takeoffer.TakeOfferModel;
import bisq.core.trade.Tradable;
import bisq.core.trade.Trade;
@@ -52,6 +53,7 @@ class CoreTradesService {
private final CoreWalletsService coreWalletsService;
private final BtcWalletService btcWalletService;
+ private final OfferUtil offerUtil;
private final ClosedTradableManager closedTradableManager;
private final TakeOfferModel takeOfferModel;
private final TradeManager tradeManager;
@@ -61,6 +63,7 @@ class CoreTradesService {
@Inject
public CoreTradesService(CoreWalletsService coreWalletsService,
BtcWalletService btcWalletService,
+ OfferUtil offerUtil,
ClosedTradableManager closedTradableManager,
TakeOfferModel takeOfferModel,
TradeManager tradeManager,
@@ -68,6 +71,7 @@ public CoreTradesService(CoreWalletsService coreWalletsService,
User user) {
this.coreWalletsService = coreWalletsService;
this.btcWalletService = btcWalletService;
+ this.offerUtil = offerUtil;
this.closedTradableManager = closedTradableManager;
this.takeOfferModel = takeOfferModel;
this.tradeManager = tradeManager;
@@ -77,7 +81,11 @@ public CoreTradesService(CoreWalletsService coreWalletsService,
void takeOffer(Offer offer,
String paymentAccountId,
+ String takerFeeCurrencyCode,
Consumer resultHandler) {
+
+ offerUtil.maybeSetFeePaymentCurrencyPreference(takerFeeCurrencyCode);
+
var paymentAccount = user.getPaymentAccount(paymentAccountId);
if (paymentAccount == null)
throw new IllegalArgumentException(format("payment account with id '%s' not found", paymentAccountId));
diff --git a/core/src/main/java/bisq/core/api/CoreWalletsService.java b/core/src/main/java/bisq/core/api/CoreWalletsService.java
index fc15ec5062a..529b97a17f6 100644
--- a/core/src/main/java/bisq/core/api/CoreWalletsService.java
+++ b/core/src/main/java/bisq/core/api/CoreWalletsService.java
@@ -18,15 +18,29 @@
package bisq.core.api;
import bisq.core.api.model.AddressBalanceInfo;
+import bisq.core.api.model.BalancesInfo;
+import bisq.core.api.model.BsqBalanceInfo;
+import bisq.core.api.model.BtcBalanceInfo;
import bisq.core.btc.Balances;
+import bisq.core.btc.exceptions.BsqChangeBelowDustException;
+import bisq.core.btc.exceptions.TransactionVerificationException;
+import bisq.core.btc.exceptions.WalletException;
import bisq.core.btc.model.AddressEntry;
+import bisq.core.btc.model.BsqTransferModel;
+import bisq.core.btc.wallet.BsqTransferService;
+import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
+import bisq.core.btc.wallet.TxBroadcaster;
import bisq.core.btc.wallet.WalletsManager;
+import bisq.core.util.coin.BsqFormatter;
import bisq.common.Timer;
import bisq.common.UserThread;
import org.bitcoinj.core.Address;
+import org.bitcoinj.core.Coin;
+import org.bitcoinj.core.InsufficientMoneyException;
+import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.core.TransactionConfidence;
import org.bitcoinj.crypto.KeyCrypterScrypt;
@@ -47,6 +61,7 @@
import javax.annotation.Nullable;
+import static bisq.core.util.ParsingUtils.parseToCoin;
import static java.lang.String.format;
import static java.util.concurrent.TimeUnit.SECONDS;
@@ -55,6 +70,9 @@ class CoreWalletsService {
private final Balances balances;
private final WalletsManager walletsManager;
+ private final BsqWalletService bsqWalletService;
+ private final BsqTransferService bsqTransferService;
+ private final BsqFormatter bsqFormatter;
private final BtcWalletService btcWalletService;
@Nullable
@@ -66,9 +84,15 @@ class CoreWalletsService {
@Inject
public CoreWalletsService(Balances balances,
WalletsManager walletsManager,
+ BsqWalletService bsqWalletService,
+ BsqTransferService bsqTransferService,
+ BsqFormatter bsqFormatter,
BtcWalletService btcWalletService) {
this.balances = balances;
this.walletsManager = walletsManager;
+ this.bsqWalletService = bsqWalletService;
+ this.bsqTransferService = bsqTransferService;
+ this.bsqFormatter = bsqFormatter;
this.btcWalletService = btcWalletService;
}
@@ -78,6 +102,7 @@ KeyParameter getKey() {
return tempAesKey;
}
+ @Deprecated
long getAvailableBalance() {
verifyWalletsAreAvailable();
verifyEncryptedWalletIsUnlocked();
@@ -89,6 +114,23 @@ long getAvailableBalance() {
return balance.getValue();
}
+ BalancesInfo getBalances(String currencyCode) {
+ verifyWalletCurrencyCodeIsValid(currencyCode);
+ verifyWalletsAreAvailable();
+ verifyEncryptedWalletIsUnlocked();
+ if (balances.getAvailableBalance().get() == null)
+ throw new IllegalStateException("balance is not yet available");
+
+ switch (currencyCode.trim().toUpperCase()) {
+ case "BSQ":
+ return new BalancesInfo(getBsqBalances(), BtcBalanceInfo.EMPTY);
+ case "BTC":
+ return new BalancesInfo(BsqBalanceInfo.EMPTY, getBtcBalances());
+ default:
+ return new BalancesInfo(getBsqBalances(), getBtcBalances());
+ }
+ }
+
long getAddressBalance(String addressString) {
Address address = getAddressEntry(addressString).getAddress();
return btcWalletService.getBalanceForAddress(address).value;
@@ -134,6 +176,27 @@ List getFundingAddresses() {
.collect(Collectors.toList());
}
+ String getUnusedBsqAddress() {
+ return bsqWalletService.getUnusedBsqAddressAsString();
+ }
+
+ void sendBsq(String address,
+ double amount,
+ TxBroadcaster.Callback callback) {
+ try {
+ LegacyAddress legacyAddress = getValidBsqLegacyAddress(address);
+ Coin receiverAmount = getValidBsqTransferAmount(amount);
+ BsqTransferModel model = bsqTransferService.getBsqTransferModel(legacyAddress, receiverAmount);
+ bsqTransferService.sendFunds(model, callback);
+ } catch (InsufficientMoneyException
+ | BsqChangeBelowDustException
+ | TransactionVerificationException
+ | WalletException ex) {
+ log.error("", ex);
+ throw new IllegalStateException(ex);
+ }
+ }
+
int getNumConfirmationsForMostRecentTransaction(String addressString) {
Address address = getAddressEntry(addressString).getAddress();
TransactionConfidence confidence = btcWalletService.getConfidenceForAddress(address);
@@ -244,6 +307,76 @@ private void verifyEncryptedWalletIsUnlocked() {
throw new IllegalStateException("wallet is locked");
}
+ // Throws a RuntimeException if wallet currency code is not BSQ or BTC.
+ private void verifyWalletCurrencyCodeIsValid(String currencyCode) {
+ if (currencyCode == null || currencyCode.isEmpty())
+ return;
+
+ if (!currencyCode.equalsIgnoreCase("BSQ")
+ && !currencyCode.equalsIgnoreCase("BTC"))
+ throw new IllegalStateException(format("wallet does not support %s", currencyCode));
+ }
+
+ private BsqBalanceInfo getBsqBalances() {
+ verifyWalletsAreAvailable();
+ verifyEncryptedWalletIsUnlocked();
+
+ var availableConfirmedBalance = bsqWalletService.getAvailableConfirmedBalance();
+ var unverifiedBalance = bsqWalletService.getUnverifiedBalance();
+ var unconfirmedChangeBalance = bsqWalletService.getUnconfirmedChangeBalance();
+ var lockedForVotingBalance = bsqWalletService.getLockedForVotingBalance();
+ var lockupBondsBalance = bsqWalletService.getLockupBondsBalance();
+ var unlockingBondsBalance = bsqWalletService.getUnlockingBondsBalance();
+
+ return new BsqBalanceInfo(availableConfirmedBalance.value,
+ unverifiedBalance.value,
+ unconfirmedChangeBalance.value,
+ lockedForVotingBalance.value,
+ lockupBondsBalance.value,
+ unlockingBondsBalance.value);
+ }
+
+ private BtcBalanceInfo getBtcBalances() {
+ verifyWalletsAreAvailable();
+ verifyEncryptedWalletIsUnlocked();
+
+ var availableBalance = balances.getAvailableBalance().get();
+ if (availableBalance == null)
+ throw new IllegalStateException("balance is not yet available");
+
+ var reservedBalance = balances.getReservedBalance().get();
+ if (reservedBalance == null)
+ throw new IllegalStateException("reserved balance is not yet available");
+
+ var lockedBalance = balances.getLockedBalance().get();
+ if (lockedBalance == null)
+ throw new IllegalStateException("locked balance is not yet available");
+
+ return new BtcBalanceInfo(availableBalance.value,
+ reservedBalance.value,
+ availableBalance.add(reservedBalance).value,
+ 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 double amount, or a RuntimeException if invalid.
+ private Coin getValidBsqTransferAmount(double amount) {
+ Coin amountAsCoin = parseToCoin(Double.toString(amount), bsqFormatter);
+ if (amountAsCoin.equals(Coin.ZERO))
+ throw new IllegalStateException(format("%.2f bsq is an invalid send amount", amount));
+
+ return amountAsCoin;
+ }
+
private KeyCrypterScrypt getKeyCrypterScrypt() {
KeyCrypterScrypt keyCrypterScrypt = walletsManager.getKeyCrypterScrypt();
if (keyCrypterScrypt == null)
diff --git a/core/src/main/java/bisq/core/api/model/BalancesInfo.java b/core/src/main/java/bisq/core/api/model/BalancesInfo.java
new file mode 100644
index 00000000000..3b063bc0d2b
--- /dev/null
+++ b/core/src/main/java/bisq/core/api/model/BalancesInfo.java
@@ -0,0 +1,45 @@
+package bisq.core.api.model;
+
+import bisq.common.Payload;
+
+import lombok.Getter;
+
+@Getter
+public class BalancesInfo implements Payload {
+
+ // Getter names are shortened for readability's sake, i.e.,
+ // balancesInfo.getBtc().getAvailableBalance() is cleaner than
+ // balancesInfo.getBtcBalanceInfo().getAvailableBalance().
+ private final BsqBalanceInfo bsq;
+ private final BtcBalanceInfo btc;
+
+ public BalancesInfo(BsqBalanceInfo bsq, BtcBalanceInfo btc) {
+ this.bsq = bsq;
+ this.btc = btc;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // PROTO BUFFER
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public bisq.proto.grpc.BalancesInfo toProtoMessage() {
+ return bisq.proto.grpc.BalancesInfo.newBuilder()
+ .setBsq(bsq.toProtoMessage())
+ .setBtc(btc.toProtoMessage())
+ .build();
+ }
+
+ public static BalancesInfo fromProto(bisq.proto.grpc.BalancesInfo proto) {
+ return new BalancesInfo(BsqBalanceInfo.fromProto(proto.getBsq()),
+ BtcBalanceInfo.fromProto(proto.getBtc()));
+ }
+
+ @Override
+ public String toString() {
+ return "BalancesInfo{" + "\n" +
+ " " + bsq.toString() + "\n" +
+ ", " + btc.toString() + "\n" +
+ '}';
+ }
+}
diff --git a/core/src/main/java/bisq/core/api/model/BsqBalanceInfo.java b/core/src/main/java/bisq/core/api/model/BsqBalanceInfo.java
new file mode 100644
index 00000000000..23324e21f33
--- /dev/null
+++ b/core/src/main/java/bisq/core/api/model/BsqBalanceInfo.java
@@ -0,0 +1,94 @@
+package bisq.core.api.model;
+
+import bisq.common.Payload;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import lombok.Getter;
+
+@Getter
+public class BsqBalanceInfo implements Payload {
+
+ public static final BsqBalanceInfo EMPTY = new BsqBalanceInfo(-1,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1);
+
+ // All balances are in BSQ satoshis.
+ private final long availableConfirmedBalance;
+ private final long unverifiedBalance;
+ private final long unconfirmedChangeBalance;
+ private final long lockedForVotingBalance;
+ private final long lockupBondsBalance;
+ private final long unlockingBondsBalance;
+
+ public BsqBalanceInfo(long availableConfirmedBalance,
+ long unverifiedBalance,
+ long unconfirmedChangeBalance,
+ long lockedForVotingBalance,
+ long lockupBondsBalance,
+ long unlockingBondsBalance) {
+ this.availableConfirmedBalance = availableConfirmedBalance;
+ this.unverifiedBalance = unverifiedBalance;
+ this.unconfirmedChangeBalance = unconfirmedChangeBalance;
+ this.lockedForVotingBalance = lockedForVotingBalance;
+ this.lockupBondsBalance = lockupBondsBalance;
+ this.unlockingBondsBalance = unlockingBondsBalance;
+ }
+
+ @VisibleForTesting
+ public static BsqBalanceInfo valueOf(long availableConfirmedBalance,
+ long unverifiedBalance,
+ long unconfirmedChangeBalance,
+ long lockedForVotingBalance,
+ long lockupBondsBalance,
+ long unlockingBondsBalance) {
+ // Convenience for creating a model instance instead of a proto.
+ return new BsqBalanceInfo(availableConfirmedBalance,
+ unverifiedBalance,
+ unconfirmedChangeBalance,
+ lockedForVotingBalance,
+ lockupBondsBalance,
+ unlockingBondsBalance);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // PROTO BUFFER
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public bisq.proto.grpc.BsqBalanceInfo toProtoMessage() {
+ return bisq.proto.grpc.BsqBalanceInfo.newBuilder()
+ .setAvailableConfirmedBalance(availableConfirmedBalance)
+ .setUnverifiedBalance(unverifiedBalance)
+ .setUnconfirmedChangeBalance(unconfirmedChangeBalance)
+ .setLockedForVotingBalance(lockedForVotingBalance)
+ .setLockupBondsBalance(lockupBondsBalance)
+ .setUnlockingBondsBalance(unlockingBondsBalance)
+ .build();
+
+ }
+
+ public static BsqBalanceInfo fromProto(bisq.proto.grpc.BsqBalanceInfo proto) {
+ return new BsqBalanceInfo(proto.getAvailableConfirmedBalance(),
+ proto.getUnverifiedBalance(),
+ proto.getUnconfirmedChangeBalance(),
+ proto.getLockedForVotingBalance(),
+ proto.getLockupBondsBalance(),
+ proto.getUnlockingBondsBalance());
+ }
+
+ @Override
+ public String toString() {
+ return "BsqBalanceInfo{" +
+ "availableConfirmedBalance=" + availableConfirmedBalance +
+ ", unverifiedBalance=" + unverifiedBalance +
+ ", unconfirmedChangeBalance=" + unconfirmedChangeBalance +
+ ", lockedForVotingBalance=" + lockedForVotingBalance +
+ ", lockupBondsBalance=" + lockupBondsBalance +
+ ", unlockingBondsBalance=" + unlockingBondsBalance +
+ '}';
+ }
+}
diff --git a/core/src/main/java/bisq/core/api/model/BtcBalanceInfo.java b/core/src/main/java/bisq/core/api/model/BtcBalanceInfo.java
new file mode 100644
index 00000000000..e3803b0001e
--- /dev/null
+++ b/core/src/main/java/bisq/core/api/model/BtcBalanceInfo.java
@@ -0,0 +1,75 @@
+package bisq.core.api.model;
+
+import bisq.common.Payload;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import lombok.Getter;
+
+@Getter
+public class BtcBalanceInfo implements Payload {
+
+ public static final BtcBalanceInfo EMPTY = new BtcBalanceInfo(-1,
+ -1,
+ -1,
+ -1);
+
+ // All balances are in BTC satoshis.
+ private final long availableBalance;
+ private final long reservedBalance;
+ private final long totalAvailableBalance; // available + reserved
+ private final long lockedBalance;
+
+ public BtcBalanceInfo(long availableBalance,
+ long reservedBalance,
+ long totalAvailableBalance,
+ long lockedBalance) {
+ this.availableBalance = availableBalance;
+ this.reservedBalance = reservedBalance;
+ this.totalAvailableBalance = totalAvailableBalance;
+ this.lockedBalance = lockedBalance;
+ }
+
+ @VisibleForTesting
+ public static BtcBalanceInfo valueOf(long availableBalance,
+ long reservedBalance,
+ long totalAvailableBalance,
+ long lockedBalance) {
+ // Convenience for creating a model instance instead of a proto.
+ return new BtcBalanceInfo(availableBalance,
+ reservedBalance,
+ totalAvailableBalance,
+ lockedBalance);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // PROTO BUFFER
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public bisq.proto.grpc.BtcBalanceInfo toProtoMessage() {
+ return bisq.proto.grpc.BtcBalanceInfo.newBuilder()
+ .setAvailableBalance(availableBalance)
+ .setReservedBalance(reservedBalance)
+ .setTotalAvailableBalance(totalAvailableBalance)
+ .setLockedBalance(lockedBalance)
+ .build();
+ }
+
+ public static BtcBalanceInfo fromProto(bisq.proto.grpc.BtcBalanceInfo proto) {
+ return new BtcBalanceInfo(proto.getAvailableBalance(),
+ proto.getReservedBalance(),
+ proto.getTotalAvailableBalance(),
+ proto.getLockedBalance());
+ }
+
+ @Override
+ public String toString() {
+ return "BtcBalanceInfo{" +
+ "availableBalance=" + availableBalance +
+ ", reservedBalance=" + reservedBalance +
+ ", totalAvailableBalance=" + totalAvailableBalance +
+ ", lockedBalance=" + lockedBalance +
+ '}';
+ }
+}
diff --git a/core/src/main/java/bisq/core/api/model/OfferInfo.java b/core/src/main/java/bisq/core/api/model/OfferInfo.java
index 219045b2762..cc11aacb1d9 100644
--- a/core/src/main/java/bisq/core/api/model/OfferInfo.java
+++ b/core/src/main/java/bisq/core/api/model/OfferInfo.java
@@ -46,6 +46,7 @@ public class OfferInfo implements Payload {
private final long volume;
private final long minVolume;
private final long buyerSecurityDeposit;
+ private final boolean isCurrencyForMakerFeeBtc;
private final String paymentAccountId; // only used when creating offer
private final String paymentMethodId;
private final String paymentMethodShortName;
@@ -67,6 +68,7 @@ public OfferInfo(OfferInfoBuilder builder) {
this.volume = builder.volume;
this.minVolume = builder.minVolume;
this.buyerSecurityDeposit = builder.buyerSecurityDeposit;
+ this.isCurrencyForMakerFeeBtc = builder.isCurrencyForMakerFeeBtc;
this.paymentAccountId = builder.paymentAccountId;
this.paymentMethodId = builder.paymentMethodId;
this.paymentMethodShortName = builder.paymentMethodShortName;
@@ -88,6 +90,7 @@ public static OfferInfo toOfferInfo(Offer offer) {
.withVolume(Objects.requireNonNull(offer.getVolume()).getValue())
.withMinVolume(Objects.requireNonNull(offer.getMinVolume()).getValue())
.withBuyerSecurityDeposit(offer.getBuyerSecurityDeposit().value)
+ .withIsCurrencyForMakerFeeBtc(offer.isCurrencyForMakerFeeBtc())
.withPaymentAccountId(offer.getMakerPaymentAccountId())
.withPaymentMethodId(offer.getPaymentMethod().getId())
.withPaymentMethodShortName(offer.getPaymentMethod().getShortName())
@@ -115,6 +118,7 @@ public bisq.proto.grpc.OfferInfo toProtoMessage() {
.setVolume(volume)
.setMinVolume(minVolume)
.setBuyerSecurityDeposit(buyerSecurityDeposit)
+ .setIsCurrencyForMakerFeeBtc(isCurrencyForMakerFeeBtc)
.setPaymentAccountId(paymentAccountId)
.setPaymentMethodId(paymentMethodId)
.setPaymentMethodShortName(paymentMethodShortName)
@@ -147,6 +151,7 @@ public static class OfferInfoBuilder {
private long volume;
private long minVolume;
private long buyerSecurityDeposit;
+ private boolean isCurrencyForMakerFeeBtc;
private String paymentAccountId;
private String paymentMethodId;
private String paymentMethodShortName;
@@ -205,6 +210,11 @@ public OfferInfoBuilder withBuyerSecurityDeposit(long buyerSecurityDeposit) {
return this;
}
+ public OfferInfoBuilder withIsCurrencyForMakerFeeBtc(boolean isCurrencyForMakerFeeBtc) {
+ this.isCurrencyForMakerFeeBtc = isCurrencyForMakerFeeBtc;
+ return this;
+ }
+
public OfferInfoBuilder withPaymentAccountId(String paymentAccountId) {
this.paymentAccountId = paymentAccountId;
return this;
diff --git a/core/src/main/java/bisq/core/api/model/PaymentAccountForm.java b/core/src/main/java/bisq/core/api/model/PaymentAccountForm.java
new file mode 100644
index 00000000000..b82656b4330
--- /dev/null
+++ b/core/src/main/java/bisq/core/api/model/PaymentAccountForm.java
@@ -0,0 +1,242 @@
+/*
+ * 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.model;
+
+import bisq.core.payment.PaymentAccount;
+import bisq.core.payment.PaymentAccountFactory;
+import bisq.core.payment.payload.PaymentMethod;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+import javax.inject.Singleton;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+
+import java.util.Map;
+
+import java.lang.reflect.Type;
+
+import lombok.extern.slf4j.Slf4j;
+
+import static bisq.core.payment.payload.PaymentMethod.getPaymentMethodById;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.lang.String.format;
+import static java.lang.System.getProperty;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+
+/**
+ *
+ * An instance of this class can write new payment account forms (editable json files),
+ * and de-serialize edited json files into {@link bisq.core.payment.PaymentAccount}
+ * instances.
+ *
+ *
+ * Example use case: (1) ask for a blank Hal Cash account form, (2) edit it, (3) derive a
+ * {@link bisq.core.payment.HalCashAccount} instance from the edited json file.
+ *
+ *
+ *
+ * (1) Ask for a hal cash account form: Pass a {@link bisq.core.payment.payload.PaymentMethod#HAL_CASH_ID}
+ * to {@link bisq.core.api.model.PaymentAccountForm#getPaymentAccountForm(String)} to
+ * get the json Hal Cash payment account form:
+ *
+ * {
+ * "_COMMENT_": "Please do not edit the paymentMethodId field.",
+ * "paymentMethodId": "HAL_CASH",
+ * "accountName": "Your accountname",
+ * "mobileNr": "Your mobilenr"
+ * }
+ *
+ *
+ *
+ * (2) Save the Hal Cash payment account form to disk, and edit it:
+ *
+ * {
+ * "_COMMENT_": "Please do not edit the paymentMethodId field.",
+ * "paymentMethodId": "HAL_CASH",
+ * "accountName": "Hal Cash Acct",
+ * "mobileNr": "798 123 456"
+ * }
+ *
+ *
+ * (3) De-serialize the edited json account form: Pass the edited json file to
+ * {@link bisq.core.api.model.PaymentAccountForm#toPaymentAccount(File)}, or
+ * a json string to {@link bisq.core.api.model.PaymentAccountForm#toPaymentAccount(String)}
+ * and get a {@link bisq.core.payment.HalCashAccount} instance.
+ *
+ * PaymentAccount(
+ * paymentMethod=PaymentMethod(id=HAL_CASH,
+ * maxTradePeriod=86400000,
+ * maxTradeLimit=50000000),
+ * id=e33c9d94-1a1a-43fd-aa11-fcaacbb46100,
+ * creationDate=Mon Nov 16 12:26:43 BRST 2020,
+ * paymentAccountPayload=HalCashAccountPayload(mobileNr=798 123 456),
+ * accountName=Hal Cash Acct,
+ * tradeCurrencies=[FiatCurrency(currency=EUR)],
+ * selectedTradeCurrency=FiatCurrency(currency=EUR)
+ * )
+ *
+ */
+@Singleton
+@Slf4j
+public class PaymentAccountForm {
+
+ private final GsonBuilder gsonBuilder = new GsonBuilder()
+ .setPrettyPrinting()
+ .serializeNulls();
+
+ // Names of PaymentAccount fields to exclude from json forms.
+ private final String[] excludedFields = new String[]{
+ "log",
+ "id",
+ "acceptedCountryCodes",
+ "countryCode",
+ "creationDate",
+ "excludeFromJsonDataMap",
+ "maxTradePeriod",
+ "paymentAccountPayload",
+ "paymentMethod",
+ "paymentMethodId",
+ "selectedTradeCurrency",
+ "tradeCurrencies",
+ "HOLDER_NAME",
+ "SALT"
+ };
+
+ /**
+ * Returns a blank payment account form (json) for the given paymentMethodId.
+ *
+ * @param paymentMethodId Determines what kind of json form to return.
+ * @return A uniquely named tmp file used to define new payment account details.
+ */
+ public File getPaymentAccountForm(String paymentMethodId) {
+ PaymentMethod paymentMethod = getPaymentMethodById(paymentMethodId);
+ File file = getTmpJsonFile(paymentMethodId);
+ try (OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream(checkNotNull(file), false), UTF_8)) {
+ PaymentAccount paymentAccount = PaymentAccountFactory.getPaymentAccount(paymentMethod);
+ Class extends PaymentAccount> clazz = paymentAccount.getClass();
+ Gson gson = gsonBuilder.registerTypeAdapter(clazz, new PaymentAccountTypeAdapter(clazz, excludedFields)).create();
+ String json = gson.toJson(paymentAccount); // serializes target to json
+ outputStreamWriter.write(json);
+ } catch (Exception ex) {
+ String errMsg = format("cannot create a payment account form for a %s payment method", paymentMethodId);
+ log.error(StringUtils.capitalize(errMsg) + ".", ex);
+ throw new IllegalStateException(errMsg);
+ }
+ return file;
+ }
+
+ /**
+ * De-serialize a PaymentAccount json form into a new PaymentAccount instance.
+ *
+ * @param jsonForm The file representing a new payment account form.
+ * @return A populated PaymentAccount subclass instance.
+ */
+ @SuppressWarnings("unused")
+ @VisibleForTesting
+ public PaymentAccount toPaymentAccount(File jsonForm) {
+ String jsonString = toJsonString(jsonForm);
+ return toPaymentAccount(jsonString);
+ }
+
+ /**
+ * De-serialize a PaymentAccount json string into a new PaymentAccount instance.
+ *
+ * @param jsonString The json data representing a new payment account form.
+ * @return A populated PaymentAccount subclass instance.
+ */
+ public PaymentAccount toPaymentAccount(String jsonString) {
+ Class extends PaymentAccount> clazz = getPaymentAccountClassFromJson(jsonString);
+ Gson gson = gsonBuilder.registerTypeAdapter(clazz, new PaymentAccountTypeAdapter(clazz)).create();
+ return gson.fromJson(jsonString, clazz);
+ }
+
+ public String toJsonString(File jsonFile) {
+ try {
+ checkNotNull(jsonFile, "json file cannot be null");
+ return new String(Files.readAllBytes(Paths.get(jsonFile.getAbsolutePath())));
+ } catch (IOException ex) {
+ String errMsg = format("cannot read json string from file '%s'",
+ jsonFile.getAbsolutePath());
+ log.error(StringUtils.capitalize(errMsg) + ".", ex);
+ throw new IllegalStateException(errMsg);
+ }
+ }
+
+ @VisibleForTesting
+ public URI getClickableURI(File jsonFile) {
+ try {
+ return new URI("file",
+ "",
+ jsonFile.toURI().getPath(),
+ null,
+ null);
+ } catch (URISyntaxException ex) {
+ String errMsg = format("cannot create clickable url to file '%s'",
+ jsonFile.getAbsolutePath());
+ log.error(StringUtils.capitalize(errMsg) + ".", ex);
+ throw new IllegalStateException(errMsg);
+ }
+ }
+
+ @VisibleForTesting
+ public static File getTmpJsonFile(String paymentMethodId) {
+ File file;
+ try {
+ // Creates a tmp file that includes a random number string between the
+ // prefix and suffix, i.e., sepa_form_13243546575879.json, so there is
+ // little chance this will fail because the tmp file already exists.
+ file = File.createTempFile(paymentMethodId.toLowerCase() + "_form_",
+ ".json",
+ Paths.get(getProperty("java.io.tmpdir")).toFile());
+ } catch (IOException ex) {
+ String errMsg = format("cannot create json file for a %s payment method",
+ paymentMethodId);
+ log.error(StringUtils.capitalize(errMsg) + ".", ex);
+ throw new IllegalStateException(errMsg);
+ }
+ return file;
+ }
+
+ private Class extends PaymentAccount> getPaymentAccountClassFromJson(String json) {
+ Map jsonMap = gsonBuilder.create().fromJson(json, (Type) Object.class);
+ String paymentMethodId = checkNotNull((String) jsonMap.get("paymentMethodId"),
+ format("cannot not find a paymentMethodId in json string: %s", json));
+ return getPaymentAccountClass(paymentMethodId);
+ }
+
+ private Class extends PaymentAccount> getPaymentAccountClass(String paymentMethodId) {
+ PaymentMethod paymentMethod = getPaymentMethodById(paymentMethodId);
+ return PaymentAccountFactory.getPaymentAccount(paymentMethod).getClass();
+ }
+}
diff --git a/core/src/main/java/bisq/core/api/model/PaymentAccountTypeAdapter.java b/core/src/main/java/bisq/core/api/model/PaymentAccountTypeAdapter.java
new file mode 100644
index 00000000000..13646d3f0d0
--- /dev/null
+++ b/core/src/main/java/bisq/core/api/model/PaymentAccountTypeAdapter.java
@@ -0,0 +1,347 @@
+/*
+ * 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.model;
+
+
+import bisq.core.locale.Country;
+import bisq.core.locale.FiatCurrency;
+import bisq.core.payment.CountryBasedPaymentAccount;
+import bisq.core.payment.MoneyGramAccount;
+import bisq.core.payment.PaymentAccount;
+import bisq.core.payment.payload.PaymentAccountPayload;
+
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.IOException;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Predicate;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import lombok.extern.slf4j.Slf4j;
+
+import static bisq.common.util.ReflectionUtils.getSetterMethodForFieldInClassHierarchy;
+import static bisq.common.util.ReflectionUtils.getVisibilityModifierAsString;
+import static bisq.common.util.ReflectionUtils.isSetterOnClass;
+import static bisq.common.util.ReflectionUtils.loadFieldListForClassHierarchy;
+import static bisq.core.locale.CountryUtil.findCountryByCode;
+import static bisq.core.locale.CurrencyUtil.getCurrencyByCountryCode;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.lang.String.format;
+import static java.util.Comparator.comparing;
+
+@Slf4j
+class PaymentAccountTypeAdapter extends TypeAdapter {
+
+ private final Class extends PaymentAccount> paymentAccountType;
+ private final Class extends PaymentAccountPayload> paymentAccountPayloadType;
+ private final Map> fieldSettersMap;
+ private final Predicate isExcludedField;
+
+ /**
+ * Constructor used when de-serializing a json payment account form into a
+ * PaymentAccount instance.
+ *
+ * @param paymentAccountType the PaymentAccount subclass being instantiated
+ */
+ public PaymentAccountTypeAdapter(Class extends PaymentAccount> paymentAccountType) {
+ this(paymentAccountType, new String[]{});
+ }
+
+ /**
+ * Constructor used when serializing a PaymentAccount subclass instance into a json
+ * payment account json form.
+ *
+ * @param paymentAccountType the PaymentAccount subclass being serialized
+ * @param excludedFields a string array of field names to exclude from the serialized
+ * payment account json form.
+ */
+ public PaymentAccountTypeAdapter(Class extends PaymentAccount> paymentAccountType, String[] excludedFields) {
+ this.paymentAccountType = paymentAccountType;
+ this.paymentAccountPayloadType = getPaymentAccountPayloadType();
+ this.isExcludedField = (f) -> Arrays.stream(excludedFields).anyMatch(e -> e.equals(f.getName()));
+ this.fieldSettersMap = getFieldSetterMap();
+ }
+
+ @Override
+ public void write(JsonWriter out, PaymentAccount account) throws IOException {
+ // We write a blank payment acct form for a payment method id.
+ // We're not serializing a real payment account instance here.
+ if (log.isDebugEnabled())
+ log.debug("Writing PaymentAccount json form for fields with accessors...");
+
+ out.beginObject();
+ writeCommonFields(out, account);
+ fieldSettersMap.forEach((field, value) -> {
+ try {
+ // Write out a json element if there is a @Setter for this field.
+ if (value.isPresent()) {
+ if (log.isDebugEnabled())
+ log.debug("Append form with settable field: {} {} {} setter: {}",
+ getVisibilityModifierAsString(field),
+ field.getType().getSimpleName(),
+ field.getName(),
+ value);
+
+ String fieldName = field.getName();
+ out.name(fieldName);
+ out.value("Your " + fieldName.toLowerCase());
+ }
+ } catch (Exception ex) {
+ String errMsg = format("cannot create a new %s json form",
+ account.getClass().getSimpleName());
+ log.error(StringUtils.capitalize(errMsg) + ".", ex);
+ throw new IllegalStateException("programmer error: " + errMsg);
+ }
+ });
+ out.endObject();
+ if (log.isDebugEnabled())
+ log.debug("Done writing PaymentAccount json form.");
+ }
+
+ @Override
+ public PaymentAccount read(JsonReader in) throws IOException {
+ if (log.isDebugEnabled())
+ log.debug("De-serializing json to new {} ...", paymentAccountType.getSimpleName());
+
+ PaymentAccount account = initNewPaymentAccount();
+ in.beginObject();
+ while (in.hasNext()) {
+ String currentFieldName = in.nextName();
+
+ // Some of the fields are common to all payment account types.
+ if (didReadCommonField(in, account, currentFieldName))
+ continue;
+
+ // If the account is a subclass of CountryBasedPaymentAccount, set the
+ // account's Country, and use the Country to derive and set the account's
+ // FiatCurrency.
+ if (didReadCountryField(in, account, currentFieldName))
+ continue;
+
+ Optional field = fieldSettersMap.keySet().stream()
+ .filter(k -> k.getName().equals(currentFieldName)).findFirst();
+
+ field.ifPresentOrElse((f) -> invokeSetterMethod(account, f, in), () -> {
+ throw new IllegalStateException(
+ format("programmer error: cannot de-serialize json to a '%s' "
+ + " because there is no %s field.",
+ account.getClass().getSimpleName(),
+ currentFieldName));
+ });
+ }
+ in.endObject();
+ if (log.isDebugEnabled())
+ log.debug("Done de-serializing json.");
+
+ return account;
+ }
+
+ private void invokeSetterMethod(PaymentAccount account, Field field, JsonReader jsonReader) {
+ Optional setter = fieldSettersMap.get(field);
+ if (setter.isPresent()) {
+ try {
+ // The setter might be on the PaymentAccount instance, or its
+ // PaymentAccountPayload instance.
+ if (isSetterOnPaymentAccountClass(setter.get(), account)) {
+ setter.get().invoke(account, nextStringOrNull(jsonReader));
+ } else if (isSetterOnPaymentAccountPayloadClass(setter.get(), account)) {
+ setter.get().invoke(account.getPaymentAccountPayload(), nextStringOrNull(jsonReader));
+ } else {
+ String errMsg = format("programmer error: cannot de-serialize json to a '%s' using reflection"
+ + " because the setter method's declaring class was not found.",
+ account.getClass().getSimpleName());
+ throw new IllegalStateException(errMsg);
+ }
+ } catch (IllegalAccessException | InvocationTargetException ex) {
+ String errMsg = format("cannot set field value for %s on %s",
+ field.getName(),
+ account.getClass().getSimpleName());
+ log.error(StringUtils.capitalize(errMsg) + ".", ex);
+ throw new IllegalStateException("programmer error: " + errMsg);
+ }
+ } else {
+ throw new IllegalStateException(
+ format("programmer error: cannot de-serialize json to a '%s' "
+ + " because there is no setter method for field %s.",
+ account.getClass().getSimpleName(),
+ field.getName()));
+ }
+ }
+
+ private boolean isSetterOnPaymentAccountClass(Method setter, PaymentAccount account) {
+ return isSetterOnClass(setter, account.getClass());
+ }
+
+ private boolean isSetterOnPaymentAccountPayloadClass(Method setter, PaymentAccount account) {
+ return isSetterOnClass(setter, account.getPaymentAccountPayload().getClass())
+ || isSetterOnClass(setter, account.getPaymentAccountPayload().getClass().getSuperclass());
+ }
+
+ private Map> getFieldSetterMap() {
+ List orderedFields = getOrderedFields();
+ Map> map = new LinkedHashMap<>();
+ for (Field field : orderedFields) {
+ Optional setter = getSetterMethodForFieldInClassHierarchy(field, paymentAccountType)
+ .or(() -> getSetterMethodForFieldInClassHierarchy(field, paymentAccountPayloadType));
+ map.put(field, setter);
+ }
+ return Collections.unmodifiableMap(map);
+ }
+
+ private List getOrderedFields() {
+ List fields = new ArrayList<>();
+ loadFieldListForClassHierarchy(fields, paymentAccountType, isExcludedField);
+ loadFieldListForClassHierarchy(fields, paymentAccountPayloadType, isExcludedField);
+ fields.sort(comparing(Field::getName));
+ return fields;
+ }
+
+ private String nextStringOrNull(JsonReader in) {
+ try {
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ } else {
+ return in.nextString();
+ }
+ } catch (IOException ex) {
+ String errMsg = "cannot see next string in json reader";
+ log.error(StringUtils.capitalize(errMsg) + ".", ex);
+ throw new IllegalStateException("programmer error: " + errMsg);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private Long nextLongOrNull(JsonReader in) {
+ try {
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ } else {
+ return in.nextLong();
+ }
+ } catch (IOException ex) {
+ String errMsg = "cannot see next long in json reader";
+ log.error(StringUtils.capitalize(errMsg) + ".", ex);
+ throw new IllegalStateException("programmer error: " + errMsg);
+ }
+ }
+
+ private void writeCommonFields(JsonWriter out, PaymentAccount account) throws IOException {
+ if (log.isDebugEnabled())
+ log.debug("writeCommonFields(out, {}) -> paymentMethodId = {}",
+ account, account.getPaymentMethod().getId());
+
+ out.name("_COMMENT_");
+ out.value("Please do not edit the paymentMethodId field.");
+
+ out.name("paymentMethodId");
+ out.value(account.getPaymentMethod().getId());
+ }
+
+ private boolean didReadCommonField(JsonReader in, PaymentAccount account, String fieldName) {
+ switch (fieldName) {
+ case "_COMMENT_":
+ case "paymentMethodId":
+ // skip
+ nextStringOrNull(in);
+ return true;
+ case "accountName":
+ account.setAccountName(nextStringOrNull(in));
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private boolean didReadCountryField(JsonReader in, PaymentAccount account, String fieldName) {
+ if (!fieldName.equals("country"))
+ return false;
+
+ String countryCode = nextStringOrNull(in);
+ Optional country = findCountryByCode(countryCode);
+ if (country.isPresent()) {
+
+ if (account.isCountryBasedPaymentAccount()) {
+ ((CountryBasedPaymentAccount) account).setCountry(country.get());
+ FiatCurrency fiatCurrency = getCurrencyByCountryCode(checkNotNull(countryCode));
+ account.setSingleTradeCurrency(fiatCurrency);
+ } else if (account.isMoneyGramAccount()) {
+ ((MoneyGramAccount) account).setCountry(country.get());
+ } else {
+ String errMsg = format("cannot set the country on a %s",
+ paymentAccountType.getSimpleName());
+ log.error(StringUtils.capitalize(errMsg) + ".");
+ throw new IllegalStateException("programmer error: " + errMsg);
+ }
+
+ return true;
+
+ } else {
+ throw new IllegalArgumentException(
+ format("'%s' is an invalid country code.", countryCode));
+ }
+ }
+
+ private Class extends PaymentAccountPayload> getPaymentAccountPayloadType() {
+ try {
+ Package pkg = PaymentAccountPayload.class.getPackage();
+ //noinspection unchecked
+ return (Class extends PaymentAccountPayload>) Class.forName(pkg.getName()
+ + "." + paymentAccountType.getSimpleName() + "Payload");
+ } catch (Exception ex) {
+ String errMsg = format("cannot get the payload class for %s",
+ paymentAccountType.getSimpleName());
+ log.error(StringUtils.capitalize(errMsg) + ".", ex);
+ throw new IllegalStateException("programmer error: " + errMsg);
+ }
+ }
+
+ private PaymentAccount initNewPaymentAccount() {
+ try {
+ Constructor> constructor = paymentAccountType.getDeclaredConstructor();
+ PaymentAccount paymentAccount = (PaymentAccount) constructor.newInstance();
+ paymentAccount.init();
+ return paymentAccount;
+ } catch (NoSuchMethodException
+ | IllegalAccessException
+ | InstantiationException
+ | InvocationTargetException ex) {
+ String errMsg = format("cannot instantiate a new %s",
+ paymentAccountType.getSimpleName());
+ log.error(StringUtils.capitalize(errMsg) + ".", ex);
+ throw new IllegalStateException("programmer error: " + errMsg);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/btc/model/BsqTransferModel.java b/core/src/main/java/bisq/core/btc/model/BsqTransferModel.java
new file mode 100644
index 00000000000..38e4c4b5423
--- /dev/null
+++ b/core/src/main/java/bisq/core/btc/model/BsqTransferModel.java
@@ -0,0 +1,77 @@
+package bisq.core.btc.model;
+
+import bisq.core.dao.state.model.blockchain.TxType;
+import bisq.core.util.coin.CoinUtil;
+
+import org.bitcoinj.core.Coin;
+import org.bitcoinj.core.LegacyAddress;
+import org.bitcoinj.core.Transaction;
+
+import lombok.Getter;
+
+@Getter
+public final class BsqTransferModel {
+
+ private final LegacyAddress receiverAddress;
+ private final Coin receiverAmount;
+ private final Transaction preparedSendTx;
+ private final Transaction txWithBtcFee;
+ private final Transaction signedTx;
+ private final Coin miningFee;
+ private final int txSize;
+ private final TxType txType;
+
+ public BsqTransferModel(LegacyAddress receiverAddress,
+ Coin receiverAmount,
+ Transaction preparedSendTx,
+ Transaction txWithBtcFee,
+ Transaction signedTx) {
+ this.receiverAddress = receiverAddress;
+ this.receiverAmount = receiverAmount;
+ this.preparedSendTx = preparedSendTx;
+ this.txWithBtcFee = txWithBtcFee;
+ this.signedTx = signedTx;
+ this.miningFee = signedTx.getFee();
+ this.txSize = signedTx.bitcoinSerialize().length;
+ this.txType = TxType.TRANSFER_BSQ;
+ }
+
+ public String getReceiverAddressAsString() {
+ return receiverAddress.toString();
+ }
+
+ public double getMiningFeeInSatoshisPerByte() {
+ return CoinUtil.getFeePerVbyte(miningFee, txSize);
+ }
+
+ public double getTxSizeInKb() {
+ return txSize / 1000d;
+ }
+
+ public String toShortString() {
+ return "{" + "\n" +
+ " receiverAddress='" + getReceiverAddressAsString() + '\'' + "\n" +
+ ", receiverAmount=" + receiverAmount + "\n" +
+ ", txWithBtcFee.txId=" + txWithBtcFee.getTxId() + "\n" +
+ ", miningFee=" + miningFee + "\n" +
+ ", miningFeeInSatoshisPerByte=" + getMiningFeeInSatoshisPerByte() + "\n" +
+ ", txSizeInKb=" + getTxSizeInKb() + "\n" +
+ '}';
+ }
+
+ @Override
+ public String toString() {
+ return "BsqTransferModel{" + "\n" +
+ " receiverAddress='" + getReceiverAddressAsString() + '\'' + "\n" +
+ ", receiverAmount=" + receiverAmount + "\n" +
+ ", preparedSendTx=" + preparedSendTx + "\n" +
+ ", txWithBtcFee=" + txWithBtcFee + "\n" +
+ ", signedTx=" + signedTx + "\n" +
+ ", miningFee=" + miningFee + "\n" +
+ ", miningFeeInSatoshisPerByte=" + getMiningFeeInSatoshisPerByte() + "\n" +
+ ", txSize=" + txSize + "\n" +
+ ", txSizeInKb=" + getTxSizeInKb() + "\n" +
+ ", txType=" + txType + "\n" +
+ '}';
+ }
+}
diff --git a/core/src/main/java/bisq/core/btc/wallet/BsqTransferService.java b/core/src/main/java/bisq/core/btc/wallet/BsqTransferService.java
new file mode 100644
index 00000000000..b6cc83e8c77
--- /dev/null
+++ b/core/src/main/java/bisq/core/btc/wallet/BsqTransferService.java
@@ -0,0 +1,59 @@
+package bisq.core.btc.wallet;
+
+import bisq.core.btc.exceptions.BsqChangeBelowDustException;
+import bisq.core.btc.exceptions.TransactionVerificationException;
+import bisq.core.btc.exceptions.WalletException;
+import bisq.core.btc.model.BsqTransferModel;
+
+import org.bitcoinj.core.Coin;
+import org.bitcoinj.core.InsufficientMoneyException;
+import org.bitcoinj.core.LegacyAddress;
+import org.bitcoinj.core.Transaction;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Singleton
+public class BsqTransferService {
+
+ private final WalletsManager walletsManager;
+ private final BsqWalletService bsqWalletService;
+ private final BtcWalletService btcWalletService;
+
+ @Inject
+ public BsqTransferService(WalletsManager walletsManager,
+ BsqWalletService bsqWalletService,
+ BtcWalletService btcWalletService) {
+ this.walletsManager = walletsManager;
+ this.bsqWalletService = bsqWalletService;
+ this.btcWalletService = btcWalletService;
+ }
+
+ public BsqTransferModel getBsqTransferModel(LegacyAddress address,
+ Coin receiverAmount)
+ throws TransactionVerificationException,
+ WalletException,
+ BsqChangeBelowDustException,
+ InsufficientMoneyException {
+
+ Transaction preparedSendTx = bsqWalletService.getPreparedSendBsqTx(address.toString(), receiverAmount);
+ Transaction txWithBtcFee = btcWalletService.completePreparedSendBsqTx(preparedSendTx, true);
+ Transaction signedTx = bsqWalletService.signTx(txWithBtcFee);
+
+ return new BsqTransferModel(address,
+ receiverAmount,
+ preparedSendTx,
+ txWithBtcFee,
+ signedTx);
+ }
+
+ public void sendFunds(BsqTransferModel bsqTransferModel, TxBroadcaster.Callback callback) {
+ log.info("Publishing BSQ transfer {}", bsqTransferModel.toShortString());
+ walletsManager.publishAndCommitBsqTx(bsqTransferModel.getTxWithBtcFee(),
+ bsqTransferModel.getTxType(),
+ callback);
+ }
+}
diff --git a/core/src/main/java/bisq/core/offer/OfferUtil.java b/core/src/main/java/bisq/core/offer/OfferUtil.java
index e85881c2112..50fd938d6e9 100644
--- a/core/src/main/java/bisq/core/offer/OfferUtil.java
+++ b/core/src/main/java/bisq/core/offer/OfferUtil.java
@@ -49,6 +49,7 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
+import java.util.function.Predicate;
import lombok.extern.slf4j.Slf4j;
@@ -63,6 +64,7 @@
import static bisq.core.offer.OfferPayload.*;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
+import static java.lang.String.format;
/**
* This class holds utility methods for creating, editing and taking an Offer.
@@ -79,6 +81,9 @@ public class OfferUtil {
private final P2PService p2PService;
private final ReferralIdService referralIdService;
+ private final Predicate isValidFeePaymentCurrencyCode = (c) ->
+ c.equalsIgnoreCase("BSQ") || c.equalsIgnoreCase("BTC");
+
@Inject
public OfferUtil(AccountAgeWitnessService accountAgeWitnessService,
BsqWalletService bsqWalletService,
@@ -96,6 +101,19 @@ public OfferUtil(AccountAgeWitnessService accountAgeWitnessService,
this.referralIdService = referralIdService;
}
+ public void maybeSetFeePaymentCurrencyPreference(String feeCurrencyCode) {
+ if (!feeCurrencyCode.isEmpty()) {
+ if (!isValidFeePaymentCurrencyCode.test(feeCurrencyCode))
+ throw new IllegalStateException(format("%s cannot be used to pay trade fees",
+ feeCurrencyCode.toUpperCase()));
+
+ if (feeCurrencyCode.equalsIgnoreCase("BSQ") && preferences.isPayFeeInBtc())
+ preferences.setPayFeeInBtc(false);
+ else if (feeCurrencyCode.equalsIgnoreCase("BTC") && !preferences.isPayFeeInBtc())
+ preferences.setPayFeeInBtc(true);
+ }
+ }
+
/**
* Given the direction, is this a BUY?
*
diff --git a/core/src/main/java/bisq/core/payment/PaymentAccount.java b/core/src/main/java/bisq/core/payment/PaymentAccount.java
index b38649ef942..b61b86d482b 100644
--- a/core/src/main/java/bisq/core/payment/PaymentAccount.java
+++ b/core/src/main/java/bisq/core/payment/PaymentAccount.java
@@ -173,10 +173,18 @@ public String getOwnerId() {
return paymentAccountPayload.getOwnerId();
}
+ public boolean isCountryBasedPaymentAccount() {
+ return this instanceof CountryBasedPaymentAccount;
+ }
+
public boolean isHalCashAccount() {
return this instanceof HalCashAccount;
}
+ public boolean isMoneyGramAccount() {
+ return this instanceof MoneyGramAccount;
+ }
+
/**
* Return an Optional of the trade currency for this payment account, or
* Optional.empty() if none is found. If this payment account has a selected
diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java
index f03155d2f8d..1efed108ca6 100644
--- a/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java
+++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java
@@ -100,6 +100,7 @@ public void createOffer(CreateOfferRequest req,
req.getMinAmount(),
req.getBuyerSecurityDeposit(),
req.getPaymentAccountId(),
+ req.getMakerFeeCurrencyCode(),
offer -> {
// This result handling consumer's accept operation will return
// the new offer to the gRPC client after async placement is done.
diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcPaymentAccountsService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcPaymentAccountsService.java
index 91060cbc829..0438d33655d 100644
--- a/daemon/src/main/java/bisq/daemon/grpc/GrpcPaymentAccountsService.java
+++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcPaymentAccountsService.java
@@ -19,13 +19,20 @@
import bisq.core.api.CoreApi;
import bisq.core.payment.PaymentAccount;
+import bisq.core.payment.payload.PaymentMethod;
import bisq.proto.grpc.CreatePaymentAccountReply;
import bisq.proto.grpc.CreatePaymentAccountRequest;
+import bisq.proto.grpc.GetPaymentAccountFormReply;
+import bisq.proto.grpc.GetPaymentAccountFormRequest;
import bisq.proto.grpc.GetPaymentAccountsReply;
import bisq.proto.grpc.GetPaymentAccountsRequest;
+import bisq.proto.grpc.GetPaymentMethodsReply;
+import bisq.proto.grpc.GetPaymentMethodsRequest;
import bisq.proto.grpc.PaymentAccountsGrpc;
+import io.grpc.Status;
+import io.grpc.StatusRuntimeException;
import io.grpc.stub.StreamObserver;
import javax.inject.Inject;
@@ -45,24 +52,86 @@ public GrpcPaymentAccountsService(CoreApi coreApi) {
@Override
public void createPaymentAccount(CreatePaymentAccountRequest req,
StreamObserver responseObserver) {
- coreApi.createPaymentAccount(req.getPaymentMethodId(),
- req.getAccountName(),
- req.getAccountNumber(),
- req.getCurrencyCode());
- var reply = CreatePaymentAccountReply.newBuilder().build();
- responseObserver.onNext(reply);
- responseObserver.onCompleted();
+ try {
+ PaymentAccount paymentAccount = coreApi.createPaymentAccount(req.getPaymentAccountForm());
+ var reply = CreatePaymentAccountReply.newBuilder()
+ .setPaymentAccount(paymentAccount.toProtoMessage())
+ .build();
+ responseObserver.onNext(reply);
+ responseObserver.onCompleted();
+ } catch (IllegalArgumentException cause) {
+ var ex = new StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription(cause.getMessage()));
+ responseObserver.onError(ex);
+ throw ex;
+ } catch (IllegalStateException cause) {
+ var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage()));
+ responseObserver.onError(ex);
+ throw ex;
+ }
}
@Override
public void getPaymentAccounts(GetPaymentAccountsRequest req,
StreamObserver responseObserver) {
- var paymentAccounts = coreApi.getPaymentAccounts().stream()
- .map(PaymentAccount::toProtoMessage)
- .collect(Collectors.toList());
- var reply = GetPaymentAccountsReply.newBuilder()
- .addAllPaymentAccounts(paymentAccounts).build();
- responseObserver.onNext(reply);
- responseObserver.onCompleted();
+ try {
+ var paymentAccounts = coreApi.getPaymentAccounts().stream()
+ .map(PaymentAccount::toProtoMessage)
+ .collect(Collectors.toList());
+ var reply = GetPaymentAccountsReply.newBuilder()
+ .addAllPaymentAccounts(paymentAccounts).build();
+ responseObserver.onNext(reply);
+ responseObserver.onCompleted();
+ } catch (IllegalArgumentException cause) {
+ var ex = new StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription(cause.getMessage()));
+ responseObserver.onError(ex);
+ throw ex;
+ } catch (IllegalStateException cause) {
+ var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage()));
+ responseObserver.onError(ex);
+ throw ex;
+ }
+ }
+
+ @Override
+ public void getPaymentMethods(GetPaymentMethodsRequest req,
+ StreamObserver responseObserver) {
+ try {
+ var paymentMethods = coreApi.getFiatPaymentMethods().stream()
+ .map(PaymentMethod::toProtoMessage)
+ .collect(Collectors.toList());
+ var reply = GetPaymentMethodsReply.newBuilder()
+ .addAllPaymentMethods(paymentMethods).build();
+ responseObserver.onNext(reply);
+ responseObserver.onCompleted();
+ } catch (IllegalArgumentException cause) {
+ var ex = new StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription(cause.getMessage()));
+ responseObserver.onError(ex);
+ throw ex;
+ } catch (IllegalStateException cause) {
+ var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage()));
+ responseObserver.onError(ex);
+ throw ex;
+ }
+ }
+
+ @Override
+ public void getPaymentAccountForm(GetPaymentAccountFormRequest req,
+ StreamObserver responseObserver) {
+ try {
+ var paymentAccountFormJson = coreApi.getPaymentAccountForm(req.getPaymentMethodId());
+ var reply = GetPaymentAccountFormReply.newBuilder()
+ .setPaymentAccountFormJson(paymentAccountFormJson)
+ .build();
+ responseObserver.onNext(reply);
+ responseObserver.onCompleted();
+ } catch (IllegalArgumentException cause) {
+ var ex = new StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription(cause.getMessage()));
+ responseObserver.onError(ex);
+ throw ex;
+ } catch (IllegalStateException cause) {
+ var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage()));
+ responseObserver.onError(ex);
+ throw ex;
+ }
}
}
diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcTradesService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcTradesService.java
index 74cd04ead93..449859a9f63 100644
--- a/daemon/src/main/java/bisq/daemon/grpc/GrpcTradesService.java
+++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcTradesService.java
@@ -79,6 +79,7 @@ public void takeOffer(TakeOfferRequest req,
try {
coreApi.takeOffer(req.getOfferId(),
req.getPaymentAccountId(),
+ req.getTakerFeeCurrencyCode(),
trade -> {
TradeInfo tradeInfo = toTradeInfo(trade);
var reply = TakeOfferReply.newBuilder()
diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcWalletsService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcWalletsService.java
index 1b5cb42e4cc..110e4b5cc65 100644
--- a/daemon/src/main/java/bisq/daemon/grpc/GrpcWalletsService.java
+++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcWalletsService.java
@@ -19,17 +19,23 @@
import bisq.core.api.CoreApi;
import bisq.core.api.model.AddressBalanceInfo;
+import bisq.core.btc.exceptions.TxBroadcastException;
+import bisq.core.btc.wallet.TxBroadcaster;
import bisq.proto.grpc.GetAddressBalanceReply;
import bisq.proto.grpc.GetAddressBalanceRequest;
-import bisq.proto.grpc.GetBalanceReply;
-import bisq.proto.grpc.GetBalanceRequest;
+import bisq.proto.grpc.GetBalancesReply;
+import bisq.proto.grpc.GetBalancesRequest;
import bisq.proto.grpc.GetFundingAddressesReply;
import bisq.proto.grpc.GetFundingAddressesRequest;
+import bisq.proto.grpc.GetUnusedBsqAddressReply;
+import bisq.proto.grpc.GetUnusedBsqAddressRequest;
import bisq.proto.grpc.LockWalletReply;
import bisq.proto.grpc.LockWalletRequest;
import bisq.proto.grpc.RemoveWalletPasswordReply;
import bisq.proto.grpc.RemoveWalletPasswordRequest;
+import bisq.proto.grpc.SendBsqReply;
+import bisq.proto.grpc.SendBsqRequest;
import bisq.proto.grpc.SetWalletPasswordReply;
import bisq.proto.grpc.SetWalletPasswordRequest;
import bisq.proto.grpc.UnlockWalletReply;
@@ -40,11 +46,16 @@
import io.grpc.StatusRuntimeException;
import io.grpc.stub.StreamObserver;
+import org.bitcoinj.core.Transaction;
+
import javax.inject.Inject;
import java.util.List;
import java.util.stream.Collectors;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
class GrpcWalletsService extends WalletsGrpc.WalletsImplBase {
private final CoreApi coreApi;
@@ -54,17 +65,13 @@ public GrpcWalletsService(CoreApi coreApi) {
this.coreApi = coreApi;
}
- // TODO we need to support 3 or 4 balance types: available, reserved, lockedInTrade
- // and maybe total wallet balance (available+reserved). To not duplicate the methods,
- // we should pass an enum type. Enums in proto are a bit cumbersome as they are
- // global so you quickly run into namespace conflicts if not always prefixes which
- // makes it more verbose. In the core code base we move to the strategy to store the
- // enum name and map it. This gives also more flexibility with updates.
@Override
- public void getBalance(GetBalanceRequest req, StreamObserver responseObserver) {
+ public void getBalances(GetBalancesRequest req, StreamObserver responseObserver) {
try {
- long availableBalance = coreApi.getAvailableBalance();
- var reply = GetBalanceReply.newBuilder().setBalance(availableBalance).build();
+ var balances = coreApi.getBalances(req.getCurrencyCode());
+ var reply = GetBalancesReply.newBuilder()
+ .setBalances(balances.toProtoMessage())
+ .build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
} catch (IllegalStateException cause) {
@@ -110,6 +117,52 @@ public void getFundingAddresses(GetFundingAddressesRequest req,
}
}
+ @Override
+ public void getUnusedBsqAddress(GetUnusedBsqAddressRequest req,
+ StreamObserver responseObserver) {
+ try {
+ String address = coreApi.getUnusedBsqAddress();
+ var reply = GetUnusedBsqAddressReply.newBuilder()
+ .setAddress(address)
+ .build();
+ responseObserver.onNext(reply);
+ responseObserver.onCompleted();
+ } catch (IllegalStateException cause) {
+ var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage()));
+ responseObserver.onError(ex);
+ throw ex;
+ }
+ }
+
+ @Override
+ public void sendBsq(SendBsqRequest req,
+ StreamObserver responseObserver) {
+ try {
+ coreApi.sendBsq(req.getAddress(), req.getAmount(), new TxBroadcaster.Callback() {
+ @Override
+ public void onSuccess(Transaction tx) {
+ log.info("Successfully published BSQ tx: id {}, output sum {} sats, fee {} sats, size {} bytes",
+ tx.getTxId().toString(),
+ tx.getOutputSum(),
+ tx.getFee(),
+ tx.getMessageSize());
+ var reply = SendBsqReply.newBuilder().build();
+ responseObserver.onNext(reply);
+ responseObserver.onCompleted();
+ }
+
+ @Override
+ public void onFailure(TxBroadcastException ex) {
+ throw new IllegalStateException(ex);
+ }
+ });
+ } catch (IllegalStateException cause) {
+ var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage()));
+ responseObserver.onError(ex);
+ throw ex;
+ }
+ }
+
@Override
public void setWalletPassword(SetWalletPasswordRequest req,
StreamObserver responseObserver) {
diff --git a/proto/src/main/proto/grpc.proto b/proto/src/main/proto/grpc.proto
index d9e0a3973d8..b20a508ddea 100644
--- a/proto/src/main/proto/grpc.proto
+++ b/proto/src/main/proto/grpc.proto
@@ -81,6 +81,7 @@ message CreateOfferRequest {
uint64 minAmount = 7;
double buyerSecurityDeposit = 8;
string paymentAccountId = 9;
+ string makerFeeCurrencyCode = 10;
}
message CreateOfferReply {
@@ -105,13 +106,14 @@ message OfferInfo {
uint64 volume = 8;
uint64 minVolume = 9;
uint64 buyerSecurityDeposit = 10;
- string paymentAccountId = 11;
- string paymentMethodId = 12;
- string paymentMethodShortName = 13;
- string baseCurrencyCode = 14;
- string counterCurrencyCode = 15;
- uint64 date = 16;
- string state = 17;
+ bool isCurrencyForMakerFeeBtc = 11;
+ string paymentAccountId = 12;
+ string paymentMethodId = 13;
+ string paymentMethodShortName = 14;
+ string baseCurrencyCode = 15;
+ string counterCurrencyCode = 16;
+ uint64 date = 17;
+ string state = 18;
}
///////////////////////////////////////////////////////////////////////////////////////////
@@ -123,17 +125,18 @@ service PaymentAccounts {
}
rpc GetPaymentAccounts (GetPaymentAccountsRequest) returns (GetPaymentAccountsReply) {
}
+ rpc GetPaymentMethods (GetPaymentMethodsRequest) returns (GetPaymentMethodsReply) {
+ }
+ rpc GetPaymentAccountForm (GetPaymentAccountFormRequest) returns (GetPaymentAccountFormReply) {
+ }
}
message CreatePaymentAccountRequest {
- string paymentMethodId = 1;
- string accountName = 2;
- string accountNumber = 3;
- // TODO Support all currencies. Maybe add a repeated and if only one is used its a singletonList.
- string currencyCode = 4;
+ string paymentAccountForm = 1;
}
message CreatePaymentAccountReply {
+ PaymentAccount paymentAccount = 1;
}
message GetPaymentAccountsRequest {
@@ -143,6 +146,21 @@ message GetPaymentAccountsReply {
repeated PaymentAccount paymentAccounts = 1;
}
+message GetPaymentMethodsRequest {
+}
+
+message GetPaymentMethodsReply {
+ repeated PaymentMethod paymentMethods = 1;
+}
+
+message GetPaymentAccountFormRequest {
+ string paymentMethodId = 1;
+}
+
+message GetPaymentAccountFormReply {
+ string paymentAccountFormJson = 1;
+}
+
///////////////////////////////////////////////////////////////////////////////////////////
// Price
///////////////////////////////////////////////////////////////////////////////////////////
@@ -198,6 +216,7 @@ service Trades {
message TakeOfferRequest {
string offerId = 1;
string paymentAccountId = 2;
+ string takerFeeCurrencyCode = 3;
}
message TakeOfferReply {
@@ -273,10 +292,14 @@ message TradeInfo {
///////////////////////////////////////////////////////////////////////////////////////////
service Wallets {
- rpc GetBalance (GetBalanceRequest) returns (GetBalanceReply) {
+ rpc GetBalances (GetBalancesRequest) returns (GetBalancesReply) {
}
rpc GetAddressBalance (GetAddressBalanceRequest) returns (GetAddressBalanceReply) {
}
+ rpc GetUnusedBsqAddress (GetUnusedBsqAddressRequest) returns (GetUnusedBsqAddressReply) {
+ }
+ rpc SendBsq (SendBsqRequest) returns (SendBsqReply) {
+ }
rpc GetFundingAddresses (GetFundingAddressesRequest) returns (GetFundingAddressesReply) {
}
rpc SetWalletPassword (SetWalletPasswordRequest) returns (SetWalletPasswordReply) {
@@ -289,11 +312,12 @@ service Wallets {
}
}
-message GetBalanceRequest {
+message GetBalancesRequest {
+ string currencyCode = 1;
}
-message GetBalanceReply {
- uint64 balance = 1;
+message GetBalancesReply {
+ BalancesInfo balances = 1;
}
message GetAddressBalanceRequest {
@@ -304,6 +328,21 @@ message GetAddressBalanceReply {
AddressBalanceInfo addressBalanceInfo = 1;
}
+message GetUnusedBsqAddressRequest {
+}
+
+message GetUnusedBsqAddressReply {
+ string address = 1;
+}
+
+message SendBsqRequest {
+ string address = 1;
+ double amount = 2;
+}
+
+message SendBsqReply {
+}
+
message GetFundingAddressesRequest {
}
@@ -340,6 +379,30 @@ message UnlockWalletRequest {
message UnlockWalletReply {
}
+message BalancesInfo {
+ // Field names are shortened for readability's sake, i.e.,
+ // balancesInfo.getBtc().getAvailableBalance() is cleaner than
+ // balancesInfo.getBtcBalanceInfo().getAvailableBalance().
+ BsqBalanceInfo bsq = 1;
+ BtcBalanceInfo btc = 2;
+}
+
+message BsqBalanceInfo {
+ uint64 availableConfirmedBalance = 1;
+ uint64 unverifiedBalance = 2;
+ uint64 unconfirmedChangeBalance = 3;
+ uint64 lockedForVotingBalance = 4;
+ uint64 lockupBondsBalance = 5;
+ uint64 unlockingBondsBalance = 6;
+}
+
+message BtcBalanceInfo {
+ uint64 availableBalance = 1;
+ uint64 reservedBalance = 2;
+ uint64 totalAvailableBalance = 3;
+ uint64 lockedBalance = 4;
+}
+
message AddressBalanceInfo {
string address = 1;
int64 balance = 2;