Merge pull request #4858 from ghubstan/21-fee-rate-apis

Add tx fee rate api methods:  gettxfeerate, settxfeerate, unsettxfeerate
This commit is contained in:
sqrrm 2020-12-03 10:41:54 +01:00 committed by GitHub
commit 9b774d1515
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
52 changed files with 4058 additions and 543 deletions

View file

@ -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:

View file

@ -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 ]
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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<PaymentAccount> 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();
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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();
}
}

View file

@ -17,36 +17,60 @@
package bisq.apitest.method;
import bisq.core.api.model.PaymentAccountForm;
import bisq.core.api.model.TxFeeRateInfo;
import bisq.core.proto.CoreProtoResolver;
import bisq.common.util.Utilities;
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.GetTxFeeRateRequest;
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.SetTxFeeRatePreferenceRequest;
import bisq.proto.grpc.SetWalletPasswordRequest;
import bisq.proto.grpc.TakeOfferRequest;
import bisq.proto.grpc.TradeInfo;
import bisq.proto.grpc.UnlockWalletRequest;
import bisq.proto.grpc.UnsetTxFeeRatePreferenceRequest;
import bisq.proto.grpc.WithdrawFundsRequest;
import protobuf.PaymentAccount;
import protobuf.PaymentMethod;
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.nio.charset.StandardCharsets.UTF_8;
import static java.util.Arrays.stream;
import static java.util.Comparator.comparing;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -70,6 +94,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 +128,12 @@ public class MethodTest extends ApiTestCase {
}
// 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 +156,14 @@ public class MethodTest extends ApiTestCase {
return LockWalletRequest.newBuilder().build();
}
protected final GetUnusedBsqAddressRequest createGetUnusedBsqAddressRequest() {
return GetUnusedBsqAddressRequest.newBuilder().build();
}
protected final SendBsqRequest createSendBsqRequest(String address, String amount) {
return SendBsqRequest.newBuilder().setAddress(address).setAmount(amount).build();
}
protected final GetFundingAddressesRequest createGetFundingAddressesRequest() {
return GetFundingAddressesRequest.newBuilder().build();
}
@ -143,8 +180,14 @@ public class MethodTest extends ApiTestCase {
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 +216,21 @@ public class MethodTest extends ApiTestCase {
}
// 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 +243,15 @@ public class MethodTest extends ApiTestCase {
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, String 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 +263,53 @@ public class MethodTest extends ApiTestCase {
.getAddress();
}
protected final CreatePaymentAccountRequest createCreatePerfectMoneyPaymentAccountRequest(
String accountName,
String accountNumber,
String currencyCode) {
return CreatePaymentAccountRequest.newBuilder()
.setPaymentMethodId(PERFECT_MONEY.getId())
.setAccountName(accountName)
.setAccountNumber(accountNumber)
.setCurrencyCode(currencyCode)
.build();
protected final List<PaymentMethod> getPaymentMethods(BisqAppConfig bisqAppConfig) {
var req = GetPaymentMethodsRequest.newBuilder().build();
return grpcStubs(bisqAppConfig).paymentAccountsService.getPaymentMethods(req).getPaymentMethodsList();
}
protected static PaymentAccount getDefaultPerfectDummyPaymentAccount(BisqAppConfig bisqAppConfig) {
var req = GetPaymentAccountsRequest.newBuilder().build();
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, UTF_8)) {
out.println(jsonString);
} catch (IOException ex) {
fail("Could not create tmp payment account form.", ex);
}
return jsonFile;
}
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<PaymentAccount> 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;
}
@ -267,6 +358,27 @@ public class MethodTest extends ApiTestCase {
var req = createWithdrawFundsRequest(tradeId, address);
grpcStubs(bisqAppConfig).tradesService.withdrawFunds(req);
}
protected final TxFeeRateInfo getTxFeeRate(BisqAppConfig bisqAppConfig) {
var req = GetTxFeeRateRequest.newBuilder().build();
return TxFeeRateInfo.fromProto(
grpcStubs(bisqAppConfig).walletsService.getTxFeeRate(req).getTxFeeRateInfo());
}
protected final TxFeeRateInfo setTxFeeRate(BisqAppConfig bisqAppConfig, long feeRate) {
var req = SetTxFeeRatePreferenceRequest.newBuilder()
.setTxFeeRatePreference(feeRate)
.build();
return TxFeeRateInfo.fromProto(
grpcStubs(bisqAppConfig).walletsService.setTxFeeRatePreference(req).getTxFeeRateInfo());
}
protected final TxFeeRateInfo unsetTxFeeRate(BisqAppConfig bisqAppConfig) {
var req = UnsetTxFeeRatePreferenceRequest.newBuilder().build();
return TxFeeRateInfo.fromProto(
grpcStubs(bisqAppConfig).walletsService.unsetTxFeeRatePreference(req).getTxFeeRateInfo());
}
// Static conveniences for test methods and test case fixture setups.
protected static RegisterDisputeAgentRequest createRegisterDisputeAgentRequest(String disputeAgentType) {
@ -275,10 +387,18 @@ public class MethodTest extends ApiTestCase {
.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));
}
protected static String encodeToHex(String s) {
return Utilities.bytesAsHexString(s.getBytes(UTF_8));
}
private bisq.core.payment.PaymentAccount fromProto(PaymentAccount proto) {
return bisq.core.payment.PaymentAccount.fromProto(proto, CORE_PROTO_RESOLVER);
}
}

View file

@ -73,22 +73,35 @@ public abstract class AbstractOfferTest extends MethodTest {
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 @@ public abstract class AbstractOfferTest extends MethodTest {
.setMarketPriceMargin(0.00)
.setPrice("0")
.setBuyerSecurityDeposit(getDefaultBuyerSecurityDepositAsPercent())
.setMakerFeeCurrencyCode(makerFeeCurrencyCode)
.build();
return grpcStubs.offersService.createOffer(req).getOffer();
}

View file

@ -54,6 +54,7 @@ public class CancelOfferTest extends AbstractOfferTest {
.setMarketPriceMargin(0.00)
.setPrice("0")
.setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent())
.setMakerFeeCurrencyCode("bsq")
.build();
// Create some offers.

View file

@ -38,6 +38,8 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals;
@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 class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
.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 class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
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 class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId());
assertEquals("BTC", newOffer.getBaseCurrencyCode());
assertEquals("AUD", newOffer.getCounterCurrencyCode());
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
}
@Test
@ -91,6 +96,7 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
.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 class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
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 class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId());
assertEquals("BTC", newOffer.getBaseCurrencyCode());
assertEquals("USD", newOffer.getCounterCurrencyCode());
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
}
@Test
@ -131,6 +139,7 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
.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 class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
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 class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId());
assertEquals("BTC", newOffer.getBaseCurrencyCode());
assertEquals("EUR", newOffer.getCounterCurrencyCode());
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
}
}

View file

@ -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 class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
.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 class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
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 class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId());
assertEquals("BTC", newOffer.getBaseCurrencyCode());
assertEquals("USD", newOffer.getCounterCurrencyCode());
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
}
@ -105,6 +110,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
.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 class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
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 class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId());
assertEquals("BTC", newOffer.getBaseCurrencyCode());
assertEquals("NZD", newOffer.getCounterCurrencyCode());
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
}
@ -146,6 +154,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
.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 class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
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 class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId());
assertEquals("BTC", newOffer.getBaseCurrencyCode());
assertEquals("GBP", newOffer.getCounterCurrencyCode());
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
}
@ -188,6 +199,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
.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 class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
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 class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId());
assertEquals("BTC", newOffer.getBaseCurrencyCode());
assertEquals("BRL", newOffer.getCounterCurrencyCode());
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
}

View file

@ -52,6 +52,7 @@ public class ValidateCreateOfferTest extends AbstractOfferTest {
.setMarketPriceMargin(0.00)
.setPrice("10000.0000")
.setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent())
.setMakerFeeCurrencyCode("bsq")
.build();
@SuppressWarnings("ResultOfMethodCallIgnored")
Throwable exception = assertThrows(StatusRuntimeException.class, () ->

View file

@ -0,0 +1,204 @@
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.ArrayList;
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_JSON_COMMENTS = "_COMMENTS_";
static final List<String> PROPERTY_VALUE_JSON_COMMENTS = new ArrayList<>() {{
add("Do not manually edit the paymentMethodId field.");
add("Edit the salt field only if you are recreating a payment"
+ " account on a new installation and wish to preserve the account age.");
}};
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_SALT = "salt";
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<String, Object> 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);
log.debug("{} Empty form saved to {}",
testName(testInfo),
PAYMENT_ACCOUNT_FORM.getClickableURI(emptyForm));
emptyForm.deleteOnExit();
return emptyForm;
}
protected final void verifyEmptyForm(File jsonForm, String paymentMethodId, String... fields) {
@SuppressWarnings("unchecked")
Map<String, Object> emptyForm = (Map<String, Object>) GSON.fromJson(
PAYMENT_ACCOUNT_FORM.toJsonString(jsonForm),
Object.class);
assertNotNull(emptyForm);
assertEquals(PROPERTY_VALUE_JSON_COMMENTS, emptyForm.get(PROPERTY_NAME_JSON_COMMENTS));
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<TradeCurrency> 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<protobuf.PaymentAccount> 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);
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_JSON_COMMENTS);
writer.beginArray();
for (String s : PROPERTY_VALUE_JSON_COMMENTS) {
writer.value(s);
}
writer.endArray();
for (Map.Entry<String, Object> 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;
}
}

View file

@ -0,0 +1,853 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
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");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Advanced Cash Acct Salt"));
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());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
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");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
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());
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 Acct");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAY_ID, "123 456 789");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_ACCOUNT_NAME, "Credit Union Australia");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Australia Pay ID Acct Salt"));
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());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
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...");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
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());
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");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Banco do Brasil Acct Salt"));
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());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
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");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
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());
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 Acct");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL_OR_MOBILE_NR, "jane@doe.com");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "Jane Doe");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Zelle Acct Salt"));
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());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
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");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
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());
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");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Faster Payments Acct Salt"));
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());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
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");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
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());
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");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Interac Transfer Acct Salt"));
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());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
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");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
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());
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");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Money Beam Acct Salt"));
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());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
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");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
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());
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");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Perfect Money Acct Salt"));
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());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
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");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
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());
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");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Prompt Pay Acct Salt"));
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());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
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");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
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());
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");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Same Bank Acct Salt"));
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());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
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");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
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());
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");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Conta Sepa Salt"));
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());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
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");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
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());
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 Acct");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_MOBILE_NR, "+46 7 6060 0101");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "Swish Acct Holder");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Swish Acct Salt"));
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());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
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");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
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());
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");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Uphold Acct Salt"));
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());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
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");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
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());
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");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored WeChat Pay Acct Salt"));
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());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
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");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
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);
log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
}
@AfterAll
public static void tearDown() {
tearDownScaffold();
}
}

View file

@ -0,0 +1,55 @@
package bisq.apitest.method.payment;
import protobuf.PaymentMethod;
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.assertTrue;
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<String> paymentMethodIds = getPaymentMethods(alicedaemon)
.stream()
.map(PaymentMethod::getId)
.collect(Collectors.toList());
assertTrue(paymentMethodIds.size() >= 20);
}
@AfterAll
public static void tearDown() {
tearDownScaffold();
}
}

View file

@ -27,13 +27,25 @@ public class AbstractTradeTest extends AbstractOfferTest {
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) {

View file

@ -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.FIAT_SENT;
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 class TakeBuyBTCOfferTest extends AbstractTradeTest {
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 class TakeBuyBTCOfferTest extends AbstractTradeTest {
.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()));
}
}

View file

@ -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.Phase.*;
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 class TakeSellBTCOfferTest extends AbstractTradeTest {
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 class TakeSellBTCOfferTest extends AbstractTradeTest {
.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()));
}
}

View file

@ -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 String 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.debug("{} -> Alice's BSQ Initial Balances -> \n{}",
testName(testInfo),
formatBsqBalanceInfoTbl(alicesBsqBalances));
verifyBsqBalances(ALICES_INITIAL_BSQ_BALANCES, alicesBsqBalances);
BsqBalanceInfo bobsBsqBalances = getBsqBalances(bobdaemon);
log.debug("{} -> 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.debug("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.debug("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.debug("{} -> Bob's BSQ Balances After {} {} BSQ-> \n{}",
testName(testInfo),
senderApp.equals(bobdaemon) ? "Sending" : "Receiving",
SEND_BSQ_AMOUNT,
formatBsqBalanceInfoTbl(bobsBsqBalances));
log.debug("{} -> 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);
}
}

View file

@ -0,0 +1,76 @@
package bisq.apitest.method.wallet;
import bisq.core.api.model.TxFeeRateInfo;
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.seednode;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import bisq.apitest.method.MethodTest;
@Disabled
@Slf4j
@TestMethodOrder(OrderAnnotation.class)
public class BtcTxFeeRateTest extends MethodTest {
@BeforeAll
public static void setUp() {
startSupportingApps(false,
true,
bitcoind,
seednode,
alicedaemon);
}
@Test
@Order(1)
public void testGetTxFeeRate(final TestInfo testInfo) {
TxFeeRateInfo txFeeRateInfo = getTxFeeRate(alicedaemon);
log.debug("{} -> Fee rate with no preference: {}", testName(testInfo), txFeeRateInfo);
assertFalse(txFeeRateInfo.isUseCustomTxFeeRate());
assertTrue(txFeeRateInfo.getFeeServiceRate() > 0);
}
@Test
@Order(2)
public void testSetTxFeeRate(final TestInfo testInfo) {
TxFeeRateInfo txFeeRateInfo = setTxFeeRate(alicedaemon, 10);
log.debug("{} -> Fee rates with custom preference: {}", testName(testInfo), txFeeRateInfo);
assertTrue(txFeeRateInfo.isUseCustomTxFeeRate());
assertEquals(10, txFeeRateInfo.getCustomTxFeeRate());
assertTrue(txFeeRateInfo.getFeeServiceRate() > 0);
}
@Test
@Order(3)
public void testUnsetTxFeeRate(final TestInfo testInfo) {
TxFeeRateInfo txFeeRateInfo = unsetTxFeeRate(alicedaemon);
log.debug("{} -> Fee rate with no preference: {}", testName(testInfo), txFeeRateInfo);
assertFalse(txFeeRateInfo.isUseCustomTxFeeRate());
assertTrue(txFeeRateInfo.getFeeServiceRate() > 0);
}
@AfterAll
public static void tearDown() {
tearDownScaffold();
}
}

View file

@ -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());
}
}

View file

@ -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.assertThrows;
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 class WalletProtectionTest extends MethodTest {
@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 class WalletProtectionTest extends MethodTest {
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 class WalletProtectionTest extends MethodTest {
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 class WalletProtectionTest extends MethodTest {
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 class WalletProtectionTest extends MethodTest {
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 class WalletProtectionTest extends MethodTest {
"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 class WalletProtectionTest extends MethodTest {
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

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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();
}
}

View file

@ -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();
}
}

View file

@ -34,7 +34,6 @@ import static org.junit.jupiter.api.Assertions.fail;
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 class StartupTest extends MethodTest {
test.testRegisterRefundAgent();
}
@Test
@Order(3)
public void testCreatePaymentAccount() {
CreatePaymentAccountTest test = new CreatePaymentAccountTest();
test.testCreatePerfectMoneyUSDPaymentAccount();
}
@AfterAll
public static void tearDown() {
tearDownScaffold();

View file

@ -24,60 +24,65 @@ 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.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.BtcTxFeeRateTest;
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.
// Batching all wallet tests in this test case reduces scaffold setup
// time. Here, we create a method WalletProtectionTest instance and run each
// test in declared order.
@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");
bitcoinCli.generateBlocks(1);
sleep(1500);
balance = getBalance(alicedaemon);
assertEquals(1250000000L, balance); // new balance is 12.5 btc
btcWalletTest.testInitialBtcBalances(testInfo);
btcWalletTest.testFundAlicesBtcWallet(testInfo);
}
@Test
@Order(2)
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
// test in declared order.
public void testBsqWalletFunding(final TestInfo testInfo) {
BsqWalletTest bsqWalletTest = new BsqWalletTest();
bsqWalletTest.testGetUnusedBsqAddress();
bsqWalletTest.testInitialBsqBalances(testInfo);
bsqWalletTest.testSendBsqAndCheckBalancesBeforeGeneratingBtcBlock(testInfo);
bsqWalletTest.testBalancesAfterSendingBsqAndGeneratingBtcBlock(testInfo);
}
@Test
@Order(3)
public void testWalletProtection() {
WalletProtectionTest walletProtectionTest = new WalletProtectionTest();
walletProtectionTest.testSetWalletPassword();
@ -92,6 +97,16 @@ public class WalletTest extends MethodTest {
walletProtectionTest.testRemoveNewWalletPassword();
}
@Test
@Order(4)
public void testTxFeeRateMethods(final TestInfo testInfo) {
BtcTxFeeRateTest test = new BtcTxFeeRateTest();
test.testGetTxFeeRate(testInfo);
test.testSetTxFeeRate(testInfo);
test.testUnsetTxFeeRate(testInfo);
}
@AfterAll
public static void tearDown() {
tearDownScaffold();

View file

@ -23,42 +23,58 @@ import bisq.proto.grpc.ConfirmPaymentStartedRequest;
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.GetTxFeeRateRequest;
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.SetTxFeeRatePreferenceRequest;
import bisq.proto.grpc.SetWalletPasswordRequest;
import bisq.proto.grpc.TakeOfferRequest;
import bisq.proto.grpc.UnlockWalletRequest;
import bisq.proto.grpc.UnsetTxFeeRatePreferenceRequest;
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.formatTxFeeRateInfo;
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 +100,19 @@ public class CliMain {
confirmpaymentreceived,
keepfunds,
withdrawfunds,
getpaymentmethods,
getpaymentacctform,
createpaymentacct,
getpaymentaccts,
getversion,
getbalance,
getaddressbalance,
getfundingaddresses,
getunusedbsqaddress,
sendbsq,
gettxfeerate,
settxfeerate,
unsettxfeerate,
lockwallet,
unlockwallet,
removewalletpassword,
@ -183,10 +206,25 @@ public class CliMain {
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 +243,73 @@ public class CliMain {
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");
var amount = nonOptionArgs.get(2);
try {
Double.parseDouble(amount);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(format("'%s' is not a number", amount));
}
var request = SendBsqRequest.newBuilder()
.setAddress(address)
.setAmount(amount)
.build();
walletsService.sendBsq(request);
out.printf("%s BSQ sent to %s%n", amount, address);
return;
}
case gettxfeerate: {
var request = GetTxFeeRateRequest.newBuilder().build();
var reply = walletsService.getTxFeeRate(request);
out.println(formatTxFeeRateInfo(reply.getTxFeeRateInfo()));
return;
}
case settxfeerate: {
if (nonOptionArgs.size() < 2)
throw new IllegalArgumentException("no tx fee rate specified");
long txFeeRate;
try {
txFeeRate = Long.parseLong(nonOptionArgs.get(2));
} catch (NumberFormatException e) {
throw new IllegalArgumentException(format("'%s' is not a number", nonOptionArgs.get(2)));
}
var request = SetTxFeeRatePreferenceRequest.newBuilder()
.setTxFeeRatePreference(txFeeRate)
.build();
var reply = walletsService.setTxFeeRatePreference(request);
out.println(formatTxFeeRateInfo(reply.getTxFeeRateInfo()));
return;
}
case unsettxfeerate: {
var request = UnsetTxFeeRatePreferenceRequest.newBuilder().build();
var reply = walletsService.unsetTxFeeRatePreference(request);
out.println(formatTxFeeRateInfo(reply.getTxFeeRateInfo()));
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 +323,11 @@ public class CliMain {
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 +339,7 @@ public class CliMain {
.setMarketPriceMargin(marketPriceMargin.doubleValue())
.setBuyerSecurityDeposit(securityDeposit.doubleValue())
.setPaymentAccountId(paymentAcctId)
.setMakerFeeCurrencyCode(makerFeeCurrencyCode)
.build();
var reply = offersService.createOffer(request);
out.println(formatOfferTable(singletonList(reply.getOffer()), currencyCode));
@ -278,26 +383,40 @@ public class CliMain {
.setCurrencyCode(currencyCode)
.build();
var reply = offersService.getOffers(request);
out.println(formatOfferTable(reply.getOffersList(), currencyCode));
List<OfferInfo> 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());
out.printf("trade %s successfully taken%n", reply.getTrade().getTradeId());
return;
}
case gettrade: {
// TODO make short-id a valid argument
if (nonOptionArgs.size() < 2)
throw new IllegalArgumentException("incorrect parameter count, expecting trade id, [,showcontract = true|false]");
throw new IllegalArgumentException("incorrect parameter count, "
+ " expecting trade id [,showcontract = true|false]");
var tradeId = nonOptionArgs.get(1);
var showContract = false;
@ -323,7 +442,7 @@ public class CliMain {
.setTradeId(tradeId)
.build();
tradesService.confirmPaymentStarted(request);
out.printf("trade '%s' payment started message sent", tradeId);
out.printf("trade %s payment started message sent%n", tradeId);
return;
}
case confirmpaymentreceived: {
@ -335,7 +454,7 @@ public class CliMain {
.setTradeId(tradeId)
.build();
tradesService.confirmPaymentReceived(request);
out.printf("trade '%s' payment received message sent", tradeId);
out.printf("trade %s payment received message sent%n", tradeId);
return;
}
case keepfunds: {
@ -347,12 +466,13 @@ public class CliMain {
.setTradeId(tradeId)
.build();
tradesService.keepFunds(request);
out.printf("funds from trade '%s' saved in bisq wallet", tradeId);
out.printf("funds from trade %s saved in bisq wallet%n", tradeId);
return;
}
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);
@ -361,33 +481,70 @@ public class CliMain {
.setAddress(address)
.build();
tradesService.withdrawFunds(request);
out.printf("funds from trade '%s' sent to btc address '%s'", tradeId, address);
out.printf("funds from trade %s sent to btc address %s%n", 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<PaymentAccount> paymentAccounts = reply.getPaymentAccountsList();
if (paymentAccounts.size() > 0)
out.println(formatPaymentAcctTbl(paymentAccounts));
else
out.println("no payment accounts are saved");
return;
}
case lockwallet: {
@ -470,6 +627,26 @@ public class CliMain {
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 +659,30 @@ public class CliMain {
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, "gettxfeerate", "", "Get current tx fee rate in sats/byte");
stream.format(rowFormat, "settxfeerate", "satoshis (per byte)", "Set custom tx fee rate in sats/byte");
stream.format(rowFormat, "unsettxfeerate", "", "Unset custom tx fee rate");
stream.format(rowFormat, "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",

View file

@ -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";

View file

@ -17,6 +17,8 @@
package bisq.cli;
import bisq.proto.grpc.TxFeeRateInfo;
import com.google.common.annotations.VisibleForTesting;
import java.text.DecimalFormat;
@ -36,13 +38,31 @@ public class CurrencyFormat {
static final BigDecimal SATOSHI_DIVISOR = new BigDecimal(100000000);
static final DecimalFormat BTC_FORMAT = new DecimalFormat("###,##0.00000000");
static final DecimalFormat BTC_TX_FEE_FORMAT = new DecimalFormat("###,##0.00");
static final BigDecimal BSQ_SATOSHI_DIVISOR = new BigDecimal(100);
static final DecimalFormat BSQ_FORMAT = new DecimalFormat("###,###,###,##0.00");
@VisibleForTesting
@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));
}
public static String formatTxFeeRateInfo(TxFeeRateInfo txFeeRateInfo) {
if (txFeeRateInfo.getUseCustomTxFeeRate())
return format("custom tx fee rate: %s sats/byte, network rate: %s sats/byte",
formatFeeSatoshis(txFeeRateInfo.getCustomTxFeeRate()),
formatFeeSatoshis(txFeeRateInfo.getFeeServiceRate()));
else
return format("tx fee rate: %s sats/byte",
formatFeeSatoshis(txFeeRateInfo.getFeeServiceRate()));
}
static String formatAmountRange(long minAmount, long amount) {
return minAmount != amount
? formatSatoshis(minAmount) + " - " + formatSatoshis(amount)
@ -78,4 +98,9 @@ public class CurrencyFormat {
throw new IllegalArgumentException(format("'%s' is not a number", btc));
}
}
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
private static String formatFeeSatoshis(long sats) {
return BTC_TX_FEE_FORMAT.format(BigDecimal.valueOf(sats).divide(SATOSHI_DIVISOR));
}
}

View file

@ -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.TimeZone;
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> 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> 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 @@ class TableFormat {
.collect(Collectors.joining("\n"));
}
static String formatOfferTable(List<OfferInfo> 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> 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

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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<Field> fields,
Class<?> clazz,
Predicate<Field> 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<Method> of the setter method for a field in the class hierarchy,
* or Optional.empty() if it does not exist.
*/
public static Optional<Method> getSetterMethodForFieldInClassHierarchy(Field field,
Class<?> clazz) {
Optional<Method> 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 "";
}
}

View file

@ -18,15 +18,20 @@
package bisq.core.api;
import bisq.core.api.model.AddressBalanceInfo;
import bisq.core.api.model.BalancesInfo;
import bisq.core.api.model.TxFeeRateInfo;
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;
import bisq.common.app.Version;
import bisq.common.handlers.ResultHandler;
import org.bitcoinj.core.Coin;
@ -107,6 +112,7 @@ public class CoreApi {
long minAmountAsLong,
double buyerSecurityDeposit,
String paymentAccountId,
String makerFeeCurrencyCode,
Consumer<Offer> resultHandler) {
coreOffersService.createAndPlaceOffer(currencyCode,
directionAsString,
@ -117,6 +123,7 @@ public class CoreApi {
minAmountAsLong,
buyerSecurityDeposit,
paymentAccountId,
makerFeeCurrencyCode,
resultHandler);
}
@ -150,20 +157,22 @@ public class CoreApi {
// 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<PaymentAccount> getPaymentAccounts() {
return paymentAccountsService.getPaymentAccounts();
}
public List<PaymentMethod> getFiatPaymentMethods() {
return paymentAccountsService.getFiatPaymentMethods();
}
public String getPaymentAccountForm(String paymentMethodId) {
return paymentAccountsService.getPaymentAccountFormAsString(paymentMethodId);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Prices
///////////////////////////////////////////////////////////////////////////////////////////
@ -178,10 +187,12 @@ public class CoreApi {
public void takeOffer(String offerId,
String paymentAccountId,
String takerFeeCurrencyCode,
Consumer<Trade> resultHandler) {
Offer offer = coreOffersService.getOffer(offerId);
coreTradesService.takeOffer(offer,
paymentAccountId,
takerFeeCurrencyCode,
resultHandler);
}
@ -213,8 +224,8 @@ public class CoreApi {
// Wallets
///////////////////////////////////////////////////////////////////////////////////////////
public long getAvailableBalance() {
return walletsService.getAvailableBalance();
public BalancesInfo getBalances(String currencyCode) {
return walletsService.getBalances(currencyCode);
}
public long getAddressBalance(String addressString) {
@ -229,6 +240,31 @@ public class CoreApi {
return walletsService.getFundingAddresses();
}
public String getUnusedBsqAddress() {
return walletsService.getUnusedBsqAddress();
}
public void sendBsq(String address, String amount, TxBroadcaster.Callback callback) {
walletsService.sendBsq(address, amount, callback);
}
public void getTxFeeRate(ResultHandler resultHandler) {
walletsService.getTxFeeRate(resultHandler);
}
public void setTxFeeRatePreference(long txFeeRate,
ResultHandler resultHandler) {
walletsService.setTxFeeRatePreference(txFeeRate, resultHandler);
}
public void unsetTxFeeRatePreference(ResultHandler resultHandler) {
walletsService.unsetTxFeeRatePreference(resultHandler);
}
public TxFeeRateInfo getMostRecentTxFeeRateInfo() {
return walletsService.getMostRecentTxFeeRateInfo();
}
public void setWalletPassword(String password, String newPassword) {
walletsService.setWalletPassword(password, newPassword);
}

View file

@ -22,6 +22,7 @@ import bisq.core.monetary.Price;
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 @@ class CoreOffersService {
long minAmountAsLong,
double buyerSecurityDeposit,
String paymentAccountId,
String makerFeeCurrencyCode,
Consumer<Offer> resultHandler) {
offerUtil.maybeSetFeePaymentCurrencyPreference(makerFeeCurrencyCode);
String upperCaseCurrencyCode = currencyCode.toUpperCase();
String offerId = createOfferService.getRandomOfferId();
Direction direction = Direction.valueOf(directionAsString.toUpperCase());

View file

@ -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<PaymentAccount> getPaymentAccounts() {
return user.getPaymentAccounts();
}
private PaymentAccount getNewPaymentAccount(String paymentMethodId,
String accountName,
String accountNumber,
String currencyCode) {
PaymentAccount paymentAccount = null;
PaymentMethod paymentMethod = getPaymentMethodById(paymentMethodId);
List<PaymentMethod> 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);
}
}

View file

@ -20,6 +20,7 @@ package bisq.core.api;
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 @@ class CoreTradesService {
User user) {
this.coreWalletsService = coreWalletsService;
this.btcWalletService = btcWalletService;
this.offerUtil = offerUtil;
this.closedTradableManager = closedTradableManager;
this.takeOfferModel = takeOfferModel;
this.tradeManager = tradeManager;
@ -77,7 +81,11 @@ class CoreTradesService {
void takeOffer(Offer offer,
String paymentAccountId,
String takerFeeCurrencyCode,
Consumer<Trade> resultHandler) {
offerUtil.maybeSetFeePaymentCurrencyPreference(takerFeeCurrencyCode);
var paymentAccount = user.getPaymentAccount(paymentAccountId);
if (paymentAccount == null)
throw new IllegalArgumentException(format("payment account with id '%s' not found", paymentAccountId));

View file

@ -18,15 +18,34 @@
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.api.model.TxFeeRateInfo;
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.provider.fee.FeeService;
import bisq.core.user.Preferences;
import bisq.core.util.coin.BsqFormatter;
import bisq.common.Timer;
import bisq.common.UserThread;
import bisq.common.handlers.ResultHandler;
import bisq.common.util.Utilities;
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;
@ -35,6 +54,11 @@ import javax.inject.Inject;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import org.bouncycastle.crypto.params.KeyParameter;
@ -47,6 +71,8 @@ import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import static bisq.core.btc.wallet.Restrictions.getMinNonDustOutput;
import static bisq.core.util.ParsingUtils.parseToCoin;
import static java.lang.String.format;
import static java.util.concurrent.TimeUnit.SECONDS;
@ -55,7 +81,12 @@ 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;
private final FeeService feeService;
private final Preferences preferences;
@Nullable
private Timer lockTimer;
@ -63,13 +94,25 @@ class CoreWalletsService {
@Nullable
private KeyParameter tempAesKey;
private final ListeningExecutorService executor = Utilities.getSingleThreadListeningExecutor("CoreWalletsService");
@Inject
public CoreWalletsService(Balances balances,
WalletsManager walletsManager,
BtcWalletService btcWalletService) {
BsqWalletService bsqWalletService,
BsqTransferService bsqTransferService,
BsqFormatter bsqFormatter,
BtcWalletService btcWalletService,
FeeService feeService,
Preferences preferences) {
this.balances = balances;
this.walletsManager = walletsManager;
this.bsqWalletService = bsqWalletService;
this.bsqTransferService = bsqTransferService;
this.bsqFormatter = bsqFormatter;
this.btcWalletService = btcWalletService;
this.feeService = feeService;
this.preferences = preferences;
}
@Nullable
@ -78,15 +121,21 @@ class CoreWalletsService {
return tempAesKey;
}
long getAvailableBalance() {
BalancesInfo getBalances(String currencyCode) {
verifyWalletCurrencyCodeIsValid(currencyCode);
verifyWalletsAreAvailable();
verifyEncryptedWalletIsUnlocked();
var balance = balances.getAvailableBalance().get();
if (balance == null)
if (balances.getAvailableBalance().get() == null)
throw new IllegalStateException("balance is not yet available");
return balance.getValue();
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) {
@ -134,6 +183,75 @@ class CoreWalletsService {
.collect(Collectors.toList());
}
String getUnusedBsqAddress() {
return bsqWalletService.getUnusedBsqAddressAsString();
}
void sendBsq(String address,
String 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);
}
}
void getTxFeeRate(ResultHandler resultHandler) {
try {
@SuppressWarnings({"unchecked", "Convert2MethodRef"})
ListenableFuture<Void> future =
(ListenableFuture<Void>) executor.submit(() -> feeService.requestFees());
Futures.addCallback(future, new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Void ignored) {
resultHandler.handleResult();
}
@Override
public void onFailure(Throwable t) {
log.error("", t);
throw new IllegalStateException("could not request fees from fee service", t);
}
}, MoreExecutors.directExecutor());
} catch (Exception ex) {
log.error("", ex);
throw new IllegalStateException("could not request fees from fee service", ex);
}
}
void setTxFeeRatePreference(long txFeeRate,
ResultHandler resultHandler) {
if (txFeeRate <= 0)
throw new IllegalStateException("cannot create transactions without fees");
preferences.setUseCustomWithdrawalTxFee(true);
Coin satsPerByte = Coin.valueOf(txFeeRate);
preferences.setWithdrawalTxFeeInVbytes(satsPerByte.value);
getTxFeeRate(resultHandler);
}
void unsetTxFeeRatePreference(ResultHandler resultHandler) {
preferences.setUseCustomWithdrawalTxFee(false);
getTxFeeRate(resultHandler);
}
TxFeeRateInfo getMostRecentTxFeeRateInfo() {
return new TxFeeRateInfo(
preferences.isUseCustomWithdrawalTxFee(),
preferences.getWithdrawalTxFeeInVbytes(),
feeService.getTxFeePerVbyte().value,
feeService.getLastRequest());
}
int getNumConfirmationsForMostRecentTransaction(String addressString) {
Address address = getAddressEntry(addressString).getAddress();
TransactionConfidence confidence = btcWalletService.getConfidenceForAddress(address);
@ -244,6 +362,76 @@ class CoreWalletsService {
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 amount string, or a RuntimeException if invalid.
private Coin getValidBsqTransferAmount(String amount) {
Coin amountAsCoin = parseToCoin(amount, bsqFormatter);
if (amountAsCoin.isLessThan(getMinNonDustOutput()))
throw new IllegalStateException(format("%s bsq is an invalid send amount", amount));
return amountAsCoin;
}
private KeyCrypterScrypt getKeyCrypterScrypt() {
KeyCrypterScrypt keyCrypterScrypt = walletsManager.getKeyCrypterScrypt();
if (keyCrypterScrypt == null)

View file

@ -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" +
'}';
}
}

View file

@ -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 +
'}';
}
}

View file

@ -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 +
'}';
}
}

View file

@ -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 class OfferInfo implements Payload {
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 class OfferInfo implements Payload {
.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 class OfferInfo implements Payload {
.setVolume(volume)
.setMinVolume(minVolume)
.setBuyerSecurityDeposit(buyerSecurityDeposit)
.setIsCurrencyForMakerFeeBtc(isCurrencyForMakerFeeBtc)
.setPaymentAccountId(paymentAccountId)
.setPaymentMethodId(paymentMethodId)
.setPaymentMethodShortName(paymentMethodShortName)
@ -125,9 +129,28 @@ public class OfferInfo implements Payload {
.build();
}
@SuppressWarnings({"unused", "SameReturnValue"})
@SuppressWarnings("unused")
public static OfferInfo fromProto(bisq.proto.grpc.OfferInfo proto) {
return null; // TODO
return new OfferInfo.OfferInfoBuilder()
.withId(proto.getId())
.withDirection(proto.getDirection())
.withPrice(proto.getPrice())
.withUseMarketBasedPrice(proto.getUseMarketBasedPrice())
.withMarketPriceMargin(proto.getMarketPriceMargin())
.withAmount(proto.getAmount())
.withMinAmount(proto.getMinAmount())
.withVolume(proto.getVolume())
.withMinVolume(proto.getMinVolume())
.withBuyerSecurityDeposit(proto.getBuyerSecurityDeposit())
.withIsCurrencyForMakerFeeBtc(proto.getIsCurrencyForMakerFeeBtc())
.withPaymentAccountId(proto.getPaymentAccountId())
.withPaymentMethodId(proto.getPaymentMethodId())
.withPaymentMethodShortName(proto.getPaymentMethodShortName())
.withBaseCurrencyCode(proto.getBaseCurrencyCode())
.withCounterCurrencyCode(proto.getCounterCurrencyCode())
.withDate(proto.getDate())
.withState(proto.getState())
.build();
}
/*
@ -147,6 +170,7 @@ public class OfferInfo implements Payload {
private long volume;
private long minVolume;
private long buyerSecurityDeposit;
private boolean isCurrencyForMakerFeeBtc;
private String paymentAccountId;
private String paymentMethodId;
private String paymentMethodShortName;
@ -205,6 +229,11 @@ public class OfferInfo implements Payload {
return this;
}
public OfferInfoBuilder withIsCurrencyForMakerFeeBtc(boolean isCurrencyForMakerFeeBtc) {
this.isCurrencyForMakerFeeBtc = isCurrencyForMakerFeeBtc;
return this;
}
public OfferInfoBuilder withPaymentAccountId(String paymentAccountId) {
this.paymentAccountId = paymentAccountId;
return this;

View file

@ -0,0 +1,250 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
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;
/**
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
* <br>
* <p>
* (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:
* <pre>
* {
* "_COMMENTS_": [
* "Do not manually edit the paymentMethodId field.",
* "Edit the salt field only if you are recreating a payment account on a new installation and wish to preserve the account age."
* ],
* "paymentMethodId": "HAL_CASH",
* "accountName": "Your accountname",
* "mobileNr": "Your mobilenr"
* "salt": ""
* }
* </pre>
* </p>
* <p>
* (2) Save the Hal Cash payment account form to disk, and edit it:
* <pre>
* {
* "_COMMENTS_": [
* "Do not manually edit the paymentMethodId field.",
* "Edit the salt field only if you are recreating a payment account on a new installation and wish to preserve the account age."
* ],
* "paymentMethodId": "HAL_CASH",
* "accountName": "Hal Cash Acct",
* "mobileNr": "798 123 456"
* "salt": ""
* }
* </pre>
* </p>
* (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.
* <pre>
* 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)
* )
* </pre>
*/
@Singleton
@Slf4j
public class PaymentAccountForm {
private final GsonBuilder gsonBuilder = new GsonBuilder()
.setPrettyPrinting()
.serializeNulls();
// A list of PaymentAccount fields to exclude from json forms.
private final String[] excludedFields = new String[]{
"log",
"id",
"acceptedCountryCodes",
"countryCode",
"creationDate",
"excludeFromJsonDataMap",
"maxTradePeriod",
"paymentAccountPayload",
"paymentMethod",
"paymentMethodId", // This field will be included, but handled differently.
"selectedTradeCurrency",
"tradeCurrencies",
"HOLDER_NAME",
"SALT" // This field will be included, but handled differently.
};
/**
* 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<String, Object> 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();
}
}

View file

@ -0,0 +1,362 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
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.common.util.Utilities.decodeFromHex;
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<PaymentAccount> {
private static final String[] JSON_COMMENTS = new String[]{
"Do not manually edit the paymentMethodId field.",
"Edit the salt field only if you are recreating a payment"
+ " account on a new installation and wish to preserve the account age."
};
private final Class<? extends PaymentAccount> paymentAccountType;
private final Class<? extends PaymentAccountPayload> paymentAccountPayloadType;
private final Map<Field, Optional<Method>> fieldSettersMap;
private final Predicate<Field> 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.
out.beginObject();
// All json forms start with immutable _COMMENTS_ and paymentMethodId fields.
out.name("_COMMENTS_");
out.beginArray();
for (String s : JSON_COMMENTS) {
out.value(s);
}
out.endArray();
out.name("paymentMethodId");
out.value(account.getPaymentMethod().getId());
// Write the editable, PaymentAccount subclass specific fields.
writeInnerMutableFields(out, account);
// The last field in all json forms is the empty, editable salt field.
out.name("salt");
out.value("");
out.endObject();
}
private void writeInnerMutableFields(JsonWriter out, PaymentAccount account) {
fieldSettersMap.forEach((field, value) -> {
try {
// Write out a json element if there is a @Setter for this field.
if (value.isPresent()) {
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);
}
});
}
@Override
public PaymentAccount read(JsonReader in) throws IOException {
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> 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();
return account;
}
private void invokeSetterMethod(PaymentAccount account, Field field, JsonReader jsonReader) {
Optional<Method> 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<Field, Optional<Method>> getFieldSetterMap() {
List<Field> orderedFields = getOrderedFields();
Map<Field, Optional<Method>> map = new LinkedHashMap<>();
for (Field field : orderedFields) {
Optional<Method> setter = getSetterMethodForFieldInClassHierarchy(field, paymentAccountType)
.or(() -> getSetterMethodForFieldInClassHierarchy(field, paymentAccountPayloadType));
map.put(field, setter);
}
return Collections.unmodifiableMap(map);
}
private List<Field> getOrderedFields() {
List<Field> 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 boolean didReadCommonField(JsonReader in,
PaymentAccount account,
String fieldName) throws IOException {
switch (fieldName) {
case "_COMMENTS_":
case "paymentMethodId":
// Skip over the the comments and paymentMethodId, which is already
// set on the PaymentAccount instance.
in.skipValue();
return true;
case "accountName":
// Set the acct name using the value read from json.
account.setAccountName(nextStringOrNull(in));
return true;
case "salt":
// Set the acct salt using the value read from json.
String saltAsHex = nextStringOrNull(in);
if (saltAsHex != null && !saltAsHex.trim().isEmpty()) {
account.setSalt(decodeFromHex(saltAsHex));
}
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> 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);
}
}
}

View file

@ -0,0 +1,75 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.api.model;
import bisq.common.Payload;
import lombok.EqualsAndHashCode;
import lombok.Getter;
@EqualsAndHashCode
@Getter
public class TxFeeRateInfo implements Payload {
private final boolean useCustomTxFeeRate;
private final long customTxFeeRate;
private final long feeServiceRate;
private final long lastFeeServiceRequestTs;
public TxFeeRateInfo(boolean useCustomTxFeeRate,
long customTxFeeRate,
long feeServiceRate,
long lastFeeServiceRequestTs) {
this.useCustomTxFeeRate = useCustomTxFeeRate;
this.customTxFeeRate = customTxFeeRate;
this.feeServiceRate = feeServiceRate;
this.lastFeeServiceRequestTs = lastFeeServiceRequestTs;
}
//////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
//////////////////////////////////////////////////////////////////////////////////////
@Override
public bisq.proto.grpc.TxFeeRateInfo toProtoMessage() {
return bisq.proto.grpc.TxFeeRateInfo.newBuilder()
.setUseCustomTxFeeRate(useCustomTxFeeRate)
.setCustomTxFeeRate(customTxFeeRate)
.setFeeServiceRate(feeServiceRate)
.setLastFeeServiceRequestTs(lastFeeServiceRequestTs)
.build();
}
@SuppressWarnings("unused")
public static TxFeeRateInfo fromProto(bisq.proto.grpc.TxFeeRateInfo proto) {
return new TxFeeRateInfo(proto.getUseCustomTxFeeRate(),
proto.getCustomTxFeeRate(),
proto.getFeeServiceRate(),
proto.getLastFeeServiceRequestTs());
}
@Override
public String toString() {
return "TxFeeRateInfo{" + "\n" +
" useCustomTxFeeRate=" + useCustomTxFeeRate + "\n" +
", customTxFeeRate=" + customTxFeeRate + "sats/byte" + "\n" +
", feeServiceRate=" + feeServiceRate + "sats/byte" + "\n" +
", lastFeeServiceRequestTs=" + lastFeeServiceRequestTs + "\n" +
'}';
}
}

View file

@ -0,0 +1,70 @@
package bisq.core.btc.model;
import bisq.core.dao.state.model.blockchain.TxType;
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 getTxSizeInKb() {
return txSize / 1000d;
}
public String toShortString() {
return "{" + "\n" +
" receiverAddress='" + getReceiverAddressAsString() + '\'' + "\n" +
", receiverAmount=" + receiverAmount + "\n" +
", txWithBtcFee.txId=" + txWithBtcFee.getTxId() + "\n" +
", miningFee=" + miningFee + "\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" +
", txSize=" + txSize + "\n" +
", txSizeInKb=" + getTxSizeInKb() + "\n" +
", txType=" + txType + "\n" +
'}';
}
}

View file

@ -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);
}
}

View file

@ -49,6 +49,7 @@ import javax.inject.Singleton;
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.btc.wallet.Restrictions.isDust;
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<String> isValidFeePaymentCurrencyCode = (c) ->
c.equalsIgnoreCase("BSQ") || c.equalsIgnoreCase("BTC");
@Inject
public OfferUtil(AccountAgeWitnessService accountAgeWitnessService,
BsqWalletService bsqWalletService,
@ -96,6 +101,19 @@ public class OfferUtil {
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?
*

View file

@ -173,10 +173,18 @@ public abstract class PaymentAccount implements PersistablePayload {
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

View file

@ -44,6 +44,7 @@ import java.time.Instant;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
@ -95,6 +96,7 @@ public class FeeService {
private final IntegerProperty feeUpdateCounter = new SimpleIntegerProperty(0);
private long txFeePerVbyte = BTC_DEFAULT_TX_FEE;
private Map<String, Long> timeStampMap;
@Getter
private long lastRequest;
private long minFeePerVByte;
private long epochInSecondAtLastRequest;

View file

@ -863,7 +863,8 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
}
public long getWithdrawalTxFeeInVbytes() {
return Math.max(prefPayload.getWithdrawalTxFeeInVbytes(), Config.baseCurrencyNetwork().getDefaultMinFeePerVbyte());
return Math.max(prefPayload.getWithdrawalTxFeeInVbytes(),
Config.baseCurrencyNetwork().getDefaultMinFeePerVbyte());
}
public boolean isDaoFullNode() {

View file

@ -100,6 +100,7 @@ class GrpcOffersService extends OffersGrpc.OffersImplBase {
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.

View file

@ -19,13 +19,20 @@ package bisq.daemon.grpc;
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 @@ class GrpcPaymentAccountsService extends PaymentAccountsGrpc.PaymentAccountsImpl
@Override
public void createPaymentAccount(CreatePaymentAccountRequest req,
StreamObserver<CreatePaymentAccountReply> 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<GetPaymentAccountsReply> 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<GetPaymentMethodsReply> 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<GetPaymentAccountFormReply> 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;
}
}
}

View file

@ -79,6 +79,7 @@ class GrpcTradesService extends TradesGrpc.TradesImplBase {
try {
coreApi.takeOffer(req.getOfferId(),
req.getPaymentAccountId(),
req.getTakerFeeCurrencyCode(),
trade -> {
TradeInfo tradeInfo = toTradeInfo(trade);
var reply = TakeOfferReply.newBuilder()

View file

@ -19,32 +19,50 @@ package bisq.daemon.grpc;
import bisq.core.api.CoreApi;
import bisq.core.api.model.AddressBalanceInfo;
import bisq.core.api.model.TxFeeRateInfo;
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.GetTxFeeRateReply;
import bisq.proto.grpc.GetTxFeeRateRequest;
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.SetTxFeeRatePreferenceReply;
import bisq.proto.grpc.SetTxFeeRatePreferenceRequest;
import bisq.proto.grpc.SetWalletPasswordReply;
import bisq.proto.grpc.SetWalletPasswordRequest;
import bisq.proto.grpc.UnlockWalletReply;
import bisq.proto.grpc.UnlockWalletRequest;
import bisq.proto.grpc.UnsetTxFeeRatePreferenceReply;
import bisq.proto.grpc.UnsetTxFeeRatePreferenceRequest;
import bisq.proto.grpc.WalletsGrpc;
import io.grpc.Status;
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 +72,13 @@ class GrpcWalletsService extends WalletsGrpc.WalletsImplBase {
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<GetBalanceReply> responseObserver) {
public void getBalances(GetBalancesRequest req, StreamObserver<GetBalancesReply> 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 +124,109 @@ class GrpcWalletsService extends WalletsGrpc.WalletsImplBase {
}
}
@Override
public void getUnusedBsqAddress(GetUnusedBsqAddressRequest req,
StreamObserver<GetUnusedBsqAddressReply> 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<SendBsqReply> 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 getTxFeeRate(GetTxFeeRateRequest req,
StreamObserver<GetTxFeeRateReply> responseObserver) {
try {
coreApi.getTxFeeRate(() -> {
TxFeeRateInfo txFeeRateInfo = coreApi.getMostRecentTxFeeRateInfo();
var reply = GetTxFeeRateReply.newBuilder()
.setTxFeeRateInfo(txFeeRateInfo.toProtoMessage())
.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 setTxFeeRatePreference(SetTxFeeRatePreferenceRequest req,
StreamObserver<SetTxFeeRatePreferenceReply> responseObserver) {
try {
coreApi.setTxFeeRatePreference(req.getTxFeeRatePreference(), () -> {
TxFeeRateInfo txFeeRateInfo = coreApi.getMostRecentTxFeeRateInfo();
var reply = SetTxFeeRatePreferenceReply.newBuilder()
.setTxFeeRateInfo(txFeeRateInfo.toProtoMessage())
.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 unsetTxFeeRatePreference(UnsetTxFeeRatePreferenceRequest req,
StreamObserver<UnsetTxFeeRatePreferenceReply> responseObserver) {
try {
coreApi.unsetTxFeeRatePreference(() -> {
TxFeeRateInfo txFeeRateInfo = coreApi.getMostRecentTxFeeRateInfo();
var reply = UnsetTxFeeRatePreferenceReply.newBuilder()
.setTxFeeRateInfo(txFeeRateInfo.toProtoMessage())
.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 setWalletPassword(SetWalletPasswordRequest req,
StreamObserver<SetWalletPasswordReply> responseObserver) {

View file

@ -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,20 @@ 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 GetTxFeeRate (GetTxFeeRateRequest) returns (GetTxFeeRateReply) {
}
rpc SetTxFeeRatePreference (SetTxFeeRatePreferenceRequest) returns (SetTxFeeRatePreferenceReply) {
}
rpc unsetTxFeeRatePreference (UnsetTxFeeRatePreferenceRequest) returns (UnsetTxFeeRatePreferenceReply) {
}
rpc GetFundingAddresses (GetFundingAddressesRequest) returns (GetFundingAddressesReply) {
}
rpc SetWalletPassword (SetWalletPasswordRequest) returns (SetWalletPasswordReply) {
@ -289,11 +318,12 @@ service Wallets {
}
}
message GetBalanceRequest {
message GetBalancesRequest {
string currencyCode = 1;
}
message GetBalanceReply {
uint64 balance = 1;
message GetBalancesReply {
BalancesInfo balances = 1;
}
message GetAddressBalanceRequest {
@ -304,6 +334,43 @@ message GetAddressBalanceReply {
AddressBalanceInfo addressBalanceInfo = 1;
}
message GetUnusedBsqAddressRequest {
}
message GetUnusedBsqAddressReply {
string address = 1;
}
message SendBsqRequest {
string address = 1;
string amount = 2;
}
message SendBsqReply {
}
message GetTxFeeRateRequest {
}
message GetTxFeeRateReply {
TxFeeRateInfo txFeeRateInfo = 1;
}
message SetTxFeeRatePreferenceRequest {
uint64 txFeeRatePreference = 1;
}
message SetTxFeeRatePreferenceReply {
TxFeeRateInfo txFeeRateInfo = 1;
}
message UnsetTxFeeRatePreferenceRequest {
}
message UnsetTxFeeRatePreferenceReply {
TxFeeRateInfo txFeeRateInfo = 1;
}
message GetFundingAddressesRequest {
}
@ -340,12 +407,43 @@ 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;
int64 numConfirmations = 3;
}
message TxFeeRateInfo {
bool useCustomTxFeeRate = 1;
uint64 customTxFeeRate = 2;
uint64 feeServiceRate = 3;
uint64 lastFeeServiceRequestTs = 4;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Version
///////////////////////////////////////////////////////////////////////////////////////////