Merge pull request #5284 from ghubstan/07-validate-paymentacct-n-new-offer

Validate offer <-> payment-acct in createoffer
This commit is contained in:
sqrrm 2021-03-09 16:51:56 +01:00 committed by GitHub
commit 463f87b39e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 674 additions and 226 deletions

View file

@ -30,6 +30,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static java.lang.String.format;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@ -55,4 +56,40 @@ public class ValidateCreateOfferTest extends AbstractOfferTest {
assertEquals("UNKNOWN: An error occurred at task: ValidateOffer",
exception.getMessage());
}
@Test
@Order(2)
public void testNoMatchingEURPaymentAccountShouldThrowException() {
PaymentAccount chfAccount = createDummyF2FAccount(aliceClient, "ch");
@SuppressWarnings("ResultOfMethodCallIgnored")
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
aliceClient.createFixedPricedOffer("buy",
"eur",
10000000L,
10000000L,
"40000.0000",
getDefaultBuyerSecurityDepositAsPercent(),
chfAccount.getId(),
"btc"));
String expectedError = format("UNKNOWN: cannot create EUR offer with payment account %s", chfAccount.getId());
assertEquals(expectedError, exception.getMessage());
}
@Test
@Order(2)
public void testNoMatchingCADPaymentAccountShouldThrowException() {
PaymentAccount audAccount = createDummyF2FAccount(aliceClient, "au");
@SuppressWarnings("ResultOfMethodCallIgnored")
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
aliceClient.createFixedPricedOffer("buy",
"cad",
10000000L,
10000000L,
"63000.0000",
getDefaultBuyerSecurityDepositAsPercent(),
audAccount.getId(),
"btc"));
String expectedError = format("UNKNOWN: cannot create CAD offer with payment account %s", audAccount.getId());
assertEquals(expectedError, exception.getMessage());
}
}

View file

@ -85,6 +85,7 @@ public class AbstractPaymentAccountTest extends MethodTest {
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_TRADE_CURRENCIES = "tradeCurrencies";
static final String PROPERTY_NAME_USERNAME = "userName";
static final Gson GSON = new GsonBuilder()

View file

@ -17,6 +17,7 @@
package bisq.apitest.method.payment;
import bisq.core.locale.TradeCurrency;
import bisq.core.payment.AdvancedCashAccount;
import bisq.core.payment.AliPayAccount;
import bisq.core.payment.AustraliaPayid;
@ -31,6 +32,7 @@ import bisq.core.payment.JapanBankAccount;
import bisq.core.payment.MoneyBeamAccount;
import bisq.core.payment.MoneyGramAccount;
import bisq.core.payment.NationalBankAccount;
import bisq.core.payment.PaymentAccount;
import bisq.core.payment.PerfectMoneyAccount;
import bisq.core.payment.PopmoneyAccount;
import bisq.core.payment.PromptPayAccount;
@ -50,8 +52,12 @@ import bisq.core.payment.payload.CashDepositAccountPayload;
import bisq.core.payment.payload.SameBankAccountPayload;
import bisq.core.payment.payload.SpecificBanksAccountPayload;
import io.grpc.StatusRuntimeException;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
@ -65,15 +71,16 @@ 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.getAllAdvancedCashCurrencies;
import static bisq.core.locale.CurrencyUtil.getAllMoneyGramCurrencies;
import static bisq.core.locale.CurrencyUtil.getAllRevolutCurrencies;
import static bisq.core.locale.CurrencyUtil.getAllUpholdCurrencies;
import static bisq.cli.TableFormat.formatPaymentAcctTbl;
import static bisq.core.locale.CurrencyUtil.*;
import static bisq.core.payment.payload.PaymentMethod.*;
import static java.util.Collections.singletonList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
@SuppressWarnings({"OptionalGetWithoutIsPresent", "ConstantConditions"})
@Disabled
@Slf4j
@TestMethodOrder(OrderAnnotation.class)
@ -105,7 +112,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
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);
print(paymentAccount);
}
@Test
@ -124,7 +131,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
verifyAccountSingleTradeCurrency("CNY", paymentAccount);
verifyCommonFormEntries(paymentAccount);
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), paymentAccount.getAccountNr());
log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
print(paymentAccount);
}
@Test
@ -146,7 +153,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
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);
print(paymentAccount);
}
@Test
@ -198,7 +205,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
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);
print(paymentAccount);
}
@Test
@ -243,7 +250,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
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);
print(paymentAccount);
}
@Test
@ -265,7 +272,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
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);
print(paymentAccount);
}
@Test
@ -288,7 +295,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
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);
print(paymentAccount);
}
@Test
@ -317,7 +324,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
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);
print(paymentAccount);
}
@Test
@ -340,7 +347,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
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);
print(paymentAccount);
}
@Test
@ -359,7 +366,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
verifyAccountSingleTradeCurrency("EUR", paymentAccount);
verifyCommonFormEntries(paymentAccount);
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_MOBILE_NR), paymentAccount.getMobileNr());
log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
print(paymentAccount);
}
@Test
@ -388,7 +395,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
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);
print(paymentAccount);
}
@Test
@ -425,7 +432,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
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);
print(paymentAccount);
}
@Test
@ -445,7 +452,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
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);
print(paymentAccount);
}
@Test
@ -474,7 +481,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
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);
print(paymentAccount);
}
@Test
@ -494,7 +501,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
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);
print(paymentAccount);
}
@Test
@ -516,7 +523,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
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);
print(paymentAccount);
}
@Test
@ -536,7 +543,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
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);
print(paymentAccount);
}
@Test
@ -555,7 +562,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
verifyAccountTradeCurrencies(getAllRevolutCurrencies(), paymentAccount);
verifyCommonFormEntries(paymentAccount);
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_USERNAME), paymentAccount.getUserName());
log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
print(paymentAccount);
}
@Test
@ -600,7 +607,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
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);
print(paymentAccount);
}
@Test
@ -631,7 +638,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
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);
print(paymentAccount);
}
@Test
@ -663,7 +670,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
// 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);
print(paymentAccount);
}
@Test
@ -710,7 +717,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
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);
print(paymentAccount);
}
@Test
@ -733,29 +740,104 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
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);
print(paymentAccount);
}
@Test
public void testCreateTransferwiseAccount(TestInfo testInfo) {
public void testCreateTransferwiseAccountWith1TradeCurrency(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_TRADE_CURRENCIES, "eur");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "jane@doe.info");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
String jsonString = getCompletedFormAsJsonString();
TransferwiseAccount paymentAccount = (TransferwiseAccount) createPaymentAccount(aliceClient, jsonString);
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
// As per commit 88f26f93241af698ae689bf081205d0f9dc929fa
// Do not autofill all currencies by default but keep all unselected.
// verifyAccountTradeCurrencies(getAllTransferwiseCurrencies(), paymentAccount);
assertEquals(0, paymentAccount.getTradeCurrencies().size());
assertEquals(1, paymentAccount.getTradeCurrencies().size());
TradeCurrency expectedCurrency = getTradeCurrency("EUR").get();
assertEquals(expectedCurrency, paymentAccount.getSelectedTradeCurrency());
List<TradeCurrency> expectedTradeCurrencies = singletonList(expectedCurrency);
verifyAccountTradeCurrencies(expectedTradeCurrencies, paymentAccount);
verifyCommonFormEntries(paymentAccount);
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
print(paymentAccount);
}
@Test
public void testCreateTransferwiseAccountWith10TradeCurrencies(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_TRADE_CURRENCIES, "ars, cad, hrk, czk, eur, hkd, idr, jpy, chf, nzd");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "jane@doe.info");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
String jsonString = getCompletedFormAsJsonString();
TransferwiseAccount paymentAccount = (TransferwiseAccount) createPaymentAccount(aliceClient, jsonString);
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
assertEquals(10, paymentAccount.getTradeCurrencies().size());
List<TradeCurrency> expectedTradeCurrencies = new ArrayList<>() {{
add(getTradeCurrency("ARS").get()); // 1st in list = selected ccy
add(getTradeCurrency("CAD").get());
add(getTradeCurrency("HRK").get());
add(getTradeCurrency("CZK").get());
add(getTradeCurrency("EUR").get());
add(getTradeCurrency("HKD").get());
add(getTradeCurrency("IDR").get());
add(getTradeCurrency("JPY").get());
add(getTradeCurrency("CHF").get());
add(getTradeCurrency("NZD").get());
}};
verifyAccountTradeCurrencies(expectedTradeCurrencies, paymentAccount);
TradeCurrency expectedSelectedCurrency = expectedTradeCurrencies.get(0);
assertEquals(expectedSelectedCurrency, paymentAccount.getSelectedTradeCurrency());
verifyCommonFormEntries(paymentAccount);
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
print(paymentAccount);
}
@Test
public void testCreateTransferwiseAccountWithInvalidBrlTradeCurrencyShouldThrowException(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_TRADE_CURRENCIES, "eur, hkd, idr, jpy, chf, nzd, brl, gbp");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "jane@doe.info");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
String jsonString = getCompletedFormAsJsonString();
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
createPaymentAccount(aliceClient, jsonString));
assertEquals("INVALID_ARGUMENT: BRL is not a member of valid currencies list",
exception.getMessage());
}
@Test
public void testCreateTransferwiseAccountWithoutTradeCurrenciesShouldThrowException(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_TRADE_CURRENCIES, "");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "jane@doe.info");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
String jsonString = getCompletedFormAsJsonString();
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
createPaymentAccount(aliceClient, jsonString));
assertEquals("INVALID_ARGUMENT: no trade currencies defined for transferwise payment account",
exception.getMessage());
}
@Test
@ -775,7 +857,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
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);
print(paymentAccount);
}
@Test
@ -797,7 +879,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
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);
print(paymentAccount);
}
@Test
@ -817,7 +899,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
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);
print(paymentAccount);
}
@Test
@ -849,11 +931,18 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
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);
print(paymentAccount);
}
@AfterAll
public static void tearDown() {
tearDownScaffold();
}
private void print(PaymentAccount paymentAccount) {
if (log.isDebugEnabled()) {
log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
log.debug("\n{}", formatPaymentAcctTbl(singletonList(paymentAccount.toProtoMessage())));
}
}
}

View file

@ -42,6 +42,8 @@ public class OfferTest extends AbstractOfferTest {
public void testAmtTooLargeShouldThrowException() {
ValidateCreateOfferTest test = new ValidateCreateOfferTest();
test.testAmtTooLargeShouldThrowException();
test.testNoMatchingEURPaymentAccountShouldThrowException();
test.testNoMatchingCADPaymentAccountShouldThrowException();
}
@Test

View file

@ -13,8 +13,6 @@ 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.Objects.requireNonNull;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
@ -27,10 +25,6 @@ import bisq.apitest.method.payment.GetPaymentMethodsTest;
@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 {
@ -75,14 +69,18 @@ public class PaymentAccountTest extends AbstractPaymentAccountTest {
test.testCreateSepaAccount(testInfo);
test.testCreateSpecificBanksAccount(testInfo);
test.testCreateSwishAccount(testInfo);
test.testCreateTransferwiseAccount(testInfo);
// TransferwiseAccount is only PaymentAccount with a
// tradeCurrencies field in the json form.
test.testCreateTransferwiseAccountWith1TradeCurrency(testInfo);
test.testCreateTransferwiseAccountWith10TradeCurrencies(testInfo);
test.testCreateTransferwiseAccountWithInvalidBrlTradeCurrencyShouldThrowException(testInfo);
test.testCreateTransferwiseAccountWithoutTradeCurrenciesShouldThrowException(testInfo);
test.testCreateUpholdAccount(testInfo);
test.testCreateUSPostalMoneyOrderAccount(testInfo);
test.testCreateWeChatPayAccount(testInfo);
test.testCreateWesternUnionAccount(testInfo);
var paymentAccounts = requireNonNull(aliceClient).getPaymentAccounts();
assertEquals(EXPECTED_NUM_PAYMENT_ACCOUNTS, paymentAccounts.size());
}
@AfterAll

View file

@ -378,6 +378,17 @@ configure(project(':cli')) {
implementation "ch.qos.logback:logback-classic:$logbackVersion"
compileOnly "org.projectlombok:lombok:$lombokVersion"
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
testImplementation "org.junit.jupiter:junit-jupiter-api:$jupiterVersion"
testImplementation "org.junit.jupiter:junit-jupiter-params:$jupiterVersion"
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$jupiterVersion")
testAnnotationProcessor "org.projectlombok:lombok:$lombokVersion"
testCompileOnly "org.projectlombok:lombok:$lombokVersion"
testRuntime "javax.annotation:javax.annotation-api:$javaxAnnotationVersion"
}
test {
useJUnitPlatform()
}
}

View file

@ -163,7 +163,7 @@ public class TableFormat {
+ padEnd(COL_HEADER_PAYMENT_METHOD, paymentMethodColWidth, ' ') + COL_HEADER_DELIMITER
+ COL_HEADER_UUID + COL_HEADER_DELIMITER + "\n";
String colDataFormat = "%-" + nameColWidth + "s" // left justify
+ " %" + COL_HEADER_CURRENCY.length() + "s" // right justify
+ " %-" + COL_HEADER_CURRENCY.length() + "s" // left justify
+ " %-" + paymentMethodColWidth + "s" // left justify
+ " %-" + COL_HEADER_UUID.length() + "s"; // left justify
return headerLine

View file

@ -17,11 +17,13 @@
package bisq.cli.opts;
import joptsimple.OptionException;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import java.util.List;
import java.util.function.Function;
import lombok.Getter;
@ -48,12 +50,29 @@ abstract class AbstractMethodOptionParser implements MethodOpts {
}
public AbstractMethodOptionParser parse() {
options = parser.parse(new ArgumentList(args).getMethodArguments());
nonOptionArguments = (List<String>) options.nonOptionArguments();
return this;
try {
options = parser.parse(new ArgumentList(args).getMethodArguments());
//noinspection unchecked
nonOptionArguments = (List<String>) options.nonOptionArguments();
return this;
} catch (OptionException ex) {
throw new IllegalArgumentException(cliExceptionMessageStyle.apply(ex), ex);
}
}
public boolean isForHelp() {
return options.has(helpOpt);
}
private final Function<OptionException, String> cliExceptionMessageStyle = (ex) -> {
if (ex.getMessage() == null)
return null;
var optionToken = "option ";
var cliMessage = ex.getMessage().toLowerCase();
if (cliMessage.startsWith(optionToken) && cliMessage.length() > optionToken.length()) {
cliMessage = cliMessage.substring(cliMessage.indexOf(" ") + 1);
}
return cliMessage;
};
}

View file

@ -21,13 +21,11 @@ package bisq.cli.opts;
import joptsimple.OptionSpec;
import static bisq.cli.opts.OptLabel.OPT_OFFER_ID;
import static joptsimple.internal.Strings.EMPTY;
public class CancelOfferOptionParser extends AbstractMethodOptionParser implements MethodOpts {
final OptionSpec<String> offerIdOpt = parser.accepts(OPT_OFFER_ID, "id of offer to cancel")
.withRequiredArg()
.defaultsTo(EMPTY);
.withRequiredArg();
public CancelOfferOptionParser(String[] args) {
super(args);
@ -40,7 +38,7 @@ public class CancelOfferOptionParser extends AbstractMethodOptionParser implemen
if (options.has(helpOpt))
return this;
if (!options.has(offerIdOpt))
if (!options.has(offerIdOpt) || options.valueOf(offerIdOpt).isEmpty())
throw new IllegalArgumentException("no offer id specified");
return this;

View file

@ -33,20 +33,16 @@ public class CreateOfferOptionParser extends AbstractMethodOptionParser implemen
.defaultsTo(EMPTY);
final OptionSpec<String> directionOpt = parser.accepts(OPT_DIRECTION, "offer direction (buy|sell)")
.withRequiredArg()
.defaultsTo(EMPTY);
.withRequiredArg();
final OptionSpec<String> currencyCodeOpt = parser.accepts(OPT_CURRENCY_CODE, "currency code (eur|usd|...)")
.withRequiredArg()
.defaultsTo(EMPTY);
.withRequiredArg();
final OptionSpec<String> amountOpt = parser.accepts(OPT_AMOUNT, "amount of btc to buy or sell")
.withRequiredArg()
.defaultsTo(EMPTY);
.withRequiredArg();
final OptionSpec<String> minAmountOpt = parser.accepts(OPT_MIN_AMOUNT, "minimum amount of btc to buy or sell")
.withOptionalArg()
.defaultsTo(EMPTY);
.withOptionalArg();
final OptionSpec<String> mktPriceMarginOpt = parser.accepts(OPT_MKT_PRICE_MARGIN, "market btc price margin (%)")
.withOptionalArg()
@ -54,11 +50,10 @@ public class CreateOfferOptionParser extends AbstractMethodOptionParser implemen
final OptionSpec<String> fixedPriceOpt = parser.accepts(OPT_FIXED_PRICE, "fixed btc price")
.withOptionalArg()
.defaultsTo(EMPTY);
.defaultsTo("0");
final OptionSpec<String> securityDepositOpt = parser.accepts(OPT_SECURITY_DEPOSIT, "maker security deposit (%)")
.withRequiredArg()
.defaultsTo(EMPTY);
.withRequiredArg();
final OptionSpec<String> makerFeeCurrencyCodeOpt = parser.accepts(OPT_FEE_CURRENCY, "maker fee currency code (bsq|btc)")
.withOptionalArg()
@ -75,19 +70,28 @@ public class CreateOfferOptionParser extends AbstractMethodOptionParser implemen
if (options.has(helpOpt))
return this;
if (!options.has(paymentAccountIdOpt))
if (!options.has(paymentAccountIdOpt) || options.valueOf(paymentAccountIdOpt).isEmpty())
throw new IllegalArgumentException("no payment account id specified");
if (!options.has(directionOpt))
if (!options.has(directionOpt) || options.valueOf(directionOpt).isEmpty())
throw new IllegalArgumentException("no direction (buy|sell) specified");
if (!options.has(amountOpt))
if (!options.has(currencyCodeOpt) || options.valueOf(currencyCodeOpt).isEmpty())
throw new IllegalArgumentException("no currency code specified");
if (!options.has(amountOpt) || options.valueOf(amountOpt).isEmpty())
throw new IllegalArgumentException("no btc amount specified");
if (!options.has(mktPriceMarginOpt) && !options.has(fixedPriceOpt))
throw new IllegalArgumentException("no market price margin or fixed price specified");
if (!options.has(securityDepositOpt))
if (options.has(mktPriceMarginOpt) && options.valueOf(mktPriceMarginOpt).isEmpty())
throw new IllegalArgumentException("no market price margin specified");
if (options.has(fixedPriceOpt) && options.valueOf(fixedPriceOpt).isEmpty())
throw new IllegalArgumentException("no fixed price specified");
if (!options.has(securityDepositOpt) || options.valueOf(securityDepositOpt).isEmpty())
throw new IllegalArgumentException("no security deposit specified");
return this;

View file

@ -25,14 +25,12 @@ import java.nio.file.Paths;
import static bisq.cli.opts.OptLabel.OPT_PAYMENT_ACCOUNT_FORM;
import static java.lang.String.format;
import static joptsimple.internal.Strings.EMPTY;
public class CreatePaymentAcctOptionParser extends AbstractMethodOptionParser implements MethodOpts {
final OptionSpec<String> paymentAcctFormPathOpt = parser.accepts(OPT_PAYMENT_ACCOUNT_FORM,
"path to json payment account form")
.withRequiredArg()
.defaultsTo(EMPTY);
.withRequiredArg();
public CreatePaymentAcctOptionParser(String[] args) {
super(args);
@ -45,7 +43,7 @@ public class CreatePaymentAcctOptionParser extends AbstractMethodOptionParser im
if (options.has(helpOpt))
return this;
if (!options.has(paymentAcctFormPathOpt))
if (!options.has(paymentAcctFormPathOpt) || options.valueOf(paymentAcctFormPathOpt).isEmpty())
throw new IllegalArgumentException("no path to json payment account form specified");
Path path = Paths.get(options.valueOf(paymentAcctFormPathOpt));

View file

@ -21,13 +21,11 @@ package bisq.cli.opts;
import joptsimple.OptionSpec;
import static bisq.cli.opts.OptLabel.OPT_ADDRESS;
import static joptsimple.internal.Strings.EMPTY;
public class GetAddressBalanceOptionParser extends AbstractMethodOptionParser implements MethodOpts {
final OptionSpec<String> addressOpt = parser.accepts(OPT_ADDRESS, "wallet btc address")
.withRequiredArg()
.defaultsTo(EMPTY);
.withRequiredArg();
public GetAddressBalanceOptionParser(String[] args) {
super(args);
@ -40,7 +38,7 @@ public class GetAddressBalanceOptionParser extends AbstractMethodOptionParser im
if (options.has(helpOpt))
return this;
if (!options.has(addressOpt))
if (!options.has(addressOpt) || options.valueOf(addressOpt).isEmpty())
throw new IllegalArgumentException("no address specified");
return this;

View file

@ -21,13 +21,11 @@ package bisq.cli.opts;
import joptsimple.OptionSpec;
import static bisq.cli.opts.OptLabel.OPT_CURRENCY_CODE;
import static joptsimple.internal.Strings.EMPTY;
public class GetBTCMarketPriceOptionParser extends AbstractMethodOptionParser implements MethodOpts {
final OptionSpec<String> currencyCodeOpt = parser.accepts(OPT_CURRENCY_CODE, "currency-code")
.withRequiredArg()
.defaultsTo(EMPTY);
.withRequiredArg();
public GetBTCMarketPriceOptionParser(String[] args) {
super(args);
@ -40,7 +38,7 @@ public class GetBTCMarketPriceOptionParser extends AbstractMethodOptionParser im
if (options.has(helpOpt))
return this;
if (!options.has(currencyCodeOpt))
if (!options.has(currencyCodeOpt) || options.valueOf(currencyCodeOpt).isEmpty())
throw new IllegalArgumentException("no currency code specified");
return this;

View file

@ -21,13 +21,11 @@ package bisq.cli.opts;
import joptsimple.OptionSpec;
import static bisq.cli.opts.OptLabel.OPT_OFFER_ID;
import static joptsimple.internal.Strings.EMPTY;
public class GetOfferOptionParser extends AbstractMethodOptionParser implements MethodOpts {
final OptionSpec<String> offerIdOpt = parser.accepts(OPT_OFFER_ID, "id of offer to get")
.withRequiredArg()
.defaultsTo(EMPTY);
.withRequiredArg();
public GetOfferOptionParser(String[] args) {
super(args);
@ -40,7 +38,7 @@ public class GetOfferOptionParser extends AbstractMethodOptionParser implements
if (options.has(helpOpt))
return this;
if (!options.has(offerIdOpt))
if (!options.has(offerIdOpt) || options.valueOf(offerIdOpt).isEmpty())
throw new IllegalArgumentException("no offer id specified");
return this;

View file

@ -22,17 +22,14 @@ import joptsimple.OptionSpec;
import static bisq.cli.opts.OptLabel.OPT_CURRENCY_CODE;
import static bisq.cli.opts.OptLabel.OPT_DIRECTION;
import static joptsimple.internal.Strings.EMPTY;
public class GetOffersOptionParser extends AbstractMethodOptionParser implements MethodOpts {
final OptionSpec<String> directionOpt = parser.accepts(OPT_DIRECTION, "offer direction (buy|sell)")
.withRequiredArg()
.defaultsTo(EMPTY);
.withRequiredArg();
final OptionSpec<String> currencyCodeOpt = parser.accepts(OPT_CURRENCY_CODE, "currency code (eur|usd|...)")
.withRequiredArg()
.defaultsTo(EMPTY);
.withRequiredArg();
public GetOffersOptionParser(String[] args) {
super(args);
@ -45,10 +42,10 @@ public class GetOffersOptionParser extends AbstractMethodOptionParser implements
if (options.has(helpOpt))
return this;
if (!options.has(directionOpt))
if (!options.has(directionOpt) || options.valueOf(directionOpt).isEmpty())
throw new IllegalArgumentException("no direction (buy|sell) specified");
if (!options.has(currencyCodeOpt))
if (!options.has(currencyCodeOpt) || options.valueOf(currencyCodeOpt).isEmpty())
throw new IllegalArgumentException("no currency code specified");
return this;

View file

@ -21,14 +21,12 @@ package bisq.cli.opts;
import joptsimple.OptionSpec;
import static bisq.cli.opts.OptLabel.OPT_PAYMENT_METHOD_ID;
import static joptsimple.internal.Strings.EMPTY;
public class GetPaymentAcctFormOptionParser extends AbstractMethodOptionParser implements MethodOpts {
final OptionSpec<String> paymentMethodIdOpt = parser.accepts(OPT_PAYMENT_METHOD_ID,
"id of payment method type used by a payment account")
.withRequiredArg()
.defaultsTo(EMPTY);
.withRequiredArg();
public GetPaymentAcctFormOptionParser(String[] args) {
super(args);
@ -41,7 +39,7 @@ public class GetPaymentAcctFormOptionParser extends AbstractMethodOptionParser i
if (options.has(helpOpt))
return this;
if (!options.has(paymentMethodIdOpt))
if (!options.has(paymentMethodIdOpt) || options.valueOf(paymentMethodIdOpt).isEmpty())
throw new IllegalArgumentException("no payment method id specified");
return this;

View file

@ -22,13 +22,11 @@ import joptsimple.OptionSpec;
import static bisq.cli.opts.OptLabel.OPT_SHOW_CONTRACT;
import static bisq.cli.opts.OptLabel.OPT_TRADE_ID;
import static joptsimple.internal.Strings.EMPTY;
public class GetTradeOptionParser extends AbstractMethodOptionParser implements MethodOpts {
final OptionSpec<String> tradeIdOpt = parser.accepts(OPT_TRADE_ID, "id of trade")
.withRequiredArg()
.defaultsTo(EMPTY);
.withRequiredArg();
final OptionSpec<Boolean> showContractOpt = parser.accepts(OPT_SHOW_CONTRACT, "show trade's json contract")
.withOptionalArg()
@ -46,7 +44,7 @@ public class GetTradeOptionParser extends AbstractMethodOptionParser implements
if (options.has(helpOpt))
return this;
if (!options.has(tradeIdOpt))
if (!options.has(tradeIdOpt) || options.valueOf(tradeIdOpt).isEmpty())
throw new IllegalArgumentException("no trade id specified");
return this;

View file

@ -21,13 +21,11 @@ package bisq.cli.opts;
import joptsimple.OptionSpec;
import static bisq.cli.opts.OptLabel.OPT_TRANSACTION_ID;
import static joptsimple.internal.Strings.EMPTY;
public class GetTransactionOptionParser extends AbstractMethodOptionParser implements MethodOpts {
final OptionSpec<String> txIdOpt = parser.accepts(OPT_TRANSACTION_ID, "id of transaction")
.withRequiredArg()
.defaultsTo(EMPTY);
.withRequiredArg();
public GetTransactionOptionParser(String[] args) {
super(args);
@ -40,7 +38,7 @@ public class GetTransactionOptionParser extends AbstractMethodOptionParser imple
if (options.has(helpOpt))
return this;
if (!options.has(txIdOpt))
if (!options.has(txIdOpt) || options.valueOf(txIdOpt).isEmpty())
throw new IllegalArgumentException("no tx id specified");
return this;

View file

@ -22,17 +22,14 @@ import joptsimple.OptionSpec;
import static bisq.cli.opts.OptLabel.OPT_DISPUTE_AGENT_TYPE;
import static bisq.cli.opts.OptLabel.OPT_REGISTRATION_KEY;
import static joptsimple.internal.Strings.EMPTY;
public class RegisterDisputeAgentOptionParser extends AbstractMethodOptionParser implements MethodOpts {
final OptionSpec<String> disputeAgentTypeOpt = parser.accepts(OPT_DISPUTE_AGENT_TYPE, "dispute agent type")
.withRequiredArg()
.defaultsTo(EMPTY);
.withRequiredArg();
final OptionSpec<String> registrationKeyOpt = parser.accepts(OPT_REGISTRATION_KEY, "registration key")
.withRequiredArg()
.defaultsTo(EMPTY);
.withRequiredArg();
public RegisterDisputeAgentOptionParser(String[] args) {
super(args);
@ -45,10 +42,10 @@ public class RegisterDisputeAgentOptionParser extends AbstractMethodOptionParser
if (options.has(helpOpt))
return this;
if (!options.has(disputeAgentTypeOpt))
if (!options.has(disputeAgentTypeOpt) || options.valueOf(disputeAgentTypeOpt).isEmpty())
throw new IllegalArgumentException("no dispute agent type specified");
if (!options.has(registrationKeyOpt))
if (!options.has(registrationKeyOpt) || options.valueOf(registrationKeyOpt).isEmpty())
throw new IllegalArgumentException("no registration key specified");
return this;

View file

@ -21,13 +21,11 @@ package bisq.cli.opts;
import joptsimple.OptionSpec;
import static bisq.cli.opts.OptLabel.OPT_WALLET_PASSWORD;
import static joptsimple.internal.Strings.EMPTY;
public class RemoveWalletPasswordOptionParser extends AbstractMethodOptionParser implements MethodOpts {
final OptionSpec<String> passwordOpt = parser.accepts(OPT_WALLET_PASSWORD, "bisq wallet password")
.withRequiredArg()
.defaultsTo(EMPTY);
.withRequiredArg();
public RemoveWalletPasswordOptionParser(String[] args) {
super(args);
@ -40,7 +38,7 @@ public class RemoveWalletPasswordOptionParser extends AbstractMethodOptionParser
if (options.has(helpOpt))
return this;
if (!options.has(passwordOpt))
if (!options.has(passwordOpt) || options.valueOf(passwordOpt).isEmpty())
throw new IllegalArgumentException("no password specified");
return this;

View file

@ -28,12 +28,10 @@ import static joptsimple.internal.Strings.EMPTY;
public class SendBsqOptionParser extends AbstractMethodOptionParser implements MethodOpts {
final OptionSpec<String> addressOpt = parser.accepts(OPT_ADDRESS, "destination bsq address")
.withRequiredArg()
.defaultsTo(EMPTY);
.withRequiredArg();
final OptionSpec<String> amountOpt = parser.accepts(OPT_AMOUNT, "amount of bsq to send")
.withRequiredArg()
.defaultsTo(EMPTY);
.withRequiredArg();
final OptionSpec<String> feeRateOpt = parser.accepts(OPT_TX_FEE_RATE, "optional tx fee rate (sats/byte)")
.withOptionalArg()
@ -50,10 +48,10 @@ public class SendBsqOptionParser extends AbstractMethodOptionParser implements M
if (options.has(helpOpt))
return this;
if (!options.has(addressOpt))
if (!options.has(addressOpt) || options.valueOf(addressOpt).isEmpty())
throw new IllegalArgumentException("no bsq address specified");
if (!options.has(amountOpt))
if (!options.has(amountOpt) || options.valueOf(amountOpt).isEmpty())
throw new IllegalArgumentException("no bsq amount specified");
return this;

View file

@ -29,12 +29,10 @@ import static joptsimple.internal.Strings.EMPTY;
public class SendBtcOptionParser extends AbstractMethodOptionParser implements MethodOpts {
final OptionSpec<String> addressOpt = parser.accepts(OPT_ADDRESS, "destination btc address")
.withRequiredArg()
.defaultsTo(EMPTY);
.withRequiredArg();
final OptionSpec<String> amountOpt = parser.accepts(OPT_AMOUNT, "amount of btc to send")
.withRequiredArg()
.defaultsTo(EMPTY);
.withRequiredArg();
final OptionSpec<String> feeRateOpt = parser.accepts(OPT_TX_FEE_RATE, "optional tx fee rate (sats/byte)")
.withOptionalArg()
@ -55,10 +53,10 @@ public class SendBtcOptionParser extends AbstractMethodOptionParser implements M
if (options.has(helpOpt))
return this;
if (!options.has(addressOpt))
if (!options.has(addressOpt) || options.valueOf(addressOpt).isEmpty())
throw new IllegalArgumentException("no btc address specified");
if (!options.has(amountOpt))
if (!options.has(amountOpt) || options.valueOf(amountOpt).isEmpty())
throw new IllegalArgumentException("no btc amount specified");
return this;

View file

@ -21,14 +21,12 @@ package bisq.cli.opts;
import joptsimple.OptionSpec;
import static bisq.cli.opts.OptLabel.OPT_TX_FEE_RATE;
import static joptsimple.internal.Strings.EMPTY;
public class SetTxFeeRateOptionParser extends AbstractMethodOptionParser implements MethodOpts {
final OptionSpec<String> feeRateOpt = parser.accepts(OPT_TX_FEE_RATE,
"tx fee rate preference (sats/byte)")
.withRequiredArg()
.defaultsTo(EMPTY);
.withRequiredArg();
public SetTxFeeRateOptionParser(String[] args) {
super(args);
@ -41,7 +39,7 @@ public class SetTxFeeRateOptionParser extends AbstractMethodOptionParser impleme
if (options.has(helpOpt))
return this;
if (!options.has(feeRateOpt))
if (!options.has(feeRateOpt) || options.valueOf(feeRateOpt).isEmpty())
throw new IllegalArgumentException("no tx fee rate specified");
return this;

View file

@ -27,8 +27,7 @@ import static joptsimple.internal.Strings.EMPTY;
public class SetWalletPasswordOptionParser extends AbstractMethodOptionParser implements MethodOpts {
final OptionSpec<String> passwordOpt = parser.accepts(OPT_WALLET_PASSWORD, "bisq wallet password")
.withRequiredArg()
.defaultsTo(EMPTY);
.withRequiredArg();
final OptionSpec<String> newPasswordOpt = parser.accepts(OPT_NEW_WALLET_PASSWORD, "new bisq wallet password")
.withOptionalArg()
@ -45,7 +44,7 @@ public class SetWalletPasswordOptionParser extends AbstractMethodOptionParser im
if (options.has(helpOpt))
return this;
if (!options.has(passwordOpt))
if (!options.has(passwordOpt) || options.valueOf(passwordOpt).isEmpty())
throw new IllegalArgumentException("no password specified");
return this;

View file

@ -23,17 +23,14 @@ import joptsimple.OptionSpec;
import static bisq.cli.opts.OptLabel.OPT_FEE_CURRENCY;
import static bisq.cli.opts.OptLabel.OPT_OFFER_ID;
import static bisq.cli.opts.OptLabel.OPT_PAYMENT_ACCOUNT;
import static joptsimple.internal.Strings.EMPTY;
public class TakeOfferOptionParser extends AbstractMethodOptionParser implements MethodOpts {
final OptionSpec<String> offerIdOpt = parser.accepts(OPT_OFFER_ID, "id of offer to take")
.withRequiredArg()
.defaultsTo(EMPTY);
.withRequiredArg();
final OptionSpec<String> paymentAccountIdOpt = parser.accepts(OPT_PAYMENT_ACCOUNT, "id of payment account used for trade")
.withRequiredArg()
.defaultsTo(EMPTY);
.withRequiredArg();
final OptionSpec<String> takerFeeCurrencyCodeOpt = parser.accepts(OPT_FEE_CURRENCY, "taker fee currency code (bsq|btc)")
.withOptionalArg()
@ -50,10 +47,10 @@ public class TakeOfferOptionParser extends AbstractMethodOptionParser implements
if (options.has(helpOpt))
return this;
if (!options.has(offerIdOpt))
if (!options.has(offerIdOpt) || options.valueOf(offerIdOpt).isEmpty())
throw new IllegalArgumentException("no offer id specified");
if (!options.has(paymentAccountIdOpt))
if (!options.has(paymentAccountIdOpt) || options.valueOf(paymentAccountIdOpt).isEmpty())
throw new IllegalArgumentException("no payment account id specified");
return this;

View file

@ -22,13 +22,11 @@ import joptsimple.OptionSpec;
import static bisq.cli.opts.OptLabel.OPT_TIMEOUT;
import static bisq.cli.opts.OptLabel.OPT_WALLET_PASSWORD;
import static joptsimple.internal.Strings.EMPTY;
public class UnlockWalletOptionParser extends AbstractMethodOptionParser implements MethodOpts {
final OptionSpec<String> passwordOpt = parser.accepts(OPT_WALLET_PASSWORD, "bisq wallet password")
.withRequiredArg()
.defaultsTo(EMPTY);
.withRequiredArg();
final OptionSpec<Long> unlockTimeoutOpt = parser.accepts(OPT_TIMEOUT, "wallet unlock timeout (s)")
.withRequiredArg()
@ -46,7 +44,7 @@ public class UnlockWalletOptionParser extends AbstractMethodOptionParser impleme
if (options.has(helpOpt))
return this;
if (!options.has(passwordOpt))
if (!options.has(passwordOpt) || options.valueOf(passwordOpt).isEmpty())
throw new IllegalArgumentException("no password specified");
if (!options.has(unlockTimeoutOpt) || options.valueOf(unlockTimeoutOpt) <= 0)

View file

@ -28,12 +28,10 @@ import static joptsimple.internal.Strings.EMPTY;
public class WithdrawFundsOptionParser extends AbstractMethodOptionParser implements MethodOpts {
final OptionSpec<String> tradeIdOpt = parser.accepts(OPT_TRADE_ID, "id of trade")
.withRequiredArg()
.defaultsTo(EMPTY);
.withRequiredArg();
final OptionSpec<String> addressOpt = parser.accepts(OPT_ADDRESS, "destination btc address")
.withRequiredArg()
.defaultsTo(EMPTY);
.withRequiredArg();
final OptionSpec<String> memoOpt = parser.accepts(OPT_MEMO, "optional tx memo")
.withOptionalArg()
@ -50,9 +48,12 @@ public class WithdrawFundsOptionParser extends AbstractMethodOptionParser implem
if (options.has(helpOpt))
return this;
if (!options.has(tradeIdOpt))
if (!options.has(tradeIdOpt) || options.valueOf(tradeIdOpt).isEmpty())
throw new IllegalArgumentException("no trade id specified");
if (!options.has(addressOpt) || options.valueOf(addressOpt).isEmpty())
throw new IllegalArgumentException("no destination address specified");
return this;
}

View file

@ -16,24 +16,24 @@ public class GetOffersSmokeTest {
public static void main(String[] args) {
out.println(">>> getoffers buy usd");
CliMain.main(new String[]{"--password=xyz", "getoffers", "buy", "usd"});
CliMain.main(new String[]{"--password=xyz", "getoffers", "--direction=buy", "--currency-code=usd"});
out.println(">>> getoffers sell usd");
CliMain.main(new String[]{"--password=xyz", "getoffers", "sell", "usd"});
CliMain.main(new String[]{"--password=xyz", "getoffers", "--direction=sell", "--currency-code=usd"});
out.println(">>> getoffers buy eur");
CliMain.main(new String[]{"--password=xyz", "getoffers", "buy", "eur"});
CliMain.main(new String[]{"--password=xyz", "getoffers", "--direction=buy", "--currency-code=eur"});
out.println(">>> getoffers sell eur");
CliMain.main(new String[]{"--password=xyz", "getoffers", "sell", "eur"});
CliMain.main(new String[]{"--password=xyz", "getoffers", "--direction=sell", "--currency-code=eur"});
out.println(">>> getoffers buy gbp");
CliMain.main(new String[]{"--password=xyz", "getoffers", "buy", "gbp"});
CliMain.main(new String[]{"--password=xyz", "getoffers", "--direction=buy", "--currency-code=gbp"});
out.println(">>> getoffers sell gbp");
CliMain.main(new String[]{"--password=xyz", "getoffers", "sell", "gbp"});
CliMain.main(new String[]{"--password=xyz", "getoffers", "--direction=sell", "--currency-code=gbp"});
out.println(">>> getoffers buy brl");
CliMain.main(new String[]{"--password=xyz", "getoffers", "buy", "brl"});
CliMain.main(new String[]{"--password=xyz", "getoffers", "--direction=buy", "--currency-code=brl"});
out.println(">>> getoffers sell brl");
CliMain.main(new String[]{"--password=xyz", "getoffers", "sell", "brl"});
CliMain.main(new String[]{"--password=xyz", "getoffers", "--direction=sell", "--currency-code=brl"});
}
}

View file

@ -0,0 +1,180 @@
package bisq.cli.opt;
import org.junit.jupiter.api.Test;
import static bisq.cli.Method.canceloffer;
import static bisq.cli.Method.createoffer;
import static bisq.cli.Method.createpaymentacct;
import static bisq.cli.opts.OptLabel.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import bisq.cli.opts.CancelOfferOptionParser;
import bisq.cli.opts.CreateOfferOptionParser;
import bisq.cli.opts.CreatePaymentAcctOptionParser;
public class OptionParsersTest {
private static final String PASSWORD_OPT = "--" + OPT_PASSWORD + "=" + "xyz";
// CancelOffer opt parsing tests
@Test
public void testCancelOfferWithMissingOfferIdOptShouldThrowException() {
String[] args = new String[]{
PASSWORD_OPT,
canceloffer.name()
};
Throwable exception = assertThrows(RuntimeException.class, () ->
new CancelOfferOptionParser(args).parse());
assertEquals("no offer id specified", exception.getMessage());
}
@Test
public void testCancelOfferWithEmptyOfferIdOptShouldThrowException() {
String[] args = new String[]{
PASSWORD_OPT,
canceloffer.name(),
"--" + OPT_OFFER_ID + "=" // missing opt value
};
Throwable exception = assertThrows(RuntimeException.class, () ->
new CancelOfferOptionParser(args).parse());
assertEquals("no offer id specified", exception.getMessage());
}
@Test
public void testCancelOfferWithMissingOfferIdValueShouldThrowException() {
String[] args = new String[]{
PASSWORD_OPT,
canceloffer.name(),
"--" + OPT_OFFER_ID // missing equals sign & opt value
};
Throwable exception = assertThrows(RuntimeException.class, () ->
new CancelOfferOptionParser(args).parse());
assertEquals("offer-id requires an argument", exception.getMessage());
}
@Test
public void testValidCancelOfferOpts() {
String[] args = new String[]{
PASSWORD_OPT,
canceloffer.name(),
"--" + OPT_OFFER_ID + "=" + "ABC-OFFER-ID"
};
new CancelOfferOptionParser(args).parse();
}
// CreateOffer opt parsing tests
@Test
public void testCreateOfferOptParserWithMissingPaymentAccountIdOptShouldThrowException() {
String[] args = new String[]{
PASSWORD_OPT,
createoffer.name()
};
Throwable exception = assertThrows(RuntimeException.class, () ->
new CreateOfferOptionParser(args).parse());
assertEquals("no payment account id specified", exception.getMessage());
}
@Test
public void testCreateOfferOptParserWithEmptyPaymentAccountIdOptShouldThrowException() {
String[] args = new String[]{
PASSWORD_OPT,
createoffer.name(),
"--" + OPT_PAYMENT_ACCOUNT
};
Throwable exception = assertThrows(RuntimeException.class, () ->
new CreateOfferOptionParser(args).parse());
assertEquals("payment-account requires an argument", exception.getMessage());
}
@Test
public void testCreateOfferOptParserWithMissingDirectionOptShouldThrowException() {
String[] args = new String[]{
PASSWORD_OPT,
createoffer.name(),
"--" + OPT_PAYMENT_ACCOUNT + "=" + "abc-payment-acct-id-123"
};
Throwable exception = assertThrows(RuntimeException.class, () ->
new CreateOfferOptionParser(args).parse());
assertEquals("no direction (buy|sell) specified", exception.getMessage());
}
@Test
public void testCreateOfferOptParserWithMissingDirectionOptValueShouldThrowException() {
String[] args = new String[]{
PASSWORD_OPT,
createoffer.name(),
"--" + OPT_PAYMENT_ACCOUNT + "=" + "abc-payment-acct-id-123",
"--" + OPT_DIRECTION + "=" + ""
};
Throwable exception = assertThrows(RuntimeException.class, () ->
new CreateOfferOptionParser(args).parse());
assertEquals("no direction (buy|sell) specified", exception.getMessage());
}
@Test
public void testValidCreateOfferOpts() {
String[] args = new String[]{
PASSWORD_OPT,
createoffer.name(),
"--" + OPT_PAYMENT_ACCOUNT + "=" + "abc-payment-acct-id-123",
"--" + OPT_DIRECTION + "=" + "BUY",
"--" + OPT_CURRENCY_CODE + "=" + "EUR",
"--" + OPT_AMOUNT + "=" + "0.125",
"--" + OPT_MKT_PRICE_MARGIN + "=" + "0.0",
"--" + OPT_SECURITY_DEPOSIT + "=" + "25.0"
};
CreateOfferOptionParser parser = new CreateOfferOptionParser(args).parse();
assertEquals("abc-payment-acct-id-123", parser.getPaymentAccountId());
assertEquals("BUY", parser.getDirection());
assertEquals("EUR", parser.getCurrencyCode());
assertEquals("0.125", parser.getAmount());
assertEquals("0.0", parser.getMktPriceMargin());
assertEquals("25.0", parser.getSecurityDeposit());
}
// CreatePaymentAcct opt parser tests
@Test
public void testCreatePaymentAcctOptParserWithMissingPaymentFormOptShouldThrowException() {
String[] args = new String[]{
PASSWORD_OPT,
createpaymentacct.name()
// OPT_PAYMENT_ACCOUNT_FORM
};
Throwable exception = assertThrows(RuntimeException.class, () ->
new CreatePaymentAcctOptionParser(args).parse());
assertEquals("no path to json payment account form specified", exception.getMessage());
}
@Test
public void testCreatePaymentAcctOptParserWithMissingPaymentFormOptValueShouldThrowException() {
String[] args = new String[]{
PASSWORD_OPT,
createpaymentacct.name(),
"--" + OPT_PAYMENT_ACCOUNT_FORM + "="
};
Throwable exception = assertThrows(RuntimeException.class, () ->
new CreatePaymentAcctOptionParser(args).parse());
assertEquals("no path to json payment account form specified", exception.getMessage());
}
@Test
public void testCreatePaymentAcctOptParserWithInvalidPaymentFormOptValueShouldThrowException() {
String[] args = new String[]{
PASSWORD_OPT,
createpaymentacct.name(),
"--" + OPT_PAYMENT_ACCOUNT_FORM + "=" + "/tmp/milkyway/solarsystem/mars"
};
Throwable exception = assertThrows(RuntimeException.class, () ->
new CreatePaymentAcctOptionParser(args).parse());
assertEquals("json payment account form '/tmp/milkyway/solarsystem/mars' could not be found",
exception.getMessage());
}
}

View file

@ -28,9 +28,13 @@ import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import lombok.extern.slf4j.Slf4j;
import static java.lang.String.format;
import static java.util.Arrays.stream;
import static org.apache.commons.lang3.StringUtils.capitalize;
@Slf4j
public class ReflectionUtils {
/**
@ -105,4 +109,32 @@ public class ReflectionUtils {
else
return "";
}
public static Field getField(String name, Class<?> clazz) {
Optional<Field> field = stream(clazz.getDeclaredFields())
.filter(f -> f.getName().equals(name)).findFirst();
return field.orElseThrow(() ->
new IllegalArgumentException(format("field %s not found in class %s",
name,
clazz.getSimpleName())));
}
public static Method getMethod(String name, Class<?> clazz) {
Optional<Method> method = stream(clazz.getDeclaredMethods())
.filter(m -> m.getName().equals(name)).findFirst();
return method.orElseThrow(() ->
new IllegalArgumentException(format("method %s not found in class %s",
name,
clazz.getSimpleName())));
}
public static void handleSetFieldValueError(Object object,
Field field,
ReflectiveOperationException ex) {
String errMsg = format("cannot set value of field %s, on class %s",
field.getName(),
object.getClass().getSimpleName());
log.error(capitalize(errMsg) + ".", ex);
throw new IllegalStateException("programmer error: " + errMsg);
}
}

View file

@ -54,6 +54,7 @@ import static bisq.common.util.MathUtils.scaleUpByPowerOf10;
import static bisq.core.locale.CurrencyUtil.isCryptoCurrency;
import static bisq.core.offer.OfferPayload.Direction;
import static bisq.core.offer.OfferPayload.Direction.BUY;
import static bisq.core.payment.PaymentAccountUtil.isPaymentAccountValidForOffer;
import static java.lang.String.format;
import static java.util.Comparator.comparing;
@ -101,6 +102,7 @@ class CoreOffersService {
Offer getOffer(String id) {
return offerBookService.getOffers().stream()
.filter(o -> o.getId().equals(id))
.filter(o -> !o.isMyOffer(keyRing))
.filter(o -> offerFilter.canTakeOffer(o, isApiUser).isValid())
.findAny().orElseThrow(() ->
new IllegalStateException(format("offer with id '%s' not found", id)));
@ -116,6 +118,7 @@ class CoreOffersService {
List<Offer> getOffers(String direction, String currencyCode) {
return offerBookService.getOffers().stream()
.filter(o -> !o.isMyOffer(keyRing))
.filter(o -> offerMatchesDirectionAndCurrency(o, direction, currencyCode))
.filter(o -> offerFilter.canTakeOffer(o, isApiUser).isValid())
.sorted(priceComparator(direction))
@ -174,6 +177,8 @@ class CoreOffersService {
buyerSecurityDeposit,
paymentAccount);
verifyPaymentAccountIsValidForNewOffer(offer, paymentAccount);
// We don't support atm funding from external wallet to keep it simple.
boolean useSavingsWallet = true;
//noinspection ConstantConditions
@ -210,7 +215,7 @@ class CoreOffersService {
}
void cancelOffer(String id) {
Offer offer = getOffer(id);
Offer offer = getMyOffer(id);
openOfferManager.removeOffer(offer,
() -> {
},
@ -219,6 +224,15 @@ class CoreOffersService {
});
}
private void verifyPaymentAccountIsValidForNewOffer(Offer offer, PaymentAccount paymentAccount) {
if (!isPaymentAccountValidForOffer(offer, paymentAccount)) {
String error = format("cannot create %s offer with payment account %s",
offer.getOfferPayload().getCounterCurrencyCode(),
paymentAccount.getId());
throw new IllegalStateException(error);
}
}
private void placeOffer(Offer offer,
double buyerSecurityDeposit,
long triggerPrice,

View file

@ -35,6 +35,8 @@ import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import static java.lang.String.format;
@Singleton
@Slf4j
class CorePaymentAccountsService {
@ -54,6 +56,7 @@ class CorePaymentAccountsService {
PaymentAccount createPaymentAccount(String jsonString) {
PaymentAccount paymentAccount = paymentAccountForm.toPaymentAccount(jsonString);
verifyPaymentAccountHasRequiredFields(paymentAccount);
user.addPaymentAccountIfNotExists(paymentAccount);
accountAgeWitnessService.publishMyAccountAgeWitness(paymentAccount.getPaymentAccountPayload());
log.info("Saved payment account with id {} and payment method {}.",
@ -82,4 +85,11 @@ class CorePaymentAccountsService {
File getPaymentAccountForm(String paymentMethodId) {
return paymentAccountForm.getPaymentAccountForm(paymentMethodId);
}
private void verifyPaymentAccountHasRequiredFields(PaymentAccount paymentAccount) {
// Do checks here to make sure required fields are populated.
if (paymentAccount.isTransferwiseAccount() && paymentAccount.getTradeCurrencies().isEmpty())
throw new IllegalArgumentException(format("no trade currencies defined for %s payment account",
paymentAccount.getPaymentMethod().getDisplayString().toLowerCase()));
}
}

View file

@ -136,7 +136,7 @@ public class PaymentAccountForm {
"paymentMethod",
"paymentMethodId", // This field will be included, but handled differently.
"selectedTradeCurrency",
"tradeCurrencies",
"tradeCurrencies", // This field may be included, but handled differently.
"HOLDER_NAME",
"SALT" // This field will be included, but handled differently.
};

View file

@ -20,6 +20,7 @@ package bisq.core.api.model;
import bisq.core.locale.Country;
import bisq.core.locale.FiatCurrency;
import bisq.core.locale.TradeCurrency;
import bisq.core.payment.CountryBasedPaymentAccount;
import bisq.core.payment.MoneyGramAccount;
import bisq.core.payment.PaymentAccount;
@ -35,12 +36,11 @@ 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.Function;
import java.util.function.Predicate;
import java.lang.reflect.Constructor;
@ -50,16 +50,20 @@ 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.ReflectionUtils.*;
import static bisq.common.util.Utilities.decodeFromHex;
import static bisq.core.locale.CountryUtil.findCountryByCode;
import static bisq.core.locale.CurrencyUtil.getAllTransferwiseCurrencies;
import static bisq.core.locale.CurrencyUtil.getCurrencyByCountryCode;
import static bisq.core.locale.CurrencyUtil.getTradeCurrencies;
import static bisq.core.locale.CurrencyUtil.getTradeCurrenciesInList;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.lang.String.format;
import static java.util.Arrays.stream;
import static java.util.Collections.singletonList;
import static java.util.Collections.unmodifiableMap;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;
@Slf4j
class PaymentAccountTypeAdapter extends TypeAdapter<PaymentAccount> {
@ -96,7 +100,7 @@ class PaymentAccountTypeAdapter extends TypeAdapter<PaymentAccount> {
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.isExcludedField = (f) -> stream(excludedFields).anyMatch(e -> e.equals(f.getName()));
this.fieldSettersMap = getFieldSetterMap();
}
@ -128,6 +132,9 @@ class PaymentAccountTypeAdapter extends TypeAdapter<PaymentAccount> {
}
private void writeInnerMutableFields(JsonWriter out, PaymentAccount account) {
if (account.isTransferwiseAccount())
writeTradeCurrenciesField(out, account);
fieldSettersMap.forEach((field, value) -> {
try {
// Write out a json element if there is a @Setter for this field.
@ -151,6 +158,25 @@ class PaymentAccountTypeAdapter extends TypeAdapter<PaymentAccount> {
});
}
// In some cases (TransferwiseAccount), we need to include a 'tradeCurrencies'
// field in the json form, though the 'tradeCurrencies' field has no setter method in
// the PaymentAccount class hierarchy. At of time of this change, TransferwiseAccount
// is the only known exception to the rule.
private void writeTradeCurrenciesField(JsonWriter out, PaymentAccount account) {
try {
String fieldName = "tradeCurrencies";
log.debug("Append form with non-settable field: {}", fieldName);
out.name(fieldName);
out.value("comma delimited currency code list, e.g., gbp,eur");
} 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();
@ -158,6 +184,11 @@ class PaymentAccountTypeAdapter extends TypeAdapter<PaymentAccount> {
while (in.hasNext()) {
String currentFieldName = in.nextName();
// The tradeCurrency field is common to all payment account types,
// but has no setter.
if (didReadTradeCurrenciesField(in, account, currentFieldName))
continue;
// Some of the fields are common to all payment account types.
if (didReadCommonField(in, account, currentFieldName))
continue;
@ -199,17 +230,13 @@ class PaymentAccountTypeAdapter extends TypeAdapter<PaymentAccount> {
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);
} catch (ReflectiveOperationException ex) {
handleSetFieldValueError(account, field, ex);
}
} else {
throw new IllegalStateException(
format("programmer error: cannot de-serialize json to a '%s' "
+ " because there is no setter method for field %s.",
+ " because field value cannot be set %s.",
account.getClass().getSimpleName(),
field.getName()));
}
@ -232,7 +259,7 @@ class PaymentAccountTypeAdapter extends TypeAdapter<PaymentAccount> {
.or(() -> getSetterMethodForFieldInClassHierarchy(field, paymentAccountPayloadType));
map.put(field, setter);
}
return Collections.unmodifiableMap(map);
return unmodifiableMap(map);
}
private List<Field> getOrderedFields() {
@ -274,6 +301,52 @@ class PaymentAccountTypeAdapter extends TypeAdapter<PaymentAccount> {
}
}
private final Predicate<String> isCommaDelimitedCurrencyList = (s) -> s != null && s.contains(",");
private final Function<String, List<String>> commaDelimitedCodesToList = (s) -> {
if (isCommaDelimitedCurrencyList.test(s))
return stream(s.split(",")).map(a -> a.trim().toUpperCase()).collect(toList());
else if (s != null && !s.isEmpty())
return singletonList(s.trim().toUpperCase());
else
return new ArrayList<>();
};
private boolean didReadTradeCurrenciesField(JsonReader in,
PaymentAccount account,
String fieldName) {
// The PaymentAccount.tradeCurrencies field is a special case because it has
// no setter, and we add currencies to the List here. Normally, it is an
// excluded field, TransferwiseAccount excepted.
if (fieldName.equals("tradeCurrencies")) {
String fieldValue = nextStringOrNull(in);
List<String> currencyCodes = commaDelimitedCodesToList.apply(fieldValue);
Optional<List<TradeCurrency>> tradeCurrencies;
if (account.isTransferwiseAccount())
tradeCurrencies = getTradeCurrenciesInList(currencyCodes, getAllTransferwiseCurrencies());
else
tradeCurrencies = getTradeCurrencies(currencyCodes);
if (tradeCurrencies.isPresent()) {
for (TradeCurrency tradeCurrency : tradeCurrencies.get()) {
account.addCurrency(tradeCurrency);
}
// For api users, define a selected currency.
account.setSelectedTradeCurrency(account.getTradeCurrency().orElse(null));
} else {
// Log a warning. We should not throw an exception here because the
// gson library will not pass it up to the calling Bisq class as it
// would be defined here. Do a check in a calling class to make sure
// the tradeCurrencies field is populated in the PaymentAccount
// object, if it is required for the payment account method.
log.warn("No trade currencies were found in the {} account form.",
account.getPaymentMethod().getDisplayString());
}
return true;
}
return false;
}
private boolean didReadCommonField(JsonReader in,
PaymentAccount account,
String fieldName) throws IOException {

View file

@ -43,6 +43,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@ -51,6 +52,7 @@ import java.util.stream.Stream;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.String.format;
@Slf4j
public class CurrencyUtil {
@ -486,6 +488,32 @@ public class CurrencyUtil {
return Optional.empty();
}
public static Optional<List<TradeCurrency>> getTradeCurrencies(List<String> currencyCodes) {
List<TradeCurrency> tradeCurrencies = new ArrayList<>();
currencyCodes.stream().forEachOrdered(c ->
tradeCurrencies.add(getTradeCurrency(c).orElseThrow(() ->
new IllegalArgumentException(format("%s is not a valid trade currency code", c)))));
return tradeCurrencies.isEmpty()
? Optional.empty()
: Optional.of(tradeCurrencies);
}
public static Optional<List<TradeCurrency>> getTradeCurrenciesInList(List<String> currencyCodes,
List<TradeCurrency> validCurrencies) {
Optional<List<TradeCurrency>> tradeCurrencies = getTradeCurrencies(currencyCodes);
Consumer<List<TradeCurrency>> validateCandidateCurrencies = (list) -> {
for (TradeCurrency tradeCurrency : list) {
if (!validCurrencies.contains(tradeCurrency)) {
throw new IllegalArgumentException(
format("%s is not a member of valid currencies list",
tradeCurrency.getCode()));
}
}
};
tradeCurrencies.ifPresent(validateCandidateCurrencies);
return tradeCurrencies;
}
public static FiatCurrency getCurrencyByCountryCode(String countryCode) {
if (countryCode.equals("XK"))
return new FiatCurrency("EUR");

View file

@ -134,7 +134,7 @@ public class OfferFilter {
public boolean isAnyPaymentAccountValidForOffer(Offer offer) {
return user.getPaymentAccounts() != null &&
PaymentAccountUtil.isAnyTakerPaymentAccountValidForOffer(offer, user.getPaymentAccounts());
PaymentAccountUtil.isAnyPaymentAccountValidForOffer(offer, user.getPaymentAccounts());
}
public boolean hasSameProtocolVersion(Offer offer) {

View file

@ -198,6 +198,10 @@ public abstract class PaymentAccount implements PersistablePayload {
return this instanceof MoneyGramAccount;
}
public boolean isTransferwiseAccount() {
return this instanceof TransferwiseAccount;
}
/**
* 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

@ -41,10 +41,10 @@ import javax.annotation.Nullable;
@Slf4j
public class PaymentAccountUtil {
public static boolean isAnyTakerPaymentAccountValidForOffer(Offer offer,
Collection<PaymentAccount> takerPaymentAccounts) {
for (PaymentAccount takerPaymentAccount : takerPaymentAccounts) {
if (isTakerPaymentAccountValidForOffer(offer, takerPaymentAccount))
public static boolean isAnyPaymentAccountValidForOffer(Offer offer,
Collection<PaymentAccount> paymentAccounts) {
for (PaymentAccount paymentAccount : paymentAccounts) {
if (isPaymentAccountValidForOffer(offer, paymentAccount))
return true;
}
return false;
@ -55,7 +55,7 @@ public class PaymentAccountUtil {
AccountAgeWitnessService accountAgeWitnessService) {
ObservableList<PaymentAccount> result = FXCollections.observableArrayList();
result.addAll(paymentAccounts.stream()
.filter(paymentAccount -> isTakerPaymentAccountValidForOffer(offer, paymentAccount))
.filter(paymentAccount -> isPaymentAccountValidForOffer(offer, paymentAccount))
.filter(paymentAccount -> isAmountValidForOffer(offer, paymentAccount, accountAgeWitnessService))
.collect(Collectors.toList()));
return result;
@ -79,7 +79,7 @@ public class PaymentAccountUtil {
"Payment method from offer: " + offer.getPaymentMethod().toString();
}
public static boolean isTakerPaymentAccountValidForOffer(Offer offer, PaymentAccount paymentAccount) {
public static boolean isPaymentAccountValidForOffer(Offer offer, PaymentAccount paymentAccount) {
return new ReceiptValidator(offer, paymentAccount).isValid();
}
@ -144,23 +144,4 @@ public class PaymentAccountUtil {
filter(e -> e.getPaymentAccountPayload().equals(paymentAccountPayload))
.findAny();
}
// TODO no code duplication found in UI code (added for API)
// That is optional and set to null if not supported (AltCoins,...)
/* public static String getCountryCode(PaymentAccount paymentAccount) {
if (paymentAccount instanceof CountryBasedPaymentAccount) {
Country country = ((CountryBasedPaymentAccount) paymentAccount).getCountry();
return country != null ? country.code : null;
} else {
return null;
}
}*/
// TODO no code duplication found in UI code (added for API)
/*public static long getMaxTradeLimit(AccountAgeWitnessService accountAgeWitnessService, PaymentAccount paymentAccount, String currencyCode) {
if (paymentAccount != null)
return accountAgeWitnessService.getMyTradeLimit(paymentAccount, currencyCode);
else
return 0;
}*/
}

View file

@ -41,7 +41,7 @@ class PaymentAccounts {
private final BiFunction<Offer, PaymentAccount, Boolean> validator;
PaymentAccounts(Set<PaymentAccount> accounts, AccountAgeWitnessService accountAgeWitnessService) {
this(accounts, accountAgeWitnessService, PaymentAccountUtil::isTakerPaymentAccountValidForOffer);
this(accounts, accountAgeWitnessService, PaymentAccountUtil::isPaymentAccountValidForOffer);
}
PaymentAccounts(Set<PaymentAccount> accounts, AccountAgeWitnessService accountAgeWitnessService,

View file

@ -105,46 +105,46 @@ public class OfferBookViewModelTest {
Collection<PaymentAccount> paymentAccounts;
paymentAccounts = new ArrayList<>(FXCollections.singletonObservableList(getSepaAccount("EUR", "DE", "1212324",
new ArrayList<>(Arrays.asList("AT", "DE")))));
assertTrue(PaymentAccountUtil.isAnyTakerPaymentAccountValidForOffer(
assertTrue(PaymentAccountUtil.isAnyPaymentAccountValidForOffer(
getSEPAPaymentMethod("EUR", "AT", new ArrayList<>(Arrays.asList("AT", "DE")), "PSK"), paymentAccounts));
// empty paymentAccounts
paymentAccounts = new ArrayList<>();
assertFalse(PaymentAccountUtil.isAnyTakerPaymentAccountValidForOffer(getSEPAPaymentMethod("EUR", "AT",
assertFalse(PaymentAccountUtil.isAnyPaymentAccountValidForOffer(getSEPAPaymentMethod("EUR", "AT",
new ArrayList<>(Arrays.asList("AT", "DE")), "PSK"), paymentAccounts));
// simple cases: same payment methods
// offer: alipay paymentAccount: alipay - same country, same currency
paymentAccounts = new ArrayList<>(FXCollections.singletonObservableList(getAliPayAccount("CNY")));
assertTrue(PaymentAccountUtil.isAnyTakerPaymentAccountValidForOffer(
assertTrue(PaymentAccountUtil.isAnyPaymentAccountValidForOffer(
getAliPayPaymentMethod("EUR"), paymentAccounts));
// offer: ether paymentAccount: ether - same country, same currency
paymentAccounts = new ArrayList<>(FXCollections.singletonObservableList(getCryptoAccount("ETH")));
assertTrue(PaymentAccountUtil.isAnyTakerPaymentAccountValidForOffer(
assertTrue(PaymentAccountUtil.isAnyPaymentAccountValidForOffer(
getBlockChainsPaymentMethod("ETH"), paymentAccounts));
// offer: sepa paymentAccount: sepa - same country, same currency
paymentAccounts = new ArrayList<>(FXCollections.singletonObservableList(getSepaAccount("EUR", "AT", "1212324",
new ArrayList<>(Arrays.asList("AT", "DE")))));
assertTrue(PaymentAccountUtil.isAnyTakerPaymentAccountValidForOffer(
assertTrue(PaymentAccountUtil.isAnyPaymentAccountValidForOffer(
getSEPAPaymentMethod("EUR", "AT", new ArrayList<>(Arrays.asList("AT", "DE")), "PSK"), paymentAccounts));
// offer: nationalBank paymentAccount: nationalBank - same country, same currency
paymentAccounts = new ArrayList<>(FXCollections.singletonObservableList(getNationalBankAccount("EUR", "AT", "PSK")));
assertTrue(PaymentAccountUtil.isAnyTakerPaymentAccountValidForOffer(
assertTrue(PaymentAccountUtil.isAnyPaymentAccountValidForOffer(
getNationalBankPaymentMethod("EUR", "AT", "PSK"), paymentAccounts));
// offer: SameBank paymentAccount: SameBank - same country, same currency
paymentAccounts = new ArrayList<>(FXCollections.singletonObservableList(getSameBankAccount("EUR", "AT", "PSK")));
assertTrue(PaymentAccountUtil.isAnyTakerPaymentAccountValidForOffer(
assertTrue(PaymentAccountUtil.isAnyPaymentAccountValidForOffer(
getSameBankPaymentMethod("EUR", "AT", "PSK"), paymentAccounts));
// offer: sepa paymentAccount: sepa - diff. country, same currency
paymentAccounts = new ArrayList<>(FXCollections.singletonObservableList(getSepaAccount("EUR", "DE", "1212324", new ArrayList<>(Arrays.asList("AT", "DE")))));
assertTrue(PaymentAccountUtil.isAnyTakerPaymentAccountValidForOffer(
assertTrue(PaymentAccountUtil.isAnyPaymentAccountValidForOffer(
getSEPAPaymentMethod("EUR", "AT", new ArrayList<>(Arrays.asList("AT", "DE")), "PSK"), paymentAccounts));
@ -152,79 +152,79 @@ public class OfferBookViewModelTest {
// offer: sepa paymentAccount: sepa - same country, same currency
paymentAccounts = new ArrayList<>(FXCollections.singletonObservableList(getSepaAccount("EUR", "AT", "1212324", new ArrayList<>(Arrays.asList("AT", "DE")))));
assertTrue(PaymentAccountUtil.isAnyTakerPaymentAccountValidForOffer(
assertTrue(PaymentAccountUtil.isAnyPaymentAccountValidForOffer(
getSEPAPaymentMethod("EUR", "AT", new ArrayList<>(Arrays.asList("AT", "DE")), "PSK"), paymentAccounts));
// offer: sepa paymentAccount: nationalBank - same country, same currency
// wrong method
paymentAccounts = new ArrayList<>(FXCollections.singletonObservableList(getNationalBankAccount("EUR", "AT", "PSK")));
assertFalse(PaymentAccountUtil.isAnyTakerPaymentAccountValidForOffer(
assertFalse(PaymentAccountUtil.isAnyPaymentAccountValidForOffer(
getSEPAPaymentMethod("EUR", "AT", new ArrayList<>(Arrays.asList("AT", "DE")), "PSK"), paymentAccounts));
// wrong currency
paymentAccounts = new ArrayList<>(FXCollections.singletonObservableList(getNationalBankAccount("USD", "US", "XXX")));
assertFalse(PaymentAccountUtil.isAnyTakerPaymentAccountValidForOffer(
assertFalse(PaymentAccountUtil.isAnyPaymentAccountValidForOffer(
getNationalBankPaymentMethod("EUR", "AT", "PSK"), paymentAccounts));
// wrong country
paymentAccounts = new ArrayList<>(FXCollections.singletonObservableList(getNationalBankAccount("EUR", "FR", "PSK")));
assertFalse(PaymentAccountUtil.isAnyTakerPaymentAccountValidForOffer(
assertFalse(PaymentAccountUtil.isAnyPaymentAccountValidForOffer(
getNationalBankPaymentMethod("EUR", "AT", "PSK"), paymentAccounts));
// sepa wrong country
paymentAccounts = new ArrayList<>(FXCollections.singletonObservableList(getNationalBankAccount("EUR", "CH", "PSK")));
assertFalse(PaymentAccountUtil.isAnyTakerPaymentAccountValidForOffer(
assertFalse(PaymentAccountUtil.isAnyPaymentAccountValidForOffer(
getSEPAPaymentMethod("EUR", "AT", new ArrayList<>(Arrays.asList("AT", "DE")), "PSK"), paymentAccounts));
// sepa wrong currency
paymentAccounts = new ArrayList<>(FXCollections.singletonObservableList(getNationalBankAccount("CHF", "DE", "PSK")));
assertFalse(PaymentAccountUtil.isAnyTakerPaymentAccountValidForOffer(
assertFalse(PaymentAccountUtil.isAnyPaymentAccountValidForOffer(
getSEPAPaymentMethod("EUR", "AT", new ArrayList<>(Arrays.asList("AT", "DE")), "PSK"), paymentAccounts));
// same bank
paymentAccounts = new ArrayList<>(FXCollections.singletonObservableList(getSameBankAccount("EUR", "AT", "PSK")));
assertTrue(PaymentAccountUtil.isAnyTakerPaymentAccountValidForOffer(
assertTrue(PaymentAccountUtil.isAnyPaymentAccountValidForOffer(
getNationalBankPaymentMethod("EUR", "AT", "PSK"), paymentAccounts));
// not same bank
paymentAccounts = new ArrayList<>(FXCollections.singletonObservableList(getSameBankAccount("EUR", "AT", "Raika")));
assertFalse(PaymentAccountUtil.isAnyTakerPaymentAccountValidForOffer(
assertFalse(PaymentAccountUtil.isAnyPaymentAccountValidForOffer(
getNationalBankPaymentMethod("EUR", "AT", "PSK"), paymentAccounts));
// same bank, wrong country
paymentAccounts = new ArrayList<>(FXCollections.singletonObservableList(getSameBankAccount("EUR", "DE", "PSK")));
assertFalse(PaymentAccountUtil.isAnyTakerPaymentAccountValidForOffer(
assertFalse(PaymentAccountUtil.isAnyPaymentAccountValidForOffer(
getNationalBankPaymentMethod("EUR", "AT", "PSK"), paymentAccounts));
// same bank, wrong currency
paymentAccounts = new ArrayList<>(FXCollections.singletonObservableList(getSameBankAccount("USD", "AT", "PSK")));
assertFalse(PaymentAccountUtil.isAnyTakerPaymentAccountValidForOffer(
assertFalse(PaymentAccountUtil.isAnyPaymentAccountValidForOffer(
getNationalBankPaymentMethod("EUR", "AT", "PSK"), paymentAccounts));
// spec. bank
paymentAccounts = new ArrayList<>(FXCollections.singletonObservableList(getSpecificBanksAccount("EUR", "AT", "PSK",
new ArrayList<>(Arrays.asList("PSK", "Raika")))));
assertTrue(PaymentAccountUtil.isAnyTakerPaymentAccountValidForOffer(
assertTrue(PaymentAccountUtil.isAnyPaymentAccountValidForOffer(
getNationalBankPaymentMethod("EUR", "AT", "PSK"), paymentAccounts));
// spec. bank, missing bank
paymentAccounts = new ArrayList<>(FXCollections.singletonObservableList(getSpecificBanksAccount("EUR", "AT", "PSK",
new ArrayList<>(FXCollections.singletonObservableList("Raika")))));
assertFalse(PaymentAccountUtil.isAnyTakerPaymentAccountValidForOffer(
assertFalse(PaymentAccountUtil.isAnyPaymentAccountValidForOffer(
getNationalBankPaymentMethod("EUR", "AT", "PSK"), paymentAccounts));
// spec. bank, wrong country
paymentAccounts = new ArrayList<>(FXCollections.singletonObservableList(getSpecificBanksAccount("EUR", "FR", "PSK",
new ArrayList<>(Arrays.asList("PSK", "Raika")))));
assertFalse(PaymentAccountUtil.isAnyTakerPaymentAccountValidForOffer(
assertFalse(PaymentAccountUtil.isAnyPaymentAccountValidForOffer(
getNationalBankPaymentMethod("EUR", "AT", "PSK"), paymentAccounts));
// spec. bank, wrong currency
paymentAccounts = new ArrayList<>(FXCollections.singletonObservableList(getSpecificBanksAccount("USD", "AT", "PSK",
new ArrayList<>(Arrays.asList("PSK", "Raika")))));
assertFalse(PaymentAccountUtil.isAnyTakerPaymentAccountValidForOffer(
assertFalse(PaymentAccountUtil.isAnyPaymentAccountValidForOffer(
getNationalBankPaymentMethod("EUR", "AT", "PSK"), paymentAccounts));
//TODO add more tests