general rebase in order to update payment methods and desktop app

Co-authored-by: Alva Swanson <alvasw@protonmail.com>
Co-authored-by: andyheko <haoen.ko@gmail.com>
Co-authored-by: Bisq GitHub Admin <51445974+bisq-github-admin-3@users.noreply.github.com>
Co-authored-by: BtcContributor <79100296+BtcContributor@users.noreply.github.com>
Co-authored-by: cd2357 <cd2357@users.noreply.github.com>
Co-authored-by: chimp1984 <chimp1984@gmx.com>
Co-authored-by: Chris Beams <chris@beams.io>
Co-authored-by: Christoph Atteneder <christoph.atteneder@gmail.com>
Co-authored-by: Devin Bileck <603793+devinbileck@users.noreply.github.com>
Co-authored-by: ghubstan <36207203+ghubstan@users.noreply.github.com>
Co-authored-by: Huey <hueydane@gmail.com>
Co-authored-by: Jakub Loucký <jakub.loucky@outlook.cz>
Co-authored-by: jmacxx <47253594+jmacxx@users.noreply.github.com>
Co-authored-by: KanoczTomas <tomas.kanocz@cnl.sk>
Co-authored-by: m52go <735155+m52go@users.noreply.github.com>
Co-authored-by: Marcus0x <marcus0x@xrhodium.org>
Co-authored-by: MarnixCroes <93143998+MarnixCroes@users.noreply.github.com>
Co-authored-by: Martin Harrigan <martinharrigan@gmail.com>
Co-authored-by: MwithM <50149324+MwithM@users.noreply.github.com>
Co-authored-by: sqrrm <sqrrm@users.noreply.github.com>
Co-authored-by: Stan <36207203+ghubstan@users.noreply.github.com>
Co-authored-by: Stephan Oeste <emzy@emzy.de>
Co-authored-by: Steven Barclay <stejbac@gmail.com>
Co-authored-by: WAT <shiido.it@gmail.com>
Co-authored-by: wiz <j@wiz.biz>
Co-authored-by: xyzmaker123 <84982606+xyzmaker123@users.noreply.github.com>
This commit is contained in:
woodser 2022-05-26 13:42:10 -04:00
parent 15a1fe8a36
commit 88578bed10
539 changed files with 27629 additions and 8178 deletions

View file

@ -17,7 +17,8 @@
package bisq.apitest;
import java.io.File;
import java.time.Duration;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
@ -29,23 +30,19 @@ import javax.annotation.Nullable;
import org.junit.jupiter.api.TestInfo;
import static bisq.apitest.config.ApiTestRateMeterInterceptorConfig.getTestRateMeterInterceptorConfig;
import static bisq.apitest.config.HavenoAppConfig.alicedaemon;
import static bisq.apitest.config.HavenoAppConfig.arbdaemon;
import static bisq.apitest.config.HavenoAppConfig.bobdaemon;
import static bisq.proto.grpc.DisputeAgentsGrpc.getRegisterDisputeAgentMethod;
import static bisq.proto.grpc.GetVersionGrpc.getGetVersionMethod;
import static com.google.common.util.concurrent.Uninterruptibles.sleepUninterruptibly;
import static java.net.InetAddress.getLoopbackAddress;
import static java.util.Arrays.stream;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import bisq.apitest.config.ApiTestConfig;
import bisq.apitest.method.BitcoinCliHelper;
import bisq.cli.GrpcClient;
import bisq.daemon.grpc.GrpcVersionService;
import bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig;
/**
* Base class for all test types: 'method', 'scenario' and 'e2e'.
@ -90,7 +87,7 @@ public class ApiTestCase {
throws InterruptedException, ExecutionException, IOException {
String[] params = new String[]{
"--supportingApps", stream(supportingApps).map(Enum::name).collect(Collectors.joining(",")),
"--callRateMeteringConfigPath", defaultRateMeterInterceptorConfig().getAbsolutePath(),
"--callRateMeteringConfigPath", getTestRateMeterInterceptorConfig().getAbsolutePath(),
"--enableBisqDebugging", "false"
};
setUpScaffold(params);
@ -136,11 +133,7 @@ public class ApiTestCase {
}
protected static void sleep(long ms) {
try {
MILLISECONDS.sleep(ms);
} catch (InterruptedException ignored) {
// empty
}
sleepUninterruptibly(Duration.ofMillis(ms));
}
protected final String testName(TestInfo testInfo) {
@ -148,37 +141,4 @@ public class ApiTestCase {
? testInfo.getTestMethod().get().getName()
: "unknown test name";
}
protected static File defaultRateMeterInterceptorConfig() {
GrpcServiceRateMeteringConfig.Builder builder = new GrpcServiceRateMeteringConfig.Builder();
builder.addCallRateMeter(GrpcVersionService.class.getSimpleName(),
getGetVersionMethod().getFullMethodName(),
1,
SECONDS);
// Only GrpcVersionService is @VisibleForTesting, so we need to
// hardcode other grpcServiceClassName parameter values used in
// builder.addCallRateMeter(...).
builder.addCallRateMeter("GrpcDisputeAgentsService",
getRegisterDisputeAgentMethod().getFullMethodName(),
10, // Same as default.
SECONDS);
// Define rate meters for non-existent method 'disabled', to override other grpc
// services' default rate meters -- defined in their rateMeteringInterceptor()
// methods.
String[] serviceClassNames = new String[]{
"GrpcGetTradeStatisticsService",
"GrpcHelpService",
"GrpcOffersService",
"GrpcPaymentAccountsService",
"GrpcPriceService",
"GrpcTradesService",
"GrpcWalletsService"
};
for (String service : serviceClassNames) {
builder.addCallRateMeter(service, "disabled", 1, MILLISECONDS);
}
File file = builder.build();
file.deleteOnExit();
return file;
}
}

View file

@ -19,17 +19,35 @@ package bisq.apitest.method;
import bisq.core.api.model.PaymentAccountForm;
import bisq.core.payment.F2FAccount;
import bisq.core.payment.NationalBankAccount;
import bisq.core.proto.CoreProtoResolver;
import bisq.common.util.Utilities;
import bisq.proto.grpc.BalancesInfo;
import io.grpc.Status;
import io.grpc.StatusRuntimeException;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import javax.annotation.Nullable;
import static bisq.apitest.config.ApiTestConfig.BTC;
import static bisq.apitest.config.ApiTestRateMeterInterceptorConfig.getTestRateMeterInterceptorConfig;
import static bisq.cli.table.builder.TableType.BTC_BALANCE_TBL;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Arrays.stream;
import static org.junit.jupiter.api.Assertions.fail;
@ -37,7 +55,9 @@ import static org.junit.jupiter.api.Assertions.fail;
import bisq.apitest.ApiTestCase;
import bisq.apitest.linux.BashCommand;
import bisq.cli.GrpcClient;
import bisq.cli.table.builder.TableBuilder;
public class MethodTest extends ApiTestCase {
@ -46,15 +66,6 @@ public class MethodTest extends ApiTestCase {
private static final Function<Enum<?>[], String> toNameList = (enums) ->
stream(enums).map(Enum::name).collect(Collectors.joining(","));
public static void startSupportingApps(File callRateMeteringConfigFile,
boolean generateBtcBlock,
Enum<?>... supportingApps) {
startSupportingApps(callRateMeteringConfigFile,
generateBtcBlock,
false,
supportingApps);
}
public static void startSupportingApps(File callRateMeteringConfigFile,
boolean generateBtcBlock,
boolean startSupportingAppsInDebugMode,
@ -71,19 +82,12 @@ public class MethodTest extends ApiTestCase {
}
}
public static void startSupportingApps(boolean generateBtcBlock,
Enum<?>... supportingApps) {
startSupportingApps(generateBtcBlock,
false,
supportingApps);
}
public static void startSupportingApps(boolean generateBtcBlock,
boolean startSupportingAppsInDebugMode,
Enum<?>... supportingApps) {
try {
// Disable call rate metering where there is no callRateMeteringConfigFile.
File callRateMeteringConfigFile = defaultRateMeterInterceptorConfig();
File callRateMeteringConfigFile = getTestRateMeterInterceptorConfig();
setUpScaffold(new String[]{
"--supportingApps", toNameList.apply(supportingApps),
"--callRateMeteringConfigPath", callRateMeteringConfigFile.getAbsolutePath(),
@ -133,17 +137,94 @@ public class MethodTest extends ApiTestCase {
return f2FAccount;
}
protected bisq.core.payment.PaymentAccount createDummyBRLAccount(GrpcClient grpcClient,
String holderName,
String nationalAccountId,
String holderTaxId) {
String nationalBankAccountJsonString = "{\n" +
" \"_COMMENTS_\": [ \"Dummy Account\" ],\n" +
" \"paymentMethodId\": \"NATIONAL_BANK\",\n" +
" \"accountName\": \"Banco do Brasil\",\n" +
" \"country\": \"BR\",\n" +
" \"bankName\": \"Banco do Brasil\",\n" +
" \"branchId\": \"456789-10\",\n" +
" \"holderName\": \"" + holderName + "\",\n" +
" \"accountNr\": \"456789-87\",\n" +
" \"nationalAccountId\": \"" + nationalAccountId + "\",\n" +
" \"holderTaxId\": \"" + holderTaxId + "\"\n" +
"}\n";
NationalBankAccount nationalBankAccount =
(NationalBankAccount) createPaymentAccount(grpcClient, nationalBankAccountJsonString);
return nationalBankAccount;
}
protected final bisq.core.payment.PaymentAccount createPaymentAccount(GrpcClient grpcClient, String jsonString) {
// Normally, we do asserts on the protos from the gRPC service, but in this
// case we need a bisq.core.payment.PaymentAccount so it can be cast to its
// sub type.
// sub-type.
var paymentAccount = grpcClient.createPaymentAccount(jsonString);
return bisq.core.payment.PaymentAccount.fromProto(paymentAccount, CORE_PROTO_RESOLVER);
}
// Static conveniences for test methods and test case fixture setups.
public static final Supplier<Double> defaultBuyerSecurityDepositPct = () -> {
var defaultPct = BigDecimal.valueOf(getDefaultBuyerSecurityDepositAsPercent());
if (defaultPct.precision() != 2)
throw new IllegalStateException(format(
"Unexpected decimal precision, expected 2 but actual is %d%n."
+ "Check for changes to Restrictions.getDefaultBuyerSecurityDepositAsPercent()",
defaultPct.precision()));
return defaultPct.movePointRight(2).doubleValue();
};
public static String formatBalancesTbls(BalancesInfo allBalances) {
StringBuilder balances = new StringBuilder(BTC).append("\n");
balances.append(new TableBuilder(BTC_BALANCE_TBL, allBalances.getBtc()).build());
balances.append("\n");
return balances.toString();
}
protected static String encodeToHex(String s) {
return Utilities.bytesAsHexString(s.getBytes(UTF_8));
}
protected static Status.Code getStatusRuntimeExceptionStatusCode(Exception grpcException) {
if (grpcException instanceof StatusRuntimeException)
return ((StatusRuntimeException) grpcException).getStatus().getCode();
else
throw new IllegalArgumentException(
format("Expected a io.grpc.StatusRuntimeException argument, but got a %s",
grpcException.getClass().getName()));
}
protected void verifyNoLoggedNodeExceptions() {
var loggedExceptions = getNodeExceptionMessages();
if (loggedExceptions != null) {
String err = format("Exception(s) found in daemon log(s):%n%s", loggedExceptions);
fail(err);
}
}
protected void printNodeExceptionMessages(Logger log) {
var loggedExceptions = getNodeExceptionMessages();
if (loggedExceptions != null)
log.error("Exception(s) found in daemon log(s):\n{}", loggedExceptions);
}
@Nullable
protected static String getNodeExceptionMessages() {
var nodeLogsSpec = config.rootAppDataDir.getAbsolutePath() + "/bisq-BTC_REGTEST_*_dao/bisq.log";
var grep = "grep Exception " + nodeLogsSpec;
var bashCommand = new BashCommand(grep);
try {
bashCommand.run();
} catch (IOException | InterruptedException ex) {
fail("Bash command execution error: " + ex);
}
if (bashCommand.getError() == null)
return bashCommand.getOutput();
else
throw new IllegalStateException("Bash command execution error: " + bashCommand.getError());
}
}

View file

@ -61,7 +61,7 @@ public class RegisterDisputeAgentsTest extends MethodTest {
public void testRegisterArbitratorShouldThrowException() {
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
arbClient.registerDisputeAgent(ARBITRATOR, DEV_PRIVILEGE_PRIV_KEY));
assertEquals("INVALID_ARGUMENT: arbitrators must be registered in a Bisq UI",
assertEquals("UNIMPLEMENTED: arbitrators must be registered in a Bisq UI",
exception.getMessage());
}

View file

@ -17,13 +17,15 @@
package bisq.apitest.method.offer;
import bisq.core.monetary.Altcoin;
import bisq.proto.grpc.OfferInfo;
import protobuf.PaymentAccount;
import org.bitcoinj.utils.Fiat;
import java.math.BigDecimal;
import java.math.MathContext;
import java.util.List;
import java.util.function.Function;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
@ -32,49 +34,96 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
import static bisq.apitest.config.ApiTestConfig.XMR;
import static bisq.apitest.config.HavenoAppConfig.alicedaemon;
import static bisq.apitest.config.HavenoAppConfig.arbdaemon;
import static bisq.apitest.config.HavenoAppConfig.bobdaemon;
import static bisq.apitest.config.HavenoAppConfig.seednode;
import static bisq.common.util.MathUtils.roundDouble;
import static bisq.common.util.MathUtils.scaleDownByPowerOf10;
import static bisq.core.locale.CurrencyUtil.isCryptoCurrency;
import static java.math.RoundingMode.HALF_UP;
import static bisq.cli.table.builder.TableType.OFFER_TBL;
import static java.lang.String.format;
import static java.lang.System.out;
import bisq.apitest.method.MethodTest;
import bisq.cli.CliMain;
import bisq.cli.table.builder.TableBuilder;
@Slf4j
public abstract class AbstractOfferTest extends MethodTest {
protected static final int ACTIVATE_OFFER = 1;
protected static final int DEACTIVATE_OFFER = 0;
protected static final String NO_TRIGGER_PRICE = "0";
@Setter
protected static boolean isLongRunningTest;
protected static PaymentAccount alicesBtcAcct;
protected static PaymentAccount bobsBtcAcct;
protected static PaymentAccount alicesXmrAcct;
protected static PaymentAccount bobsXmrAcct;
@BeforeAll
public static void setUp() {
setUp(false);
}
public static void setUp(boolean startSupportingAppsInDebugMode) {
startSupportingApps(true,
false,
startSupportingAppsInDebugMode,
bitcoind,
seednode,
arbdaemon,
alicedaemon,
bobdaemon);
initPaymentAccounts();
}
protected double getScaledOfferPrice(double offerPrice, String currencyCode) {
int precision = isCryptoCurrency(currencyCode) ? Altcoin.SMALLEST_UNIT_EXPONENT : Fiat.SMALLEST_UNIT_EXPONENT;
return scaleDownByPowerOf10(offerPrice, precision);
protected static final Function<OfferInfo, String> toOfferTable = (offer) ->
new TableBuilder(OFFER_TBL, offer).build().toString();
protected static final Function<List<OfferInfo>, String> toOffersTable = (offers) ->
new TableBuilder(OFFER_TBL, offers).build().toString();
protected static String calcPriceAsString(double base, double delta, int precision) {
var mathContext = new MathContext(precision);
var priceAsBigDecimal = new BigDecimal(Double.toString(base), mathContext)
.add(new BigDecimal(Double.toString(delta), mathContext))
.round(mathContext);
return format("%." + precision + "f", priceAsBigDecimal.doubleValue());
}
protected final double getPercentageDifference(double price1, double price2) {
return BigDecimal.valueOf(roundDouble((1 - (price1 / price2)), 5))
.setScale(4, HALF_UP)
.doubleValue();
@SuppressWarnings("ConstantConditions")
public static void initPaymentAccounts() {
alicesBtcAcct = aliceClient.getPaymentAccount("BTC");
bobsBtcAcct = bobClient.getPaymentAccount("BTC");
}
@SuppressWarnings("ConstantConditions")
public static void createXmrPaymentAccounts() {
alicesXmrAcct = aliceClient.createCryptoCurrencyPaymentAccount("Alice's XMR Account",
XMR,
"44G4jWmSvTEfifSUZzTDnJVLPvYATmq9XhhtDqUof1BGCLceG82EQsVYG9Q9GN4bJcjbAJEc1JD1m5G7iK4UPZqACubV4Mq",
false);
log.trace("Alices XMR Account: {}", alicesXmrAcct);
bobsXmrAcct = bobClient.createCryptoCurrencyPaymentAccount("Bob's XMR Account",
XMR,
"4BDRhdSBKZqAXs3PuNTbMtaXBNqFj5idC2yMVnQj8Rm61AyKY8AxLTt9vGRJ8pwcG4EtpyD8YpGqdZWCZ2VZj6yVBN2RVKs",
false);
log.trace("Bob's XMR Account: {}", bobsXmrAcct);
}
@AfterAll
public static void tearDown() {
tearDownScaffold();
}
protected static void runCliGetOffer(String offerId) {
out.println("Alice's CLI 'getmyoffer' response:");
CliMain.main(new String[]{"--password=xyz", "--port=9998", "getmyoffer", "--offer-id=" + offerId});
out.println("Bob's CLI 'getoffer' response:");
CliMain.main(new String[]{"--password=xyz", "--port=9999", "getoffer", "--offer-id=" + offerId});
}
}

View file

@ -32,9 +32,8 @@ import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static protobuf.OfferPayload.Direction.BUY;
import static protobuf.OfferDirection.BUY;
@Disabled
@Slf4j
@ -51,8 +50,9 @@ public class CancelOfferTest extends AbstractOfferTest {
10000000L,
10000000L,
0.00,
getDefaultBuyerSecurityDepositAsPercent(),
paymentAccountId);
defaultBuyerSecurityDepositPct.get(),
paymentAccountId,
NO_TRIGGER_PRICE);
};
@Test

View file

@ -28,14 +28,14 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.config.ApiTestConfig.XMR;
import static bisq.cli.TableFormat.formatOfferTable;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static java.util.Collections.singletonList;
import static bisq.apitest.config.ApiTestConfig.EUR;
import static bisq.apitest.config.ApiTestConfig.USD;
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 protobuf.OfferPayload.Direction.BUY;
import static protobuf.OfferPayload.Direction.SELL;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static protobuf.OfferDirection.BUY;
import static protobuf.OfferDirection.SELL;
@Disabled
@Slf4j
@ -44,35 +44,44 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
@Test
@Order(1)
public void testCreateAUDXMRBuyOfferUsingFixedPrice16000() {
public void testCreateAUDBTCBuyOfferUsingFixedPrice16000() {
PaymentAccount audAccount = createDummyF2FAccount(aliceClient, "AU");
var newOffer = aliceClient.createFixedPricedOffer(BUY.name(),
"aud",
10_000_000L,
10_000_000L,
"36000",
getDefaultBuyerSecurityDepositAsPercent(),
defaultBuyerSecurityDepositPct.get(),
audAccount.getId());
log.info("OFFER #1:\n{}", formatOfferTable(singletonList(newOffer), "AUD"));
log.debug("Offer #1:\n{}", toOfferTable.apply(newOffer));
assertTrue(newOffer.getIsMyOffer());
assertFalse(newOffer.getIsActivated());
String newOfferId = newOffer.getId();
assertNotEquals("", newOfferId);
assertEquals(BUY.name(), newOffer.getDirection());
assertFalse(newOffer.getUseMarketBasedPrice());
assertEquals(360_000_000, newOffer.getPrice());
assertEquals("36000.0000", newOffer.getPrice());
assertEquals(10_000_000, newOffer.getAmount());
assertEquals(10_000_000, newOffer.getMinAmount());
assertEquals("3600", newOffer.getVolume());
assertEquals("3600", newOffer.getMinVolume());
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
assertEquals(audAccount.getId(), newOffer.getPaymentAccountId());
assertEquals(XMR, newOffer.getBaseCurrencyCode());
assertEquals("AUD", newOffer.getCounterCurrencyCode());
newOffer = aliceClient.getMyOffer(newOfferId);
newOffer = aliceClient.getOffer(newOfferId);
assertTrue(newOffer.getIsMyOffer());
assertTrue(newOffer.getIsActivated());
assertEquals(newOfferId, newOffer.getId());
assertEquals(BUY.name(), newOffer.getDirection());
assertFalse(newOffer.getUseMarketBasedPrice());
assertEquals(360_000_000, newOffer.getPrice());
assertEquals("36000.0000", newOffer.getPrice());
assertEquals(10_000_000, newOffer.getAmount());
assertEquals(10_000_000, newOffer.getMinAmount());
assertEquals("3600", newOffer.getVolume());
assertEquals("3600", newOffer.getMinVolume());
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
assertEquals(audAccount.getId(), newOffer.getPaymentAccountId());
assertEquals(XMR, newOffer.getBaseCurrencyCode());
@ -81,75 +90,93 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
@Test
@Order(2)
public void testCreateUSDXMRBuyOfferUsingFixedPrice100001234() {
public void testCreateUSDBTCBuyOfferUsingFixedPrice100001234() {
PaymentAccount usdAccount = createDummyF2FAccount(aliceClient, "US");
var newOffer = aliceClient.createFixedPricedOffer(BUY.name(),
"usd",
10_000_000L,
10_000_000L,
"30000.1234",
getDefaultBuyerSecurityDepositAsPercent(),
defaultBuyerSecurityDepositPct.get(),
usdAccount.getId());
log.info("OFFER #2:\n{}", formatOfferTable(singletonList(newOffer), "USD"));
log.debug("Offer #2:\n{}", toOfferTable.apply(newOffer));
assertTrue(newOffer.getIsMyOffer());
assertFalse(newOffer.getIsActivated());
String newOfferId = newOffer.getId();
assertNotEquals("", newOfferId);
assertEquals(BUY.name(), newOffer.getDirection());
assertFalse(newOffer.getUseMarketBasedPrice());
assertEquals(300_001_234, newOffer.getPrice());
assertEquals("30000.1234", newOffer.getPrice());
assertEquals(10_000_000, newOffer.getAmount());
assertEquals(10_000_000, newOffer.getMinAmount());
assertEquals("3000", newOffer.getVolume());
assertEquals("3000", newOffer.getMinVolume());
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
assertEquals(XMR, newOffer.getBaseCurrencyCode());
assertEquals("USD", newOffer.getCounterCurrencyCode());
assertEquals(USD, newOffer.getCounterCurrencyCode());
newOffer = aliceClient.getMyOffer(newOfferId);
newOffer = aliceClient.getOffer(newOfferId);
assertTrue(newOffer.getIsMyOffer());
assertTrue(newOffer.getIsActivated());
assertEquals(newOfferId, newOffer.getId());
assertEquals(BUY.name(), newOffer.getDirection());
assertFalse(newOffer.getUseMarketBasedPrice());
assertEquals(300_001_234, newOffer.getPrice());
assertEquals("30000.1234", newOffer.getPrice());
assertEquals(10_000_000, newOffer.getAmount());
assertEquals(10_000_000, newOffer.getMinAmount());
assertEquals("3000", newOffer.getVolume());
assertEquals("3000", newOffer.getMinVolume());
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
assertEquals(XMR, newOffer.getBaseCurrencyCode());
assertEquals("USD", newOffer.getCounterCurrencyCode());
assertEquals(USD, newOffer.getCounterCurrencyCode());
}
@Test
@Order(3)
public void testCreateEURXMRSellOfferUsingFixedPrice95001234() {
public void testCreateEURBTCSellOfferUsingFixedPrice95001234() {
PaymentAccount eurAccount = createDummyF2FAccount(aliceClient, "FR");
var newOffer = aliceClient.createFixedPricedOffer(SELL.name(),
"eur",
10_000_000L,
5_000_000L,
"29500.1234",
getDefaultBuyerSecurityDepositAsPercent(),
defaultBuyerSecurityDepositPct.get(),
eurAccount.getId());
log.info("OFFER #3:\n{}", formatOfferTable(singletonList(newOffer), "EUR"));
log.debug("Offer #3:\n{}", toOfferTable.apply(newOffer));
assertTrue(newOffer.getIsMyOffer());
assertFalse(newOffer.getIsActivated());
String newOfferId = newOffer.getId();
assertNotEquals("", newOfferId);
assertEquals(SELL.name(), newOffer.getDirection());
assertFalse(newOffer.getUseMarketBasedPrice());
assertEquals(295_001_234, newOffer.getPrice());
assertEquals("29500.1234", newOffer.getPrice());
assertEquals(10_000_000, newOffer.getAmount());
assertEquals(5_000_000, newOffer.getMinAmount());
assertEquals("2950", newOffer.getVolume());
assertEquals("1475", newOffer.getMinVolume());
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
assertEquals(eurAccount.getId(), newOffer.getPaymentAccountId());
assertEquals(XMR, newOffer.getBaseCurrencyCode());
assertEquals("EUR", newOffer.getCounterCurrencyCode());
assertEquals(EUR, newOffer.getCounterCurrencyCode());
newOffer = aliceClient.getMyOffer(newOfferId);
newOffer = aliceClient.getOffer(newOfferId);
assertTrue(newOffer.getIsMyOffer());
assertTrue(newOffer.getIsActivated());
assertEquals(newOfferId, newOffer.getId());
assertEquals(SELL.name(), newOffer.getDirection());
assertFalse(newOffer.getUseMarketBasedPrice());
assertEquals(295_001_234, newOffer.getPrice());
assertEquals("29500.1234", newOffer.getPrice());
assertEquals(10_000_000, newOffer.getAmount());
assertEquals(5_000_000, newOffer.getMinAmount());
assertEquals("2950", newOffer.getVolume());
assertEquals("1475", newOffer.getMinVolume());
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
assertEquals(eurAccount.getId(), newOffer.getPaymentAccountId());
assertEquals(XMR, newOffer.getBaseCurrencyCode());
assertEquals("EUR", newOffer.getCounterCurrencyCode());
assertEquals(EUR, newOffer.getCounterCurrencyCode());
}
}

View file

@ -23,6 +23,8 @@ import bisq.proto.grpc.OfferInfo;
import java.text.DecimalFormat;
import java.math.BigDecimal;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Disabled;
@ -31,20 +33,22 @@ import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.config.ApiTestConfig.XMR;
import static bisq.cli.TableFormat.formatOfferTable;
import static bisq.apitest.config.ApiTestConfig.BTC;
import static bisq.apitest.config.ApiTestConfig.USD;
import static bisq.common.util.MathUtils.roundDouble;
import static bisq.common.util.MathUtils.scaleDownByPowerOf10;
import static bisq.common.util.MathUtils.scaleUpByPowerOf10;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static java.lang.Math.abs;
import static java.lang.String.format;
import static java.util.Collections.singletonList;
import static java.math.RoundingMode.HALF_UP;
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 protobuf.OfferPayload.Direction.BUY;
import static protobuf.OfferPayload.Direction.SELL;
import static protobuf.OfferDirection.BUY;
import static protobuf.OfferDirection.SELL;
@SuppressWarnings("ConstantConditions")
@Disabled
@Slf4j
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@ -54,77 +58,95 @@ 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 testCreateUSDXMRBuyOffer5PctPriceMargin() {
public void testCreateUSDBTCBuyOffer5PctPriceMargin() {
PaymentAccount usdAccount = createDummyF2FAccount(aliceClient, "US");
double priceMarginPctInput = 5.00;
double priceMarginPctInput = 5.00d;
var newOffer = aliceClient.createMarketBasedPricedOffer(BUY.name(),
"usd",
10_000_000L,
10_000_000L,
priceMarginPctInput,
getDefaultBuyerSecurityDepositAsPercent(),
usdAccount.getId());
log.info("OFFER #1:\n{}", formatOfferTable(singletonList(newOffer), "usd"));
defaultBuyerSecurityDepositPct.get(),
usdAccount.getId(),
NO_TRIGGER_PRICE);
log.debug("Offer #1:\n{}", toOfferTable.apply(newOffer));
assertTrue(newOffer.getIsMyOffer());
assertFalse(newOffer.getIsActivated());
String newOfferId = newOffer.getId();
assertNotEquals("", newOfferId);
assertEquals(BUY.name(), newOffer.getDirection());
assertTrue(newOffer.getUseMarketBasedPrice());
assertEquals(priceMarginPctInput, newOffer.getMarketPriceMarginPct());
assertEquals(10_000_000, newOffer.getAmount());
assertEquals(10_000_000, newOffer.getMinAmount());
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
assertEquals(XMR, newOffer.getBaseCurrencyCode());
assertEquals("USD", newOffer.getCounterCurrencyCode());
assertEquals(BTC, newOffer.getBaseCurrencyCode());
assertEquals(USD, newOffer.getCounterCurrencyCode());
newOffer = aliceClient.getMyOffer(newOfferId);
newOffer = aliceClient.getOffer(newOfferId);
assertTrue(newOffer.getIsMyOffer());
assertTrue(newOffer.getIsActivated());
assertEquals(newOfferId, newOffer.getId());
assertEquals(BUY.name(), newOffer.getDirection());
assertTrue(newOffer.getUseMarketBasedPrice());
assertEquals(priceMarginPctInput, newOffer.getMarketPriceMarginPct());
assertEquals(10_000_000, newOffer.getAmount());
assertEquals(10_000_000, newOffer.getMinAmount());
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
assertEquals(XMR, newOffer.getBaseCurrencyCode());
assertEquals("USD", newOffer.getCounterCurrencyCode());
assertEquals(BTC, newOffer.getBaseCurrencyCode());
assertEquals(USD, newOffer.getCounterCurrencyCode());
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
}
@Test
@Order(2)
public void testCreateNZDXMRBuyOfferMinus2PctPriceMargin() {
public void testCreateNZDBTCBuyOfferMinus2PctPriceMargin() {
PaymentAccount nzdAccount = createDummyF2FAccount(aliceClient, "NZ");
double priceMarginPctInput = -2.00;
double priceMarginPctInput = -2.00d; // -2%
var newOffer = aliceClient.createMarketBasedPricedOffer(BUY.name(),
"nzd",
10_000_000L,
10_000_000L,
priceMarginPctInput,
getDefaultBuyerSecurityDepositAsPercent(),
nzdAccount.getId());
log.info("OFFER #2:\n{}", formatOfferTable(singletonList(newOffer), "nzd"));
defaultBuyerSecurityDepositPct.get(),
nzdAccount.getId(),
NO_TRIGGER_PRICE);
log.debug("Offer #2:\n{}", toOfferTable.apply(newOffer));
assertTrue(newOffer.getIsMyOffer());
assertFalse(newOffer.getIsActivated());
String newOfferId = newOffer.getId();
assertNotEquals("", newOfferId);
assertEquals(BUY.name(), newOffer.getDirection());
assertTrue(newOffer.getUseMarketBasedPrice());
assertEquals(priceMarginPctInput, newOffer.getMarketPriceMarginPct());
assertEquals(10_000_000, newOffer.getAmount());
assertEquals(10_000_000, newOffer.getMinAmount());
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
assertEquals(nzdAccount.getId(), newOffer.getPaymentAccountId());
assertEquals(XMR, newOffer.getBaseCurrencyCode());
assertEquals(BTC, newOffer.getBaseCurrencyCode());
assertEquals("NZD", newOffer.getCounterCurrencyCode());
newOffer = aliceClient.getMyOffer(newOfferId);
newOffer = aliceClient.getOffer(newOfferId);
assertTrue(newOffer.getIsMyOffer());
assertTrue(newOffer.getIsActivated());
assertEquals(newOfferId, newOffer.getId());
assertEquals(BUY.name(), newOffer.getDirection());
assertTrue(newOffer.getUseMarketBasedPrice());
assertEquals(priceMarginPctInput, newOffer.getMarketPriceMarginPct());
assertEquals(10_000_000, newOffer.getAmount());
assertEquals(10_000_000, newOffer.getMinAmount());
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
assertEquals(nzdAccount.getId(), newOffer.getPaymentAccountId());
assertEquals(XMR, newOffer.getBaseCurrencyCode());
assertEquals(BTC, newOffer.getBaseCurrencyCode());
assertEquals("NZD", newOffer.getCounterCurrencyCode());
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
@ -132,7 +154,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
@Test
@Order(3)
public void testCreateGBPXMRSellOfferMinus1Point5PctPriceMargin() {
public void testCreateGBPBTCSellOfferMinus1Point5PctPriceMargin() {
PaymentAccount gbpAccount = createDummyF2FAccount(aliceClient, "GB");
double priceMarginPctInput = -1.5;
var newOffer = aliceClient.createMarketBasedPricedOffer(SELL.name(),
@ -140,29 +162,37 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
10_000_000L,
5_000_000L,
priceMarginPctInput,
getDefaultBuyerSecurityDepositAsPercent(),
gbpAccount.getId());
log.info("OFFER #3:\n{}", formatOfferTable(singletonList(newOffer), "gbp"));
defaultBuyerSecurityDepositPct.get(),
gbpAccount.getId(),
NO_TRIGGER_PRICE);
log.debug("Offer #3:\n{}", toOfferTable.apply(newOffer));
assertTrue(newOffer.getIsMyOffer());
assertFalse(newOffer.getIsActivated());
String newOfferId = newOffer.getId();
assertNotEquals("", newOfferId);
assertEquals(SELL.name(), newOffer.getDirection());
assertTrue(newOffer.getUseMarketBasedPrice());
assertEquals(priceMarginPctInput, newOffer.getMarketPriceMarginPct());
assertEquals(10_000_000, newOffer.getAmount());
assertEquals(5_000_000, newOffer.getMinAmount());
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
assertEquals(gbpAccount.getId(), newOffer.getPaymentAccountId());
assertEquals(XMR, newOffer.getBaseCurrencyCode());
assertEquals(BTC, newOffer.getBaseCurrencyCode());
assertEquals("GBP", newOffer.getCounterCurrencyCode());
newOffer = aliceClient.getMyOffer(newOfferId);
newOffer = aliceClient.getOffer(newOfferId);
assertTrue(newOffer.getIsMyOffer());
assertTrue(newOffer.getIsActivated());
assertEquals(newOfferId, newOffer.getId());
assertEquals(SELL.name(), newOffer.getDirection());
assertTrue(newOffer.getUseMarketBasedPrice());
assertEquals(priceMarginPctInput, newOffer.getMarketPriceMarginPct());
assertEquals(10_000_000, newOffer.getAmount());
assertEquals(5_000_000, newOffer.getMinAmount());
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
assertEquals(gbpAccount.getId(), newOffer.getPaymentAccountId());
assertEquals(XMR, newOffer.getBaseCurrencyCode());
assertEquals(BTC, newOffer.getBaseCurrencyCode());
assertEquals("GBP", newOffer.getCounterCurrencyCode());
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
@ -170,7 +200,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
@Test
@Order(4)
public void testCreateBRLXMRSellOffer6Point55PctPriceMargin() {
public void testCreateBRLBTCSellOffer6Point55PctPriceMargin() {
PaymentAccount brlAccount = createDummyF2FAccount(aliceClient, "BR");
double priceMarginPctInput = 6.55;
var newOffer = aliceClient.createMarketBasedPricedOffer(SELL.name(),
@ -178,53 +208,92 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
10_000_000L,
5_000_000L,
priceMarginPctInput,
getDefaultBuyerSecurityDepositAsPercent(),
brlAccount.getId());
log.info("OFFER #4:\n{}", formatOfferTable(singletonList(newOffer), "brl"));
defaultBuyerSecurityDepositPct.get(),
brlAccount.getId(),
NO_TRIGGER_PRICE);
log.debug("Offer #4:\n{}", toOfferTable.apply(newOffer));
assertTrue(newOffer.getIsMyOffer());
assertFalse(newOffer.getIsActivated());
String newOfferId = newOffer.getId();
assertNotEquals("", newOfferId);
assertEquals(SELL.name(), newOffer.getDirection());
assertTrue(newOffer.getUseMarketBasedPrice());
assertEquals(priceMarginPctInput, newOffer.getMarketPriceMarginPct());
assertEquals(10_000_000, newOffer.getAmount());
assertEquals(5_000_000, newOffer.getMinAmount());
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
assertEquals(brlAccount.getId(), newOffer.getPaymentAccountId());
assertEquals(XMR, newOffer.getBaseCurrencyCode());
assertEquals(BTC, newOffer.getBaseCurrencyCode());
assertEquals("BRL", newOffer.getCounterCurrencyCode());
newOffer = aliceClient.getMyOffer(newOfferId);
newOffer = aliceClient.getOffer(newOfferId);
assertTrue(newOffer.getIsMyOffer());
assertTrue(newOffer.getIsActivated());
assertEquals(newOfferId, newOffer.getId());
assertEquals(SELL.name(), newOffer.getDirection());
assertTrue(newOffer.getUseMarketBasedPrice());
assertEquals(priceMarginPctInput, newOffer.getMarketPriceMarginPct());
assertEquals(10_000_000, newOffer.getAmount());
assertEquals(5_000_000, newOffer.getMinAmount());
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
assertEquals(brlAccount.getId(), newOffer.getPaymentAccountId());
assertEquals(XMR, newOffer.getBaseCurrencyCode());
assertEquals(BTC, newOffer.getBaseCurrencyCode());
assertEquals("BRL", newOffer.getCounterCurrencyCode());
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
}
@Test
@Order(5)
public void testCreateUSDBTCBuyOfferWithTriggerPrice() {
PaymentAccount usdAccount = createDummyF2FAccount(aliceClient, "US");
double mktPriceAsDouble = aliceClient.getBtcPrice("usd");
String triggerPrice = calcPriceAsString(mktPriceAsDouble, Double.parseDouble("1000.9999"), 4);
var newOffer = aliceClient.createMarketBasedPricedOffer(BUY.name(),
"usd",
10_000_000L,
5_000_000L,
0.0,
defaultBuyerSecurityDepositPct.get(),
usdAccount.getId(),
triggerPrice);
assertTrue(newOffer.getIsMyOffer());
assertFalse(newOffer.getIsActivated());
genBtcBlocksThenWait(1, 4000); // give time to add to offer book
newOffer = aliceClient.getOffer(newOffer.getId());
log.debug("Offer #5:\n{}", toOfferTable.apply(newOffer));
assertTrue(newOffer.getIsMyOffer());
assertTrue(newOffer.getIsActivated());
assertEquals(triggerPrice, newOffer.getTriggerPrice());
}
private void assertCalculatedPriceIsCorrect(OfferInfo offer, double priceMarginPctInput) {
assertTrue(() -> {
String counterCurrencyCode = offer.getCounterCurrencyCode();
double mktPrice = aliceClient.getBtcPrice(counterCurrencyCode);
double scaledOfferPrice = getScaledOfferPrice(offer.getPrice(), counterCurrencyCode);
double priceAsDouble = Double.parseDouble(offer.getPrice());
double expectedDiffPct = scaleDownByPowerOf10(priceMarginPctInput, 2);
double actualDiffPct = offer.getDirection().equals(BUY.name())
? getPercentageDifference(scaledOfferPrice, mktPrice)
: getPercentageDifference(mktPrice, scaledOfferPrice);
? getPercentageDifference(priceAsDouble, mktPrice)
: getPercentageDifference(mktPrice, priceAsDouble);
double pctDiffDelta = abs(expectedDiffPct) - abs(actualDiffPct);
return isCalculatedPriceWithinErrorTolerance(pctDiffDelta,
expectedDiffPct,
actualDiffPct,
mktPrice,
scaledOfferPrice,
priceAsDouble,
offer);
});
}
private double getPercentageDifference(double price1, double price2) {
return BigDecimal.valueOf(roundDouble((1 - (price1 / price2)), 5))
.setScale(4, HALF_UP)
.doubleValue();
}
private boolean isCalculatedPriceWithinErrorTolerance(double delta,
double expectedDiffPct,
double actualDiffPct,
@ -245,7 +314,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
actualDiffPct,
mktPrice,
scaledOfferPrice);
log.warn(offer.toString());
log.trace(offer.toString());
}
return true;

View file

@ -0,0 +1,265 @@
/*
* This file is part of Haveno.
*
* Haveno 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.
*
* Haveno 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 Haveno. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.apitest.method.offer;
import bisq.proto.grpc.OfferInfo;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
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.config.ApiTestConfig.BTC;
import static bisq.apitest.config.ApiTestConfig.XMR;
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 protobuf.OfferDirection.BUY;
import static protobuf.OfferDirection.SELL;
@SuppressWarnings("ConstantConditions")
@Disabled
@Slf4j
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class CreateXMROffersTest extends AbstractOfferTest {
private static final String MAKER_FEE_CURRENCY_CODE = BTC;
@BeforeAll
public static void setUp() {
AbstractOfferTest.setUp();
createXmrPaymentAccounts();
}
@Test
@Order(1)
public void testCreateFixedPriceBuy1BTCFor200KXMROffer() {
// Remember alt coin trades are BTC trades. When placing an offer, you are
// offering to buy or sell BTC, not ETH, XMR, etc. In this test case,
// Alice places an offer to BUY BTC.
var newOffer = aliceClient.createFixedPricedOffer(BUY.name(),
XMR,
100_000_000L,
75_000_000L,
"0.005", // FIXED PRICE IN BTC FOR 1 XMR
defaultBuyerSecurityDepositPct.get(),
alicesXmrAcct.getId());
log.debug("Sell XMR (Buy BTC) offer:\n{}", toOfferTable.apply(newOffer));
assertTrue(newOffer.getIsMyOffer());
assertFalse(newOffer.getIsActivated());
String newOfferId = newOffer.getId();
assertNotEquals("", newOfferId);
assertEquals(BUY.name(), newOffer.getDirection());
assertFalse(newOffer.getUseMarketBasedPrice());
assertEquals("0.00500000", newOffer.getPrice());
assertEquals(100_000_000L, newOffer.getAmount());
assertEquals(75_000_000L, newOffer.getMinAmount());
assertEquals(15_000_000, newOffer.getBuyerSecurityDeposit());
assertEquals(alicesXmrAcct.getId(), newOffer.getPaymentAccountId());
assertEquals(XMR, newOffer.getBaseCurrencyCode());
assertEquals(BTC, newOffer.getCounterCurrencyCode());
genBtcBlockAndWaitForOfferPreparation();
newOffer = aliceClient.getOffer(newOfferId);
assertTrue(newOffer.getIsMyOffer());
assertTrue(newOffer.getIsActivated());
assertEquals(newOfferId, newOffer.getId());
assertEquals(BUY.name(), newOffer.getDirection());
assertFalse(newOffer.getUseMarketBasedPrice());
assertEquals("0.00500000", newOffer.getPrice());
assertEquals(100_000_000L, newOffer.getAmount());
assertEquals(75_000_000L, newOffer.getMinAmount());
assertEquals(15_000_000, newOffer.getBuyerSecurityDeposit());
assertEquals(alicesXmrAcct.getId(), newOffer.getPaymentAccountId());
assertEquals(XMR, newOffer.getBaseCurrencyCode());
assertEquals(BTC, newOffer.getCounterCurrencyCode());
}
@Test
@Order(2)
public void testCreateFixedPriceSell1BTCFor200KXMROffer() {
// Alice places an offer to SELL BTC for XMR.
var newOffer = aliceClient.createFixedPricedOffer(SELL.name(),
XMR,
100_000_000L,
50_000_000L,
"0.005", // FIXED PRICE IN BTC (satoshis) FOR 1 XMR
defaultBuyerSecurityDepositPct.get(),
alicesXmrAcct.getId());
log.debug("Buy XMR (Sell BTC) offer:\n{}", toOfferTable.apply(newOffer));
assertTrue(newOffer.getIsMyOffer());
assertFalse(newOffer.getIsActivated());
String newOfferId = newOffer.getId();
assertNotEquals("", newOfferId);
assertEquals(SELL.name(), newOffer.getDirection());
assertFalse(newOffer.getUseMarketBasedPrice());
assertEquals("0.00500000", newOffer.getPrice());
assertEquals(100_000_000L, newOffer.getAmount());
assertEquals(50_000_000L, newOffer.getMinAmount());
assertEquals(15_000_000, newOffer.getBuyerSecurityDeposit());
assertEquals(alicesXmrAcct.getId(), newOffer.getPaymentAccountId());
assertEquals(XMR, newOffer.getBaseCurrencyCode());
assertEquals(BTC, newOffer.getCounterCurrencyCode());
genBtcBlockAndWaitForOfferPreparation();
newOffer = aliceClient.getOffer(newOfferId);
assertTrue(newOffer.getIsMyOffer());
assertTrue(newOffer.getIsActivated());
assertEquals(newOfferId, newOffer.getId());
assertEquals(SELL.name(), newOffer.getDirection());
assertFalse(newOffer.getUseMarketBasedPrice());
assertEquals("0.00500000", newOffer.getPrice());
assertEquals(100_000_000L, newOffer.getAmount());
assertEquals(50_000_000L, newOffer.getMinAmount());
assertEquals(15_000_000, newOffer.getBuyerSecurityDeposit());
assertEquals(alicesXmrAcct.getId(), newOffer.getPaymentAccountId());
assertEquals(XMR, newOffer.getBaseCurrencyCode());
assertEquals(BTC, newOffer.getCounterCurrencyCode());
}
@Test
@Order(3)
public void testCreatePriceMarginBasedBuy1BTCOfferWithTriggerPrice() {
double priceMarginPctInput = 1.00;
double mktPriceAsDouble = aliceClient.getBtcPrice(XMR);
String triggerPrice = calcPriceAsString(mktPriceAsDouble, Double.parseDouble("-0.001"), 8);
var newOffer = aliceClient.createMarketBasedPricedOffer(BUY.name(),
XMR,
100_000_000L,
75_000_000L,
priceMarginPctInput,
defaultBuyerSecurityDepositPct.get(),
alicesXmrAcct.getId(),
triggerPrice);
log.debug("Pending Sell XMR (Buy BTC) offer:\n{}", toOfferTable.apply(newOffer));
assertTrue(newOffer.getIsMyOffer());
assertFalse(newOffer.getIsActivated());
String newOfferId = newOffer.getId();
assertNotEquals("", newOfferId);
assertEquals(BUY.name(), newOffer.getDirection());
assertTrue(newOffer.getUseMarketBasedPrice());
// There is no trigger price while offer is pending.
assertEquals(NO_TRIGGER_PRICE, newOffer.getTriggerPrice());
assertEquals(100_000_000L, newOffer.getAmount());
assertEquals(75_000_000L, newOffer.getMinAmount());
assertEquals(15_000_000, newOffer.getBuyerSecurityDeposit());
assertEquals(alicesXmrAcct.getId(), newOffer.getPaymentAccountId());
assertEquals(XMR, newOffer.getBaseCurrencyCode());
assertEquals(BTC, newOffer.getCounterCurrencyCode());
genBtcBlockAndWaitForOfferPreparation();
newOffer = aliceClient.getOffer(newOfferId);
log.debug("Available Sell XMR (Buy BTC) offer:\n{}", toOfferTable.apply(newOffer));
assertTrue(newOffer.getIsMyOffer());
assertTrue(newOffer.getIsActivated());
assertEquals(newOfferId, newOffer.getId());
assertEquals(BUY.name(), newOffer.getDirection());
assertTrue(newOffer.getUseMarketBasedPrice());
// The trigger price should exist on the prepared offer.
assertEquals(triggerPrice, newOffer.getTriggerPrice());
assertEquals(100_000_000L, newOffer.getAmount());
assertEquals(75_000_000L, newOffer.getMinAmount());
assertEquals(15_000_000, newOffer.getBuyerSecurityDeposit());
assertEquals(alicesXmrAcct.getId(), newOffer.getPaymentAccountId());
assertEquals(XMR, newOffer.getBaseCurrencyCode());
assertEquals(BTC, newOffer.getCounterCurrencyCode());
}
@Test
@Order(4)
public void testCreatePriceMarginBasedSell1BTCOffer() {
// Alice places an offer to SELL BTC for XMR.
double priceMarginPctInput = 0.50;
var newOffer = aliceClient.createMarketBasedPricedOffer(SELL.name(),
XMR,
100_000_000L,
50_000_000L,
priceMarginPctInput,
defaultBuyerSecurityDepositPct.get(),
alicesXmrAcct.getId(),
NO_TRIGGER_PRICE);
log.debug("Buy XMR (Sell BTC) offer:\n{}", toOfferTable.apply(newOffer));
assertTrue(newOffer.getIsMyOffer());
assertFalse(newOffer.getIsActivated());
String newOfferId = newOffer.getId();
assertNotEquals("", newOfferId);
assertEquals(SELL.name(), newOffer.getDirection());
assertTrue(newOffer.getUseMarketBasedPrice());
assertEquals(100_000_000L, newOffer.getAmount());
assertEquals(50_000_000L, newOffer.getMinAmount());
assertEquals(15_000_000, newOffer.getBuyerSecurityDeposit());
assertEquals(alicesXmrAcct.getId(), newOffer.getPaymentAccountId());
assertEquals(XMR, newOffer.getBaseCurrencyCode());
assertEquals(BTC, newOffer.getCounterCurrencyCode());
genBtcBlockAndWaitForOfferPreparation();
newOffer = aliceClient.getOffer(newOfferId);
assertTrue(newOffer.getIsMyOffer());
assertTrue(newOffer.getIsActivated());
assertEquals(newOfferId, newOffer.getId());
assertEquals(SELL.name(), newOffer.getDirection());
assertTrue(newOffer.getUseMarketBasedPrice());
assertEquals(100_000_000L, newOffer.getAmount());
assertEquals(50_000_000L, newOffer.getMinAmount());
assertEquals(15_000_000, newOffer.getBuyerSecurityDeposit());
assertEquals(alicesXmrAcct.getId(), newOffer.getPaymentAccountId());
assertEquals(XMR, newOffer.getBaseCurrencyCode());
assertEquals(BTC, newOffer.getCounterCurrencyCode());
}
@Test
@Order(5)
public void testGetAllMyXMROffers() {
List<OfferInfo> offers = aliceClient.getMyOffersSortedByDate(XMR);
log.debug("All of Alice's XMR offers:\n{}", toOffersTable.apply(offers));
assertEquals(4, offers.size());
log.debug("Alice's balances\n{}", formatBalancesTbls(aliceClient.getBalances()));
}
@Test
@Order(6)
public void testGetAvailableXMROffers() {
List<OfferInfo> offers = bobClient.getOffersSortedByDate(XMR);
log.debug("All of Bob's available XMR offers:\n{}", toOffersTable.apply(offers));
assertEquals(4, offers.size());
log.debug("Bob's balances\n{}", formatBalancesTbls(bobClient.getBalances()));
}
private void genBtcBlockAndWaitForOfferPreparation() {
genBtcBlocksThenWait(1, 5000);
}
}

View file

@ -30,11 +30,10 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.config.ApiTestConfig.BTC;
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;
import static protobuf.OfferPayload.Direction.BUY;
import static protobuf.OfferDirection.BUY;
@Disabled
@Slf4j
@ -52,7 +51,7 @@ public class ValidateCreateOfferTest extends AbstractOfferTest {
100000000000L, // exceeds amount limit
100000000000L,
"10000.0000",
getDefaultBuyerSecurityDepositAsPercent(),
defaultBuyerSecurityDepositPct.get(),
usdAccount.getId()));
assertEquals("UNKNOWN: An error occurred at task: ValidateOffer", exception.getMessage());
}
@ -68,7 +67,7 @@ public class ValidateCreateOfferTest extends AbstractOfferTest {
10000000L,
10000000L,
"40000.0000",
getDefaultBuyerSecurityDepositAsPercent(),
defaultBuyerSecurityDepositPct.get(),
chfAccount.getId()));
String expectedError = format("UNKNOWN: cannot create EUR offer with payment account %s", chfAccount.getId());
assertEquals(expectedError, exception.getMessage());
@ -85,7 +84,7 @@ public class ValidateCreateOfferTest extends AbstractOfferTest {
10000000L,
10000000L,
"63000.0000",
getDefaultBuyerSecurityDepositAsPercent(),
defaultBuyerSecurityDepositPct.get(),
audAccount.getId()));
String expectedError = format("UNKNOWN: cannot create CAD offer with payment account %s", audAccount.getId());
assertEquals(expectedError, exception.getMessage());

View file

@ -1,6 +1,7 @@
package bisq.apitest.method.payment;
import bisq.core.api.model.PaymentAccountForm;
import bisq.core.locale.FiatCurrency;
import bisq.core.locale.Res;
import bisq.core.locale.TradeCurrency;
import bisq.core.payment.PaymentAccount;
@ -17,10 +18,13 @@ import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
@ -57,14 +61,23 @@ public class AbstractPaymentAccountTest extends MethodTest {
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_ADDRESS = "bankAddress";
static final String PROPERTY_NAME_BANK_BRANCH = "bankBranch";
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";
static final String PROPERTY_NAME_BANK_COUNTRY_CODE = "bankCountryCode";
@SuppressWarnings("unused")
static final String PROPERTY_NAME_BANK_ID = "bankId";
static final String PROPERTY_NAME_BANK_NAME = "bankName";
static final String PROPERTY_NAME_BANK_SWIFT_CODE = "bankSwiftCode";
static final String PROPERTY_NAME_BRANCH_ID = "branchId";
static final String PROPERTY_NAME_BIC = "bic";
static final String PROPERTY_NAME_BENEFICIARY_NAME = "beneficiaryName";
static final String PROPERTY_NAME_BENEFICIARY_ACCOUNT_NR = "beneficiaryAccountNr";
static final String PROPERTY_NAME_BENEFICIARY_ADDRESS = "beneficiaryAddress";
static final String PROPERTY_NAME_BENEFICIARY_CITY = "beneficiaryCity";
static final String PROPERTY_NAME_BENEFICIARY_PHONE = "beneficiaryPhone";
static final String PROPERTY_NAME_COUNTRY = "country";
static final String PROPERTY_NAME_CITY = "city";
static final String PROPERTY_NAME_CONTACT = "contact";
@ -75,6 +88,11 @@ public class AbstractPaymentAccountTest extends MethodTest {
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_INTERMEDIARY_ADDRESS = "intermediaryAddress";
static final String PROPERTY_NAME_INTERMEDIARY_BRANCH = "intermediaryBranch";
static final String PROPERTY_NAME_INTERMEDIARY_COUNTRY_CODE = "intermediaryCountryCode";
static final String PROPERTY_NAME_INTERMEDIARY_NAME = "intermediaryName";
static final String PROPERTY_NAME_INTERMEDIARY_SWIFT_CODE = "intermediarySwiftCode";
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";
@ -83,7 +101,9 @@ public class AbstractPaymentAccountTest extends MethodTest {
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_SELECTED_TRADE_CURRENCY = "selectedTradeCurrency";
static final String PROPERTY_NAME_SORT_CODE = "sortCode";
static final String PROPERTY_NAME_SPECIAL_INSTRUCTIONS = "specialInstructions";
static final String PROPERTY_NAME_STATE = "state";
static final String PROPERTY_NAME_TRADE_CURRENCIES = "tradeCurrencies";
static final String PROPERTY_NAME_USERNAME = "userName";
@ -110,7 +130,7 @@ public class AbstractPaymentAccountTest extends MethodTest {
COMPLETED_FORM_MAP.clear();
File emptyForm = getPaymentAccountForm(aliceClient, paymentMethodId);
// A short cut over the API:
// A shortcut over the API:
// File emptyForm = PAYMENT_ACCOUNT_FORM.getPaymentAccountForm(paymentMethodId);
log.debug("{} Empty form saved to {}",
testName(testInfo),
@ -125,7 +145,13 @@ public class AbstractPaymentAccountTest extends MethodTest {
PAYMENT_ACCOUNT_FORM.toJsonString(jsonForm),
Object.class);
assertNotNull(emptyForm);
assertEquals(PROPERTY_VALUE_JSON_COMMENTS, emptyForm.get(PROPERTY_NAME_JSON_COMMENTS));
if (paymentMethodId.equals("SWIFT_ID")) {
assertEquals(getSwiftFormComments(), emptyForm.get(PROPERTY_NAME_JSON_COMMENTS));
} else {
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) {
@ -149,6 +175,15 @@ public class AbstractPaymentAccountTest extends MethodTest {
assertEquals(expectedCurrencyCode, paymentAccount.getSingleTradeCurrency().getCode());
}
protected final void verifyAccountTradeCurrencies(Collection<FiatCurrency> expectedFiatCurrencies,
PaymentAccount paymentAccount) {
assertNotNull(paymentAccount.getTradeCurrencies());
List<TradeCurrency> expectedTradeCurrencies = new ArrayList<>() {{
addAll(expectedFiatCurrencies);
}};
assertArrayEquals(expectedTradeCurrencies.toArray(), paymentAccount.getTradeCurrencies().toArray());
}
protected final void verifyAccountTradeCurrencies(List<TradeCurrency> expectedTradeCurrencies,
PaymentAccount paymentAccount) {
assertNotNull(paymentAccount.getTradeCurrencies());
@ -164,14 +199,36 @@ public class AbstractPaymentAccountTest extends MethodTest {
assertTrue(paymentAccount.isPresent());
}
protected final String getCompletedFormAsJsonString() {
File completedForm = fillPaymentAccountForm();
protected final String getCompletedFormAsJsonString(List<String> comments) {
File completedForm = fillPaymentAccountForm(comments);
String jsonString = PAYMENT_ACCOUNT_FORM.toJsonString(completedForm);
log.debug("Completed form: {}", jsonString);
return jsonString;
}
private File fillPaymentAccountForm() {
protected final String getCompletedFormAsJsonString() {
File completedForm = fillPaymentAccountForm(PROPERTY_VALUE_JSON_COMMENTS);
String jsonString = PAYMENT_ACCOUNT_FORM.toJsonString(completedForm);
log.debug("Completed form: {}", jsonString);
return jsonString;
}
protected final String getCommaDelimitedFiatCurrencyCodes(Collection<FiatCurrency> fiatCurrencies) {
return fiatCurrencies.stream()
.sorted(Comparator.comparing(TradeCurrency::getCode))
.map(c -> c.getCurrency().getCurrencyCode())
.collect(Collectors.joining(","));
}
protected final List<String> getSwiftFormComments() {
List<String> comments = new ArrayList<>();
comments.addAll(PROPERTY_VALUE_JSON_COMMENTS);
List<String> wrappedSwiftComments = Res.getWrappedAsList("payment.swift.info.account", 110);
comments.addAll(wrappedSwiftComments);
return comments;
}
private File fillPaymentAccountForm(List<String> comments) {
File tmpJsonForm = null;
try {
tmpJsonForm = File.createTempFile("temp_acct_form_",
@ -182,7 +239,7 @@ public class AbstractPaymentAccountTest extends MethodTest {
writer.name(PROPERTY_NAME_JSON_COMMENTS);
writer.beginArray();
for (String s : PROPERTY_VALUE_JSON_COMMENTS) {
for (String s : comments) {
writer.value(s);
}
writer.endArray();

View file

@ -17,12 +17,13 @@
package bisq.apitest.method.payment;
import bisq.core.locale.FiatCurrency;
import bisq.core.locale.TradeCurrency;
import bisq.core.payment.AdvancedCashAccount;
import bisq.core.payment.AliPayAccount;
import bisq.core.payment.AustraliaPayid;
import bisq.core.payment.AustraliaPayidAccount;
import bisq.core.payment.CapitualAccount;
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;
@ -32,7 +33,9 @@ import bisq.core.payment.JapanBankAccount;
import bisq.core.payment.MoneyBeamAccount;
import bisq.core.payment.MoneyGramAccount;
import bisq.core.payment.NationalBankAccount;
import bisq.core.payment.PaxumAccount;
import bisq.core.payment.PaymentAccount;
import bisq.core.payment.PayseraAccount;
import bisq.core.payment.PerfectMoneyAccount;
import bisq.core.payment.PopmoneyAccount;
import bisq.core.payment.PromptPayAccount;
@ -41,6 +44,7 @@ import bisq.core.payment.SameBankAccount;
import bisq.core.payment.SepaAccount;
import bisq.core.payment.SepaInstantAccount;
import bisq.core.payment.SpecificBanksAccount;
import bisq.core.payment.SwiftAccount;
import bisq.core.payment.SwishAccount;
import bisq.core.payment.TransferwiseAccount;
import bisq.core.payment.USPostalMoneyOrderAccount;
@ -51,14 +55,17 @@ 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 bisq.core.payment.payload.SwiftAccountPayload;
import io.grpc.StatusRuntimeException;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
@ -70,16 +77,23 @@ import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
import static bisq.apitest.config.ApiTestConfig.EUR;
import static bisq.apitest.config.ApiTestConfig.USD;
import static bisq.apitest.config.HavenoAppConfig.alicedaemon;
import static bisq.cli.TableFormat.formatPaymentAcctTbl;
import static bisq.core.locale.CurrencyUtil.*;
import static bisq.cli.table.builder.TableType.PAYMENT_ACCOUNT_TBL;
import static bisq.core.locale.CurrencyUtil.getAllSortedFiatCurrencies;
import static bisq.core.locale.CurrencyUtil.getTradeCurrency;
import static bisq.core.payment.payload.PaymentMethod.*;
import static java.util.Collections.singletonList;
import static java.util.Comparator.comparing;
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;
import bisq.cli.table.builder.TableBuilder;
@SuppressWarnings({"OptionalGetWithoutIsPresent", "ConstantConditions"})
@Disabled
@Slf4j
@ -104,11 +118,18 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
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_TRADE_CURRENCIES, AdvancedCashAccount.SUPPORTED_CURRENCIES
.stream()
.map(TradeCurrency::getCode)
.collect(Collectors.joining(",")));
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "RUB");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Advanced Cash Acct Salt"));
String jsonString = getCompletedFormAsJsonString();
AdvancedCashAccount paymentAccount = (AdvancedCashAccount) createPaymentAccount(aliceClient, jsonString);
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
verifyAccountTradeCurrencies(getAllAdvancedCashCurrencies(), paymentAccount);
verifyAccountTradeCurrencies(AdvancedCashAccount.SUPPORTED_CURRENCIES, paymentAccount);
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY),
paymentAccount.getSelectedTradeCurrency().getCode());
verifyCommonFormEntries(paymentAccount);
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), paymentAccount.getAccountNr());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
@ -146,7 +167,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
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(aliceClient, jsonString);
AustraliaPayidAccount paymentAccount = (AustraliaPayidAccount) createPaymentAccount(aliceClient, jsonString);
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
verifyAccountSingleTradeCurrency("AUD", paymentAccount);
verifyCommonFormEntries(paymentAccount);
@ -156,6 +177,33 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
print(paymentAccount);
}
@Test
public void testCreateCapitualAccount(TestInfo testInfo) {
File emptyForm = getEmptyForm(testInfo, CAPITUAL_ID);
verifyEmptyForm(emptyForm,
CAPITUAL_ID,
PROPERTY_NAME_ACCOUNT_NR);
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, CAPITUAL_ID);
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Capitual Acct");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NR, "1111 2222 3333-4");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, CapitualAccount.SUPPORTED_CURRENCIES
.stream()
.map(TradeCurrency::getCode)
.collect(Collectors.joining(",")));
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "BRL");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Capitual Acct Salt"));
String jsonString = getCompletedFormAsJsonString();
CapitualAccount paymentAccount = (CapitualAccount) createPaymentAccount(aliceClient, jsonString);
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
verifyAccountTradeCurrencies(CapitualAccount.SUPPORTED_CURRENCIES, paymentAccount);
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY),
paymentAccount.getSelectedTradeCurrency().getCode());
verifyCommonFormEntries(paymentAccount);
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), paymentAccount.getAccountNr());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
print(paymentAccount);
}
@Test
public void testCreateCashDepositAccount(TestInfo testInfo) {
File emptyForm = getEmptyForm(testInfo, CASH_DEPOSIT_ID);
@ -189,7 +237,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
String jsonString = getCompletedFormAsJsonString();
CashDepositAccount paymentAccount = (CashDepositAccount) createPaymentAccount(aliceClient, jsonString);
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
verifyAccountSingleTradeCurrency("EUR", paymentAccount);
verifyAccountSingleTradeCurrency(EUR, paymentAccount);
verifyCommonFormEntries(paymentAccount);
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
Objects.requireNonNull(paymentAccount.getCountry()).code);
@ -253,28 +301,6 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
print(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(aliceClient, jsonString);
verifyUserPayloadHasPaymentAccountWithId(aliceClient, 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());
print(paymentAccount);
}
@Test
public void testCreateClearXChangeAccount(TestInfo testInfo) {
File emptyForm = getEmptyForm(testInfo, CLEAR_X_CHANGE_ID);
@ -290,7 +316,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
String jsonString = getCompletedFormAsJsonString();
ClearXchangeAccount paymentAccount = (ClearXchangeAccount) createPaymentAccount(aliceClient, jsonString);
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
verifyAccountSingleTradeCurrency("USD", paymentAccount);
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());
@ -363,7 +389,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
String jsonString = getCompletedFormAsJsonString();
HalCashAccount paymentAccount = (HalCashAccount) createPaymentAccount(aliceClient, jsonString);
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
verifyAccountSingleTradeCurrency("EUR", paymentAccount);
verifyAccountSingleTradeCurrency(EUR, paymentAccount);
verifyCommonFormEntries(paymentAccount);
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_MOBILE_NR), paymentAccount.getMobileNr());
print(paymentAccount);
@ -448,7 +474,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
String jsonString = getCompletedFormAsJsonString();
MoneyBeamAccount paymentAccount = (MoneyBeamAccount) createPaymentAccount(aliceClient, jsonString);
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
verifyAccountSingleTradeCurrency("EUR", paymentAccount);
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());
@ -466,6 +492,11 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
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_TRADE_CURRENCIES, MoneyGramAccount.SUPPORTED_CURRENCIES
.stream()
.map(TradeCurrency::getCode)
.collect(Collectors.joining(",")));
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "INR");
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");
@ -474,7 +505,9 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
String jsonString = getCompletedFormAsJsonString();
MoneyGramAccount paymentAccount = (MoneyGramAccount) createPaymentAccount(aliceClient, jsonString);
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
verifyAccountTradeCurrencies(getAllMoneyGramCurrencies(), paymentAccount);
verifyAccountTradeCurrencies(MoneyGramAccount.SUPPORTED_CURRENCIES, paymentAccount);
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY),
paymentAccount.getSelectedTradeCurrency().getCode());
verifyCommonFormEntries(paymentAccount);
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getFullName());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
@ -497,13 +530,65 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
String jsonString = getCompletedFormAsJsonString();
PerfectMoneyAccount paymentAccount = (PerfectMoneyAccount) createPaymentAccount(aliceClient, jsonString);
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
verifyAccountSingleTradeCurrency("USD", paymentAccount);
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());
print(paymentAccount);
}
@Test
public void testCreatePaxumAccount(TestInfo testInfo) {
File emptyForm = getEmptyForm(testInfo, PAXUM_ID);
verifyEmptyForm(emptyForm,
PAXUM_ID,
PROPERTY_NAME_EMAIL);
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, PAXUM_ID);
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Paxum Acct");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, PaxumAccount.SUPPORTED_CURRENCIES
.stream()
.map(TradeCurrency::getCode)
.collect(Collectors.joining(",")));
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "SEK");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "jane@doe.net");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
String jsonString = getCompletedFormAsJsonString();
PaxumAccount paymentAccount = (PaxumAccount) createPaymentAccount(aliceClient, jsonString);
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
verifyAccountTradeCurrencies(PaxumAccount.SUPPORTED_CURRENCIES, paymentAccount);
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY),
paymentAccount.getSelectedTradeCurrency().getCode());
verifyCommonFormEntries(paymentAccount);
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
print(paymentAccount);
}
@Test
public void testCreatePayseraAccount(TestInfo testInfo) {
File emptyForm = getEmptyForm(testInfo, PAYSERA_ID);
verifyEmptyForm(emptyForm,
PAYSERA_ID,
PROPERTY_NAME_EMAIL);
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, PAYSERA_ID);
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Paysera Acct");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, PayseraAccount.SUPPORTED_CURRENCIES
.stream()
.map(TradeCurrency::getCode)
.collect(Collectors.joining(",")));
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "ZAR");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "jane@doe.net");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
String jsonString = getCompletedFormAsJsonString();
PayseraAccount paymentAccount = (PayseraAccount) createPaymentAccount(aliceClient, jsonString);
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
verifyAccountTradeCurrencies(PayseraAccount.SUPPORTED_CURRENCIES, paymentAccount);
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY),
paymentAccount.getSelectedTradeCurrency().getCode());
verifyCommonFormEntries(paymentAccount);
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
print(paymentAccount);
}
@Test
public void testCreatePopmoneyAccount(TestInfo testInfo) {
File emptyForm = getEmptyForm(testInfo, POPMONEY_ID);
@ -519,7 +604,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
String jsonString = getCompletedFormAsJsonString();
PopmoneyAccount paymentAccount = (PopmoneyAccount) createPaymentAccount(aliceClient, jsonString);
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
verifyAccountSingleTradeCurrency("USD", paymentAccount);
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());
@ -554,12 +639,19 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
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_TRADE_CURRENCIES, RevolutAccount.SUPPORTED_CURRENCIES
.stream()
.map(TradeCurrency::getCode)
.collect(Collectors.joining(",")));
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "QAR");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_USERNAME, "revolut123");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
String jsonString = getCompletedFormAsJsonString();
RevolutAccount paymentAccount = (RevolutAccount) createPaymentAccount(aliceClient, jsonString);
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
verifyAccountTradeCurrencies(getAllRevolutCurrencies(), paymentAccount);
verifyAccountTradeCurrencies(RevolutAccount.SUPPORTED_CURRENCIES, paymentAccount);
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY),
paymentAccount.getSelectedTradeCurrency().getCode());
verifyCommonFormEntries(paymentAccount);
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_USERNAME), paymentAccount.getUserName());
print(paymentAccount);
@ -631,7 +723,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
Objects.requireNonNull(paymentAccount.getCountry()).code);
verifyAccountSingleTradeCurrency("EUR", paymentAccount);
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());
@ -662,7 +754,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
Objects.requireNonNull(paymentAccount.getCountry()).code);
verifyAccountSingleTradeCurrency("EUR", paymentAccount);
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());
@ -720,6 +812,64 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
print(paymentAccount);
}
@Test
public void testCreateSwiftAccount(TestInfo testInfo) {
// https://www.theswiftcodes.com
File emptyForm = getEmptyForm(testInfo, SWIFT_ID);
verifyEmptyForm(emptyForm,
SWIFT_ID,
PROPERTY_NAME_BANK_SWIFT_CODE);
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, SWIFT_ID);
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "IT Swift Acct w/ DE Intermediary");
Collection<FiatCurrency> swiftCurrenciesSortedByCode = getAllSortedFiatCurrencies(comparing(TradeCurrency::getCode));
String allFiatCodes = getCommaDelimitedFiatCurrencyCodes(swiftCurrenciesSortedByCode);
COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, allFiatCodes);
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, EUR);
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_SWIFT_CODE, "PASCITMMFIR");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_COUNTRY_CODE, "IT");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_NAME, "BANCA MONTE DEI PASCHI DI SIENA S.P.A.");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_BRANCH, "SUCC. DI FIRENZE");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_ADDRESS, "Via dei Pecori, 8, 50123 Firenze FI, Italy");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BENEFICIARY_NAME, "Vito de' Medici");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BENEFICIARY_ACCOUNT_NR, "0000 1111 2222 3333");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BENEFICIARY_ADDRESS, "Via dei Pecori, 1, 50123 Firenze FI, Italy");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BENEFICIARY_CITY, "Firenze");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BENEFICIARY_PHONE, "+39 055 222222");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SPECIAL_INSTRUCTIONS, "N/A");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_INTERMEDIARY_SWIFT_CODE, "DEUTDEFFXXX");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_INTERMEDIARY_COUNTRY_CODE, "DE");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_INTERMEDIARY_NAME, "Kosmo Krump");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_INTERMEDIARY_ADDRESS, "TAUNUSANLAGE 12, FRANKFURT AM MAIN, 60262");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_INTERMEDIARY_BRANCH, "Deutsche Bank Frankfurt F");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Swift Acct Salt"));
String jsonString = getCompletedFormAsJsonString(getSwiftFormComments());
SwiftAccount paymentAccount = (SwiftAccount) createPaymentAccount(aliceClient, jsonString);
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
verifyAccountTradeCurrencies(swiftCurrenciesSortedByCode, paymentAccount);
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY),
paymentAccount.getSelectedTradeCurrency().getCode());
verifyCommonFormEntries(paymentAccount);
SwiftAccountPayload payload = (SwiftAccountPayload) paymentAccount.getPaymentAccountPayload();
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_SWIFT_CODE), payload.getBankSwiftCode());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_COUNTRY_CODE), payload.getBankCountryCode());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_NAME), payload.getBankName());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_BRANCH), payload.getBankBranch());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_ADDRESS), payload.getBankAddress());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BENEFICIARY_NAME), payload.getBeneficiaryName());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BENEFICIARY_ACCOUNT_NR), payload.getBeneficiaryAccountNr());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BENEFICIARY_ADDRESS), payload.getBeneficiaryAddress());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BENEFICIARY_CITY), payload.getBeneficiaryCity());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BENEFICIARY_PHONE), payload.getBeneficiaryPhone());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SPECIAL_INSTRUCTIONS), payload.getSpecialInstructions());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_INTERMEDIARY_SWIFT_CODE), payload.getIntermediarySwiftCode());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_INTERMEDIARY_COUNTRY_CODE), payload.getIntermediaryCountryCode());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_INTERMEDIARY_NAME), payload.getIntermediaryName());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_INTERMEDIARY_BRANCH), payload.getIntermediaryBranch());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_INTERMEDIARY_ADDRESS), payload.getIntermediaryAddress());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
print(paymentAccount);
}
@Test
public void testCreateSwishAccount(TestInfo testInfo) {
File emptyForm = getEmptyForm(testInfo, SWISH_ID);
@ -751,17 +901,16 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
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");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, "NZD");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "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(1, paymentAccount.getTradeCurrencies().size());
TradeCurrency expectedCurrency = getTradeCurrency("EUR").get();
assertEquals(expectedCurrency, paymentAccount.getSelectedTradeCurrency());
List<TradeCurrency> expectedTradeCurrencies = singletonList(expectedCurrency);
verifyAccountTradeCurrencies(expectedTradeCurrencies, paymentAccount);
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY),
paymentAccount.getSelectedTradeCurrency().getCode());
verifyCommonFormEntries(paymentAccount);
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
print(paymentAccount);
@ -775,7 +924,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
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_TRADE_CURRENCIES, "ARS,CAD,HRK,CZK,EUR,HKD,IDR,JPY,CHF,NZD");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "CHF");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "jane@doe.info");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
String jsonString = getCompletedFormAsJsonString();
@ -787,7 +937,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
add(getTradeCurrency("CAD").get());
add(getTradeCurrency("HRK").get());
add(getTradeCurrency("CZK").get());
add(getTradeCurrency("EUR").get());
add(getTradeCurrency(EUR).get());
add(getTradeCurrency("HKD").get());
add(getTradeCurrency("IDR").get());
add(getTradeCurrency("JPY").get());
@ -795,8 +945,34 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
add(getTradeCurrency("NZD").get());
}};
verifyAccountTradeCurrencies(expectedTradeCurrencies, paymentAccount);
TradeCurrency expectedSelectedCurrency = expectedTradeCurrencies.get(0);
assertEquals(expectedSelectedCurrency, paymentAccount.getSelectedTradeCurrency());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY),
paymentAccount.getSelectedTradeCurrency().getCode());
verifyCommonFormEntries(paymentAccount);
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
print(paymentAccount);
}
@Test
public void testCreateTransferwiseAccountWithSupportedTradeCurrencies(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, TransferwiseAccount.SUPPORTED_CURRENCIES
.stream()
.map(TradeCurrency::getCode)
.collect(Collectors.joining(",")));
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "AUD");
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());
verifyAccountTradeCurrencies(TransferwiseAccount.SUPPORTED_CURRENCIES, paymentAccount);
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY),
paymentAccount.getSelectedTradeCurrency().getCode());
verifyCommonFormEntries(paymentAccount);
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
print(paymentAccount);
@ -836,7 +1012,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
createPaymentAccount(aliceClient, jsonString));
assertEquals("INVALID_ARGUMENT: no trade currencies defined for transferwise payment account",
assertEquals("INVALID_ARGUMENT: no trade currency defined for transferwise payment account",
exception.getMessage());
}
@ -849,11 +1025,18 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
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_TRADE_CURRENCIES, UpholdAccount.SUPPORTED_CURRENCIES
.stream()
.map(TradeCurrency::getCode)
.collect(Collectors.joining(",")));
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "MXN");
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Uphold Acct Salt"));
String jsonString = getCompletedFormAsJsonString();
UpholdAccount paymentAccount = (UpholdAccount) createPaymentAccount(aliceClient, jsonString);
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
verifyAccountTradeCurrencies(getAllUpholdCurrencies(), paymentAccount);
verifyAccountTradeCurrencies(UpholdAccount.SUPPORTED_CURRENCIES, paymentAccount);
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY),
paymentAccount.getSelectedTradeCurrency().getCode());
verifyCommonFormEntries(paymentAccount);
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_ID), paymentAccount.getAccountId());
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
@ -875,7 +1058,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
String jsonString = getCompletedFormAsJsonString();
USPostalMoneyOrderAccount paymentAccount = (USPostalMoneyOrderAccount) createPaymentAccount(aliceClient, jsonString);
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
verifyAccountSingleTradeCurrency("USD", paymentAccount);
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());
@ -923,7 +1106,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
String jsonString = getCompletedFormAsJsonString();
WesternUnionAccount paymentAccount = (WesternUnionAccount) createPaymentAccount(aliceClient, jsonString);
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
verifyAccountSingleTradeCurrency("USD", paymentAccount);
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());
@ -942,7 +1125,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
private void print(PaymentAccount paymentAccount) {
if (log.isDebugEnabled()) {
log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
log.debug("\n{}", formatPaymentAcctTbl(singletonList(paymentAccount.toProtoMessage())));
log.debug("\n{}", new TableBuilder(PAYMENT_ACCOUNT_TBL, paymentAccount.toProtoMessage()).build());
}
}
}

View file

@ -2,28 +2,47 @@ package bisq.apitest.method.trade;
import bisq.proto.grpc.TradeInfo;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import org.slf4j.Logger;
import lombok.Getter;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.TestInfo;
import static bisq.cli.TradeFormat.format;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static bisq.cli.table.builder.TableType.TRADE_DETAIL_TBL;
import static bisq.core.trade.Trade.Phase.DEPOSIT_UNLOCKED;
import static bisq.core.trade.Trade.Phase.PAYMENT_SENT;
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
import static bisq.core.trade.Trade.State.BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG;
import static bisq.core.trade.Trade.State.DEPOSIT_UNLOCKED_IN_BLOCK_CHAIN;
import static bisq.core.trade.Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG;
import static java.lang.String.format;
import static java.lang.System.out;
import static org.junit.jupiter.api.Assertions.*;
import bisq.apitest.method.offer.AbstractOfferTest;
import bisq.cli.CliMain;
import bisq.cli.GrpcClient;
import bisq.cli.table.builder.TableBuilder;
public class AbstractTradeTest extends AbstractOfferTest {
public static final ExpectedProtocolStatus EXPECTED_PROTOCOL_STATUS = new ExpectedProtocolStatus();
// A Trade ID cache for use in @Test sequences.
@Getter
protected static String tradeId;
protected final Supplier<Integer> maxTradeStateAndPhaseChecks = () -> isLongRunningTest ? 10 : 2;
protected final Function<TradeInfo, String> toTradeDetailTable = (trade) ->
new TableBuilder(TRADE_DETAIL_TBL, trade).build().toString();
protected final Function<GrpcClient, String> toUserName = (client) -> client.equals(aliceClient) ? "Alice" : "Bob";
@BeforeAll
public static void initStaticFixtures() {
@ -32,13 +51,129 @@ public class AbstractTradeTest extends AbstractOfferTest {
protected final TradeInfo takeAlicesOffer(String offerId,
String paymentAccountId) {
return bobClient.takeOffer(offerId, paymentAccountId);
return takeAlicesOffer(offerId,
paymentAccountId,
true);
}
@SuppressWarnings("unused")
protected final TradeInfo takeBobsOffer(String offerId,
String paymentAccountId) {
return aliceClient.takeOffer(offerId, paymentAccountId);
protected final TradeInfo takeAlicesOffer(String offerId,
String paymentAccountId,
boolean generateBtcBlock) {
@SuppressWarnings("ConstantConditions")
var trade = bobClient.takeOffer(offerId,
paymentAccountId);
assertNotNull(trade);
assertEquals(offerId, trade.getTradeId());
// Cache the trade id for the other tests.
tradeId = trade.getTradeId();
if (generateBtcBlock)
genBtcBlocksThenWait(1, 6_000);
return trade;
}
protected final void waitForDepositConfirmation(Logger log,
TestInfo testInfo,
GrpcClient grpcClient,
String tradeId) {
Predicate<TradeInfo> isTradeInDepositUnlockedStateAndPhase = (t) ->
t.getState().equals(DEPOSIT_UNLOCKED_IN_BLOCK_CHAIN.name())
&& t.getPhase().equals(DEPOSIT_UNLOCKED.name());
String userName = toUserName.apply(grpcClient);
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
TradeInfo trade = grpcClient.getTrade(tradeId);
if (!isTradeInDepositUnlockedStateAndPhase.test(trade)) {
log.warn("{} still waiting on trade {} tx {}: DEPOSIT_UNLOCKED_IN_BLOCK_CHAIN, attempt # {}",
userName,
trade.getShortId(),
trade.getMakerDepositTxId(),
trade.getTakerDepositTxId(),
i);
genBtcBlocksThenWait(1, 4_000);
} else {
EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_UNLOCKED_IN_BLOCK_CHAIN)
.setPhase(DEPOSIT_UNLOCKED)
.setDepositPublished(true)
.setDepositConfirmed(true);
verifyExpectedProtocolStatus(trade);
logTrade(log,
testInfo,
userName + "'s view after deposit is confirmed",
trade);
break;
}
}
}
protected final void verifyTakerDepositConfirmed(TradeInfo trade) {
if (!trade.getIsDepositUnlocked()) {
fail(format("INVALID_PHASE for trade %s in STATE=%s PHASE=%s, deposit tx never unlocked.",
trade.getShortId(),
trade.getState(),
trade.getPhase()));
}
}
protected final void waitForBuyerSeesPaymentInitiatedMessage(Logger log,
TestInfo testInfo,
GrpcClient grpcClient,
String tradeId) {
String userName = toUserName.apply(grpcClient);
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
TradeInfo trade = grpcClient.getTrade(tradeId);
if (!trade.getIsPaymentSent()) {
log.warn("{} still waiting for trade {} {}, attempt # {}",
userName,
trade.getShortId(),
BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG,
i);
sleep(5_000);
} else {
// Do not check trade.getOffer().getState() here because
// it might be AVAILABLE, not OFFER_FEE_RESERVED.
EXPECTED_PROTOCOL_STATUS.setState(BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG)
.setPhase(PAYMENT_SENT)
.setPaymentStartedMessageSent(true);
verifyExpectedProtocolStatus(trade);
logTrade(log, testInfo, userName + "'s view after confirming trade payment sent", trade);
break;
}
}
}
protected final void waitForSellerSeesPaymentInitiatedMessage(Logger log,
TestInfo testInfo,
GrpcClient grpcClient,
String tradeId) {
Predicate<TradeInfo> isTradeInPaymentReceiptConfirmedStateAndPhase = (t) ->
t.getState().equals(SELLER_RECEIVED_PAYMENT_SENT_MSG.name()) &&
(t.getPhase().equals(PAYOUT_PUBLISHED.name()) || t.getPhase().equals(PAYMENT_SENT.name()));
String userName = toUserName.apply(grpcClient);
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
TradeInfo trade = grpcClient.getTrade(tradeId);
if (!isTradeInPaymentReceiptConfirmedStateAndPhase.test(trade)) {
log.warn("INVALID_PHASE for {}'s trade {} in STATE={} PHASE={}, cannot confirm payment received yet.",
userName,
trade.getShortId(),
trade.getState(),
trade.getPhase());
sleep(10_000);
} else {
break;
}
}
TradeInfo trade = grpcClient.getTrade(tradeId);
if (!isTradeInPaymentReceiptConfirmedStateAndPhase.test(trade)) {
fail(format("INVALID_PHASE for %s's trade %s in STATE=%s PHASE=%s, cannot confirm payment received.",
userName,
trade.getShortId(),
trade.getState(),
trade.getPhase()));
}
}
protected final void verifyExpectedProtocolStatus(TradeInfo trade) {
@ -49,35 +184,54 @@ public class AbstractTradeTest extends AbstractOfferTest {
if (!isLongRunningTest)
assertEquals(EXPECTED_PROTOCOL_STATUS.isDepositPublished, trade.getIsDepositPublished());
assertEquals(EXPECTED_PROTOCOL_STATUS.isDepositUnlocked, trade.getIsDepositUnlocked());
assertEquals(EXPECTED_PROTOCOL_STATUS.isPaymentSent, trade.getIsPaymentSent());
assertEquals(EXPECTED_PROTOCOL_STATUS.isPaymentReceived, trade.getIsPaymentReceived());
assertEquals(EXPECTED_PROTOCOL_STATUS.isDepositConfirmed, trade.getIsDepositUnlocked());
assertEquals(EXPECTED_PROTOCOL_STATUS.isPaymentStartedMessageSent, trade.getIsPaymentSent());
assertEquals(EXPECTED_PROTOCOL_STATUS.isPaymentReceivedMessageSent, trade.getIsPaymentReceived());
assertEquals(EXPECTED_PROTOCOL_STATUS.isPayoutPublished, trade.getIsPayoutPublished());
assertEquals(EXPECTED_PROTOCOL_STATUS.isWithdrawn, trade.getIsWithdrawn());
assertEquals(EXPECTED_PROTOCOL_STATUS.isCompleted, trade.getIsCompleted());
}
protected final void logBalances(Logger log, TestInfo testInfo) {
var alicesBalances = aliceClient.getBalances();
log.debug("{} Alice's Current Balances:\n{}",
testName(testInfo),
formatBalancesTbls(alicesBalances));
var bobsBalances = bobClient.getBalances();
log.debug("{} Bob's Current Balances:\n{}",
testName(testInfo),
formatBalancesTbls(bobsBalances));
}
protected final void logTrade(Logger log,
TestInfo testInfo,
String description,
TradeInfo trade) {
logTrade(log, testInfo, description, trade, false);
}
protected final void logTrade(Logger log,
TestInfo testInfo,
String description,
TradeInfo trade,
boolean force) {
if (force)
log.info(String.format("%s %s%n%s",
if (log.isDebugEnabled()) {
log.debug(format("%s %s%n%s",
testName(testInfo),
description.toUpperCase(),
format(trade)));
else if (log.isDebugEnabled()) {
log.debug(String.format("%s %s%n%s",
testName(testInfo),
description.toUpperCase(),
format(trade)));
description,
new TableBuilder(TRADE_DETAIL_TBL, trade).build()));
}
}
protected static void runCliGetTrade(String tradeId) {
out.println("Alice's CLI 'gettrade' response:");
CliMain.main(new String[]{"--password=xyz", "--port=9998", "gettrade", "--trade-id=" + tradeId});
out.println("Bob's CLI 'gettrade' response:");
CliMain.main(new String[]{"--password=xyz", "--port=9999", "gettrade", "--trade-id=" + tradeId});
}
protected static void runCliGetOpenTrades() {
out.println("Alice's CLI 'gettrades --category=open' response:");
CliMain.main(new String[]{"--password=xyz", "--port=9998", "gettrades", "--category=open"});
out.println("Bob's CLI 'gettrades --category=open' response:");
CliMain.main(new String[]{"--password=xyz", "--port=9999", "gettrades", "--category=open"});
}
protected static void runCliGetClosedTrades() {
out.println("Alice's CLI 'gettrades --category=closed' response:");
CliMain.main(new String[]{"--password=xyz", "--port=9998", "gettrades", "--category=closed"});
out.println("Bob's CLI 'gettrades --category=closed' response:");
CliMain.main(new String[]{"--password=xyz", "--port=9999", "gettrades", "--category=closed"});
}
}

View file

@ -10,11 +10,11 @@ public class ExpectedProtocolStatus {
Trade.State state;
Trade.Phase phase;
boolean isDepositPublished;
boolean isDepositUnlocked;
boolean isPaymentSent;
boolean isPaymentReceived;
boolean isDepositConfirmed;
boolean isPaymentStartedMessageSent;
boolean isPaymentReceivedMessageSent;
boolean isPayoutPublished;
boolean isWithdrawn;
boolean isCompleted;
public ExpectedProtocolStatus setState(Trade.State state) {
this.state = state;
@ -31,18 +31,18 @@ public class ExpectedProtocolStatus {
return this;
}
public ExpectedProtocolStatus setDepositUnlocked(boolean depositUnlocked) {
isDepositUnlocked = depositUnlocked;
public ExpectedProtocolStatus setDepositConfirmed(boolean depositConfirmed) {
isDepositConfirmed = depositConfirmed;
return this;
}
public ExpectedProtocolStatus setFiatSent(boolean paymentSent) {
isPaymentSent = paymentSent;
public ExpectedProtocolStatus setPaymentStartedMessageSent(boolean paymentStartedMessageSent) {
isPaymentStartedMessageSent = paymentStartedMessageSent;
return this;
}
public ExpectedProtocolStatus setFiatReceived(boolean paymentReceived) {
isPaymentReceived = paymentReceived;
public ExpectedProtocolStatus setPaymentReceivedMessageSent(boolean paymentReceivedMessageSent) {
isPaymentReceivedMessageSent = paymentReceivedMessageSent;
return this;
}
@ -51,8 +51,8 @@ public class ExpectedProtocolStatus {
return this;
}
public ExpectedProtocolStatus setWithdrawn(boolean withdrawn) {
isWithdrawn = withdrawn;
public ExpectedProtocolStatus setCompleted(boolean completed) {
isCompleted = completed;
return this;
}
@ -60,10 +60,10 @@ public class ExpectedProtocolStatus {
state = null;
phase = null;
isDepositPublished = false;
isDepositUnlocked = false;
isPaymentSent = false;
isPaymentReceived = false;
isDepositConfirmed = false;
isPaymentStartedMessageSent = false;
isPaymentReceivedMessageSent = false;
isPayoutPublished = false;
isWithdrawn = false;
isCompleted = false;
}
}

View file

@ -19,12 +19,8 @@ package bisq.apitest.method.trade;
import bisq.core.payment.PaymentAccount;
import bisq.proto.grpc.TradeInfo;
import io.grpc.StatusRuntimeException;
import java.util.function.Predicate;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Disabled;
@ -34,18 +30,15 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestMethodOrder;
import static bisq.cli.TableFormat.formatBalancesTbls;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static bisq.core.trade.Trade.Phase.DEPOSIT_UNLOCKED;
import static bisq.core.trade.Trade.Phase.PAYMENT_SENT;
import static bisq.apitest.config.ApiTestConfig.BTC;
import static bisq.apitest.config.ApiTestConfig.USD;
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
import static bisq.core.trade.Trade.State.*;
import static java.lang.String.format;
import static bisq.core.trade.Trade.State.BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG;
import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.fail;
import static protobuf.Offer.State.OFFER_FEE_RESERVED;
import static protobuf.OfferPayload.Direction.BUY;
import static protobuf.OfferDirection.BUY;
import static protobuf.OpenOffer.State.AVAILABLE;
@Disabled
@ -61,62 +54,35 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
try {
PaymentAccount alicesUsdAccount = createDummyF2FAccount(aliceClient, "US");
var alicesOffer = aliceClient.createMarketBasedPricedOffer(BUY.name(),
"usd",
USD,
12_500_000L,
12_500_000L, // min-amount = amount
0.00,
getDefaultBuyerSecurityDepositAsPercent(),
alicesUsdAccount.getId());
defaultBuyerSecurityDepositPct.get(),
alicesUsdAccount.getId(),
NO_TRIGGER_PRICE);
var offerId = alicesOffer.getId();
// Wait for Alice's AddToOfferBook task.
// Wait times vary; my logs show >= 2 second delay.
sleep(3000); // TODO loop instead of hard code wait time
var alicesUsdOffers = aliceClient.getMyOffersSortedByDate(BUY.name(), "usd");
// Wait times vary; my logs show >= 2-second delay.
sleep(3_000); // TODO loop instead of hard code a wait time
var alicesUsdOffers = aliceClient.getMyOffersSortedByDate(BUY.name(), USD);
assertEquals(1, alicesUsdOffers.size());
PaymentAccount bobsUsdAccount = createDummyF2FAccount(bobClient, "US");
var trade = takeAlicesOffer(offerId, bobsUsdAccount.getId());
assertNotNull(trade);
assertEquals(offerId, trade.getTradeId());
// Cache the trade id for the other tests.
tradeId = trade.getTradeId();
genBtcBlocksThenWait(1, 4000);
alicesUsdOffers = aliceClient.getMyOffersSortedByDate(BUY.name(), "usd");
var trade = takeAlicesOffer(offerId,
bobsUsdAccount.getId(),
false);
sleep(2_500); // Allow available offer to be removed from offer book.
alicesUsdOffers = aliceClient.getMyOffersSortedByDate(BUY.name(), USD);
assertEquals(0, alicesUsdOffers.size());
genBtcBlocksThenWait(1, 2_500);
waitForDepositConfirmation(log, testInfo, bobClient, trade.getTradeId());
genBtcBlocksThenWait(1, 2500);
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
trade = bobClient.getTrade(trade.getTradeId());
if (!trade.getIsDepositUnlocked()) {
log.warn("Bob still waiting on trade {} maker tx {} taker tx {}: DEPOSIT_UNLOCKED_IN_BLOCK_CHAIN, attempt # {}",
trade.getShortId(),
trade.getMakerDepositTxId(),
trade.getTakerDepositTxId(),
i);
genBtcBlocksThenWait(1, 4000);
continue;
} else {
EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_UNLOCKED_IN_BLOCK_CHAIN)
.setPhase(DEPOSIT_UNLOCKED)
.setDepositPublished(true)
.setDepositUnlocked(true);
verifyExpectedProtocolStatus(trade);
logTrade(log, testInfo, "Bob's view after deposit is unlocked", trade, true);
break;
}
}
if (!trade.getIsDepositUnlocked()) {
fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, deposit tx never unlocked.",
trade.getShortId(),
trade.getState(),
trade.getPhase()));
}
trade = bobClient.getTrade(tradeId);
verifyTakerDepositConfirmed(trade);
logTrade(log, testInfo, "Alice's Maker/Buyer View", aliceClient.getTrade(tradeId));
logTrade(log, testInfo, "Bob's Taker/Seller View", bobClient.getTrade(tradeId));
} catch (StatusRuntimeException e) {
fail(e);
}
@ -127,56 +93,10 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
public void testAlicesConfirmPaymentStarted(final TestInfo testInfo) {
try {
var trade = aliceClient.getTrade(tradeId);
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
t.getState().equals(DEPOSIT_UNLOCKED_IN_BLOCK_CHAIN.name())
&& t.getPhase().equals(DEPOSIT_UNLOCKED.name());
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
if (!tradeStateAndPhaseCorrect.test(trade)) {
log.warn("INVALID_PHASE for Alice's trade {} in STATE={} PHASE={}, cannot confirm payment started yet.",
trade.getShortId(),
trade.getState(),
trade.getPhase());
// fail("Bad trade state and phase.");
sleep(1000 * 10);
trade = aliceClient.getTrade(tradeId);
continue;
} else {
break;
}
}
if (!tradeStateAndPhaseCorrect.test(trade)) {
fail(format("INVALID_PHASE for Alice's trade %s in STATE=%s PHASE=%s, could not confirm payment started.",
trade.getShortId(),
trade.getState(),
trade.getPhase()));
}
waitForDepositConfirmation(log, testInfo, aliceClient, trade.getTradeId());
aliceClient.confirmPaymentStarted(trade.getTradeId());
sleep(6000);
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
trade = aliceClient.getTrade(tradeId);
if (!trade.getIsPaymentSent()) {
log.warn("Alice still waiting for trade {} BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG, attempt # {}",
trade.getShortId(),
i);
sleep(5000);
continue;
} else {
assertEquals(OFFER_FEE_RESERVED.name(), trade.getOffer().getState());
EXPECTED_PROTOCOL_STATUS.setState(BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG)
.setPhase(PAYMENT_SENT)
.setFiatSent(true);
verifyExpectedProtocolStatus(trade);
logTrade(log, testInfo, "Alice's view after confirming fiat payment sent", trade);
break;
}
}
sleep(6_000);
waitForBuyerSeesPaymentInitiatedMessage(log, testInfo, aliceClient, tradeId);
} catch (StatusRuntimeException e) {
fail(e);
}
@ -186,82 +106,19 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
@Order(3)
public void testBobsConfirmPaymentReceived(final TestInfo testInfo) {
try {
waitForSellerSeesPaymentInitiatedMessage(log, testInfo, bobClient, tradeId);
var trade = bobClient.getTrade(tradeId);
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
t.getState().equals(SELLER_RECEIVED_PAYMENT_SENT_MSG.name())
&& (t.getPhase().equals(PAYOUT_PUBLISHED.name()) || t.getPhase().equals(PAYMENT_SENT.name()));
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
if (!tradeStateAndPhaseCorrect.test(trade)) {
log.warn("INVALID_PHASE for Bob's trade {} in STATE={} PHASE={}, cannot confirm payment received yet.",
trade.getShortId(),
trade.getState(),
trade.getPhase());
// fail("Bad trade state and phase.");
sleep(1000 * 10);
trade = bobClient.getTrade(tradeId);
continue;
} else {
break;
}
}
if (!tradeStateAndPhaseCorrect.test(trade)) {
fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, cannot confirm payment received.",
trade.getShortId(),
trade.getState(),
trade.getPhase()));
}
bobClient.confirmPaymentReceived(trade.getTradeId());
sleep(3000);
sleep(3_000);
trade = bobClient.getTrade(tradeId);
// Note: offer.state == available
assertEquals(AVAILABLE.name(), trade.getOffer().getState());
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
.setPhase(PAYOUT_PUBLISHED)
.setPayoutPublished(true)
.setFiatReceived(true);
.setPaymentReceivedMessageSent(true);
verifyExpectedProtocolStatus(trade);
logTrade(log, testInfo, "Bob's view after confirming fiat payment received", trade);
} catch (StatusRuntimeException e) {
fail(e);
}
}
@Test
@Order(4)
public void testAlicesKeepFunds(final TestInfo testInfo) {
try {
genBtcBlocksThenWait(1, 1000);
var trade = aliceClient.getTrade(tradeId);
logTrade(log, testInfo, "Alice's view before keeping funds", trade);
aliceClient.keepFunds(tradeId);
genBtcBlocksThenWait(1, 1000);
trade = aliceClient.getTrade(tradeId);
EXPECTED_PROTOCOL_STATUS.setState(BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG)
.setPhase(PAYOUT_PUBLISHED);
verifyExpectedProtocolStatus(trade);
logTrade(log, testInfo, "Alice's view after keeping funds", trade);
logTrade(log, testInfo, "Alice's Maker/Buyer View (Done)", aliceClient.getTrade(tradeId), true);
logTrade(log, testInfo, "Bob's Taker/Seller View (Done)", bobClient.getTrade(tradeId), true);
var alicesBalances = aliceClient.getBalances();
log.info("{} Alice's Current Balance:\n{}",
testName(testInfo),
formatBalancesTbls(alicesBalances));
var bobsBalances = bobClient.getBalances();
log.info("{} Bob's Current Balance:\n{}",
testName(testInfo),
formatBalancesTbls(bobsBalances));
} catch (StatusRuntimeException e) {
fail(e);
}

View file

@ -0,0 +1,253 @@
/*
* This file is part of Haveno.
*
* Haveno 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.
*
* Haveno 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 Haveno. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* This file is part of Haveno.
*
* Haveno 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.
*
* Haveno 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 Haveno. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.apitest.method.trade;
import bisq.core.payment.PaymentAccount;
import bisq.core.payment.payload.NationalBankAccountPayload;
import io.grpc.StatusRuntimeException;
import lombok.extern.slf4j.Slf4j;
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.TestInfo;
import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.config.ApiTestConfig.BTC;
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
import static org.junit.jupiter.api.Assertions.*;
import static protobuf.Offer.State.OFFER_FEE_RESERVED;
import static protobuf.OfferDirection.BUY;
import static protobuf.OpenOffer.State.AVAILABLE;
/**
* Test case verifies trade can be made with national bank payment method,
* and json contracts exclude bank acct details until deposit tx is confirmed.
*/
@SuppressWarnings("ConstantConditions")
@Disabled
@Slf4j
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class TakeBuyBTCOfferWithNationalBankAcctTest extends AbstractTradeTest {
// Alice is maker/buyer, Bob is taker/seller.
private static final String BRL = "BRL";
private static PaymentAccount alicesPaymentAccount;
private static PaymentAccount bobsPaymentAccount;
@BeforeAll
public static void setUp() {
setUp(false);
}
@Test
@Order(1)
public void testTakeAlicesBuyOffer(final TestInfo testInfo) {
try {
alicesPaymentAccount = createDummyBRLAccount(aliceClient,
"Alicia da Silva",
String.valueOf(System.currentTimeMillis()),
"123.456.789-01");
bobsPaymentAccount = createDummyBRLAccount(bobClient,
"Roberto da Silva",
String.valueOf(System.currentTimeMillis()),
"123.456.789-02");
var alicesOffer = aliceClient.createMarketBasedPricedOffer(BUY.name(),
BRL,
1_000_000L,
1_000_000L, // min-amount = amount
0.00,
defaultBuyerSecurityDepositPct.get(),
alicesPaymentAccount.getId(),
NO_TRIGGER_PRICE);
var offerId = alicesOffer.getId();
// Wait for Alice's AddToOfferBook task.
// Wait times vary; my logs show >= 2 second delay.
sleep(3_000); // TODO loop instead of hard code wait time
var alicesOffers = aliceClient.getMyOffersSortedByDate(BUY.name(), BRL);
assertEquals(1, alicesOffers.size());
var trade = takeAlicesOffer(offerId,
bobsPaymentAccount.getId(),
false);
// Before generating a blk and confirming deposit tx, make sure there
// are no bank acct details in the either side's contract.
while (true) {
try {
var alicesContract = aliceClient.getTrade(trade.getTradeId()).getContractAsJson();
var bobsContract = bobClient.getTrade(trade.getTradeId()).getContractAsJson();
verifyJsonContractExcludesBankAccountDetails(alicesContract, alicesPaymentAccount);
verifyJsonContractExcludesBankAccountDetails(alicesContract, bobsPaymentAccount);
verifyJsonContractExcludesBankAccountDetails(bobsContract, alicesPaymentAccount);
verifyJsonContractExcludesBankAccountDetails(bobsContract, bobsPaymentAccount);
break;
} catch (StatusRuntimeException ex) {
if (ex.getMessage() == null) {
String message = ex.getMessage().replaceFirst("^[A-Z_]+: ", "");
if (message.contains("trade") && message.contains("not found")) {
fail(ex);
}
} else {
sleep(500);
}
}
}
genBtcBlocksThenWait(1, 4000);
alicesOffers = aliceClient.getMyOffersSortedByDate(BUY.name(), BRL);
assertEquals(0, alicesOffers.size());
genBtcBlocksThenWait(1, 2_500);
waitForDepositConfirmation(log, testInfo, bobClient, trade.getTradeId());
trade = bobClient.getTrade(tradeId);
verifyTakerDepositConfirmed(trade);
logTrade(log, testInfo, "Alice's Maker/Buyer View", aliceClient.getTrade(tradeId));
logTrade(log, testInfo, "Bob's Taker/Seller View", bobClient.getTrade(tradeId));
} catch (StatusRuntimeException e) {
fail(e);
}
}
@Test
@Order(2)
public void testBankAcctDetailsIncludedInContracts(final TestInfo testInfo) {
assertNotNull(alicesPaymentAccount);
assertNotNull(bobsPaymentAccount);
var alicesTrade = aliceClient.getTrade(tradeId);
assertNotEquals("", alicesTrade.getContract().getMakerPaymentAccountPayload().getPaymentDetails());
assertNotEquals("", alicesTrade.getContract().getTakerPaymentAccountPayload().getPaymentDetails());
var alicesContractJson = alicesTrade.getContractAsJson();
verifyJsonContractIncludesBankAccountDetails(alicesContractJson, alicesPaymentAccount);
verifyJsonContractIncludesBankAccountDetails(alicesContractJson, bobsPaymentAccount);
var bobsTrade = bobClient.getTrade(tradeId);
assertNotEquals("", bobsTrade.getContract().getMakerPaymentAccountPayload().getPaymentDetails());
assertNotEquals("", bobsTrade.getContract().getTakerPaymentAccountPayload().getPaymentDetails());
var bobsContractJson = bobsTrade.getContractAsJson();
verifyJsonContractIncludesBankAccountDetails(bobsContractJson, alicesPaymentAccount);
verifyJsonContractIncludesBankAccountDetails(bobsContractJson, bobsPaymentAccount);
}
@Test
@Order(3)
public void testAlicesConfirmPaymentStarted(final TestInfo testInfo) {
try {
var trade = aliceClient.getTrade(tradeId);
waitForDepositConfirmation(log, testInfo, aliceClient, trade.getTradeId());
aliceClient.confirmPaymentStarted(trade.getTradeId());
sleep(6_000);
waitForBuyerSeesPaymentInitiatedMessage(log, testInfo, aliceClient, tradeId);
trade = aliceClient.getTrade(tradeId);
assertEquals(OFFER_FEE_RESERVED.name(), trade.getOffer().getState());
logTrade(log, testInfo, "Alice's Maker/Buyer View (Payment Sent)", aliceClient.getTrade(tradeId));
logTrade(log, testInfo, "Bob's Taker/Seller View (Payment Sent)", bobClient.getTrade(tradeId));
} catch (StatusRuntimeException e) {
fail(e);
}
}
@Test
@Order(4)
public void testBobsConfirmPaymentReceived(final TestInfo testInfo) {
try {
waitForSellerSeesPaymentInitiatedMessage(log, testInfo, bobClient, tradeId);
var trade = bobClient.getTrade(tradeId);
bobClient.confirmPaymentReceived(trade.getTradeId());
sleep(3_000);
trade = bobClient.getTrade(tradeId);
// Note: offer.state == available
assertEquals(AVAILABLE.name(), trade.getOffer().getState());
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
.setPhase(PAYOUT_PUBLISHED)
.setPayoutPublished(true)
.setPaymentReceivedMessageSent(true);
verifyExpectedProtocolStatus(trade);
logTrade(log, testInfo, "Bob's view after confirming fiat payment received", trade);
} catch (StatusRuntimeException e) {
fail(e);
}
}
private void verifyJsonContractExcludesBankAccountDetails(String jsonContract,
PaymentAccount paymentAccount) {
NationalBankAccountPayload nationalBankAccountPayload =
(NationalBankAccountPayload) paymentAccount.getPaymentAccountPayload();
// The client cannot know exactly when payment acct payloads are added to a contract,
// so auto-failing here results in a flaky test.
// assertFalse(jsonContract.contains(nationalBankAccountPayload.getNationalAccountId()));
// assertFalse(jsonContract.contains(nationalBankAccountPayload.getBranchId()));
// assertFalse(jsonContract.contains(nationalBankAccountPayload.getAccountNr()));
// assertFalse(jsonContract.contains(nationalBankAccountPayload.getHolderName()));
// assertFalse(jsonContract.contains(nationalBankAccountPayload.getHolderTaxId()));
// Log warning if bank acct details are found in json contract.
if (jsonContract.contains(nationalBankAccountPayload.getNationalAccountId()))
log.warn("Could not check json contract soon enough; it contains national bank acct id");
if (jsonContract.contains(nationalBankAccountPayload.getBranchId()))
log.warn("Could not check json contract soon enough; it contains natl bank branch id");
if (jsonContract.contains(nationalBankAccountPayload.getAccountNr()))
log.warn("Could not check json contract soon enough; it contains natl bank acct #");
if (jsonContract.contains(nationalBankAccountPayload.getHolderName()))
log.warn("Could not check json contract soon enough; it contains natl bank acct holder name");
if (jsonContract.contains(nationalBankAccountPayload.getHolderTaxId()))
log.warn("Could not check json contract soon enough; it contains natl bank acct holder tax id");
}
private void verifyJsonContractIncludesBankAccountDetails(String jsonContract,
PaymentAccount paymentAccount) {
NationalBankAccountPayload nationalBankAccountPayload =
(NationalBankAccountPayload) paymentAccount.getPaymentAccountPayload();
assertTrue(jsonContract.contains(nationalBankAccountPayload.getNationalAccountId()));
assertTrue(jsonContract.contains(nationalBankAccountPayload.getBranchId()));
assertTrue(jsonContract.contains(nationalBankAccountPayload.getAccountNr()));
assertTrue(jsonContract.contains(nationalBankAccountPayload.getHolderName()));
assertTrue(jsonContract.contains(nationalBankAccountPayload.getHolderTaxId()));
}
}

View file

@ -0,0 +1,144 @@
/*
* This file is part of Haveno.
*
* Haveno 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.
*
* Haveno 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 Haveno. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.apitest.method.trade;
import io.grpc.StatusRuntimeException;
import lombok.extern.slf4j.Slf4j;
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.TestInfo;
import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.config.ApiTestConfig.BTC;
import static bisq.apitest.config.ApiTestConfig.XMR;
import static bisq.cli.table.builder.TableType.OFFER_TBL;
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static protobuf.Offer.State.OFFER_FEE_RESERVED;
import static protobuf.OfferDirection.SELL;
import bisq.apitest.method.offer.AbstractOfferTest;
import bisq.cli.table.builder.TableBuilder;
@Disabled
@Slf4j
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class TakeBuyXMROfferTest extends AbstractTradeTest {
// Alice is maker / xmr buyer (btc seller), Bob is taker / xmr seller (btc buyer).
@BeforeAll
public static void setUp() {
AbstractOfferTest.setUp();
createXmrPaymentAccounts();
EXPECTED_PROTOCOL_STATUS.init();
}
@Test
@Order(1)
public void testTakeAlicesSellBTCForXMROffer(final TestInfo testInfo) {
try {
// Alice is going to BUY XMR, but the Offer direction = SELL because it is a
// BTC trade; Alice will SELL BTC for XMR. Bob will send Alice XMR.
// Confused me, but just need to remember there are only BTC offers.
var btcTradeDirection = SELL.name();
var alicesOffer = aliceClient.createFixedPricedOffer(btcTradeDirection,
XMR,
15_000_000L,
7_500_000L,
"0.00455500", // FIXED PRICE IN BTC (satoshis) FOR 1 XMR
defaultBuyerSecurityDepositPct.get(),
alicesXmrAcct.getId());
log.debug("Alice's BUY XMR (SELL BTC) Offer:\n{}", new TableBuilder(OFFER_TBL, alicesOffer).build());
genBtcBlocksThenWait(1, 5000);
var offerId = alicesOffer.getId();
var alicesXmrOffers = aliceClient.getMyOffers(btcTradeDirection, XMR);
assertEquals(1, alicesXmrOffers.size());
var trade = takeAlicesOffer(offerId, bobsXmrAcct.getId());
alicesXmrOffers = aliceClient.getMyOffersSortedByDate(XMR);
assertEquals(0, alicesXmrOffers.size());
genBtcBlocksThenWait(1, 2_500);
waitForDepositConfirmation(log, testInfo, bobClient, trade.getTradeId());
trade = bobClient.getTrade(tradeId);
verifyTakerDepositConfirmed(trade);
logTrade(log, testInfo, "Alice's Maker/Buyer View", aliceClient.getTrade(tradeId));
logTrade(log, testInfo, "Bob's Taker/Seller View", bobClient.getTrade(tradeId));
} catch (StatusRuntimeException e) {
fail(e);
}
}
@Test
@Order(2)
public void testBobsConfirmPaymentStarted(final TestInfo testInfo) {
try {
var trade = bobClient.getTrade(tradeId);
verifyTakerDepositConfirmed(trade);
log.debug("Bob sends XMR payment to Alice for trade {}", trade.getTradeId());
bobClient.confirmPaymentStarted(trade.getTradeId());
sleep(3500);
waitForBuyerSeesPaymentInitiatedMessage(log, testInfo, bobClient, tradeId);
logTrade(log, testInfo, "Alice's Maker/Buyer View (Payment Sent)", aliceClient.getTrade(tradeId));
logTrade(log, testInfo, "Bob's Taker/Seller View (Payment Sent)", bobClient.getTrade(tradeId));
} catch (StatusRuntimeException e) {
fail(e);
}
}
@Test
@Order(3)
public void testAlicesConfirmPaymentReceived(final TestInfo testInfo) {
try {
waitForSellerSeesPaymentInitiatedMessage(log, testInfo, aliceClient, tradeId);
sleep(2_000);
var trade = aliceClient.getTrade(tradeId);
// If we were trading BSQ, Alice would verify payment has been sent to her
// Bisq wallet, but we can do no such checks for XMR payments.
// All XMR transfers are done outside Bisq.
log.debug("Alice verifies XMR payment was received from Bob, for trade {}", trade.getTradeId());
aliceClient.confirmPaymentReceived(trade.getTradeId());
sleep(3_000);
trade = aliceClient.getTrade(tradeId);
assertEquals(OFFER_FEE_RESERVED.name(), trade.getOffer().getState());
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
.setPhase(PAYOUT_PUBLISHED)
.setPayoutPublished(true)
.setPaymentReceivedMessageSent(true);
verifyExpectedProtocolStatus(trade);
logTrade(log, testInfo, "Alice's Maker/Buyer View (Payment Received)", aliceClient.getTrade(tradeId));
logTrade(log, testInfo, "Bob's Taker/Seller View (Payment Received)", bobClient.getTrade(tradeId));
} catch (StatusRuntimeException e) {
fail(e);
}
}
}

View file

@ -19,12 +19,8 @@ package bisq.apitest.method.trade;
import bisq.core.payment.PaymentAccount;
import bisq.proto.grpc.TradeInfo;
import io.grpc.StatusRuntimeException;
import java.util.function.Predicate;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Disabled;
@ -35,20 +31,16 @@ import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.config.ApiTestConfig.BTC;
import static bisq.cli.TableFormat.formatBalancesTbls;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static bisq.core.trade.Trade.Phase.DEPOSIT_UNLOCKED;
import static bisq.core.trade.Trade.Phase.PAYMENT_SENT;
import static bisq.apitest.config.ApiTestConfig.USD;
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
import static bisq.core.trade.Trade.Phase.WITHDRAWN;
import static bisq.core.trade.Trade.State.*;
import static java.lang.String.format;
import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
import static bisq.core.trade.Trade.State.WITHDRAW_COMPLETED;
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_RESERVED;
import static protobuf.OfferPayload.Direction.SELL;
import static protobuf.OpenOffer.State.AVAILABLE;
import static protobuf.OfferDirection.SELL;
@Disabled
@Slf4j
@ -57,6 +49,9 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
// Alice is maker/seller, Bob is taker/buyer.
// Maker and Taker fees are in BTC.
private static final String TRADE_FEE_CURRENCY_CODE = BTC;
private static final String WITHDRAWAL_TX_MEMO = "Bob's trade withdrawal";
@Test
@ -65,63 +60,35 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
try {
PaymentAccount alicesUsdAccount = createDummyF2FAccount(aliceClient, "US");
var alicesOffer = aliceClient.createMarketBasedPricedOffer(SELL.name(),
"usd",
USD,
12_500_000L,
12_500_000L, // min-amount = amount
0.00,
getDefaultBuyerSecurityDepositAsPercent(),
alicesUsdAccount.getId());
defaultBuyerSecurityDepositPct.get(),
alicesUsdAccount.getId(),
NO_TRIGGER_PRICE);
var offerId = alicesOffer.getId();
// Wait for Alice's AddToOfferBook task.
// Wait times vary; my logs show >= 2 second delay, but taking sell offers
// Wait times vary; my logs show >= 2-second delay, but taking sell offers
// seems to require more time to prepare.
sleep(3000); // TODO loop instead of hard code wait time
var alicesUsdOffers = aliceClient.getMyOffersSortedByDate(SELL.name(), "usd");
sleep(3_000); // TODO loop instead of hard code a wait time
var alicesUsdOffers = aliceClient.getMyOffersSortedByDate(SELL.name(), USD);
assertEquals(1, alicesUsdOffers.size());
PaymentAccount bobsUsdAccount = createDummyF2FAccount(bobClient, "US");
var trade = takeAlicesOffer(offerId, bobsUsdAccount.getId());
assertNotNull(trade);
assertEquals(offerId, trade.getTradeId());
// Cache the trade id for the other tests.
tradeId = trade.getTradeId();
genBtcBlocksThenWait(1, 4000);
var takeableUsdOffers = bobClient.getOffersSortedByDate(SELL.name(), "usd");
var trade = takeAlicesOffer(offerId,
bobsUsdAccount.getId(),
false);
sleep(2_500); // Allow available offer to be removed from offer book.
var takeableUsdOffers = bobClient.getOffersSortedByDate(SELL.name(), USD);
assertEquals(0, takeableUsdOffers.size());
genBtcBlocksThenWait(1, 2500);
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
trade = bobClient.getTrade(trade.getTradeId());
if (!trade.getIsDepositUnlocked()) {
log.warn("Bob still waiting on trade {} maker tx {} taker tx {}: DEPOSIT_UNLOCKED_IN_BLOCK_CHAIN, attempt # {}",
trade.getShortId(),
trade.getMakerDepositTxId(),
trade.getTakerDepositTxId(),
i);
genBtcBlocksThenWait(1, 4000);
continue;
} else {
EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_UNLOCKED_IN_BLOCK_CHAIN)
.setPhase(DEPOSIT_UNLOCKED)
.setDepositPublished(true)
.setDepositUnlocked(true);
verifyExpectedProtocolStatus(trade);
logTrade(log, testInfo, "Bob's view after deposit is confirmed", trade, true);
break;
}
}
if (!trade.getIsDepositUnlocked()) {
fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, deposit tx never unlocked.",
trade.getShortId(),
trade.getState(),
trade.getPhase()));
}
genBtcBlocksThenWait(1, 2_500);
waitForDepositConfirmation(log, testInfo, bobClient, trade.getTradeId());
trade = bobClient.getTrade(tradeId);
verifyTakerDepositConfirmed(trade);
logTrade(log, testInfo, "Alice's Maker/Buyer View", aliceClient.getTrade(tradeId));
logTrade(log, testInfo, "Bob's Taker/Seller View", bobClient.getTrade(tradeId));
} catch (StatusRuntimeException e) {
fail(e);
}
@ -132,54 +99,10 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
public void testBobsConfirmPaymentStarted(final TestInfo testInfo) {
try {
var trade = bobClient.getTrade(tradeId);
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
t.getState().equals(DEPOSIT_UNLOCKED_IN_BLOCK_CHAIN.name()) && t.getPhase().equals(DEPOSIT_UNLOCKED.name());
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
if (!tradeStateAndPhaseCorrect.test(trade)) {
log.warn("INVALID_PHASE for Bob's trade {} in STATE={} PHASE={}, cannot confirm payment started yet.",
trade.getShortId(),
trade.getState(),
trade.getPhase());
// fail("Bad trade state and phase.");
sleep(1000 * 10);
trade = bobClient.getTrade(tradeId);
continue;
} else {
break;
}
}
if (!tradeStateAndPhaseCorrect.test(trade)) {
fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, could not confirm payment started.",
trade.getShortId(),
trade.getState(),
trade.getPhase()));
}
verifyTakerDepositConfirmed(trade);
bobClient.confirmPaymentStarted(tradeId);
sleep(6000);
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
trade = bobClient.getTrade(tradeId);
if (!trade.getIsPaymentSent()) {
log.warn("Bob still waiting for trade {} BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG, attempt # {}",
trade.getShortId(),
i);
sleep(5000);
continue;
} else {
// Note: offer.state == available
assertEquals(AVAILABLE.name(), trade.getOffer().getState());
EXPECTED_PROTOCOL_STATUS.setState(BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG)
.setPhase(PAYMENT_SENT)
.setFiatSent(true);
verifyExpectedProtocolStatus(trade);
logTrade(log, testInfo, "Bob's view after confirming fiat payment sent", trade);
break;
}
}
sleep(6_000);
waitForBuyerSeesPaymentInitiatedMessage(log, testInfo, bobClient, tradeId);
} catch (StatusRuntimeException e) {
fail(e);
}
@ -189,83 +112,21 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
@Order(3)
public void testAlicesConfirmPaymentReceived(final TestInfo testInfo) {
try {
waitForSellerSeesPaymentInitiatedMessage(log, testInfo, aliceClient, tradeId);
var trade = aliceClient.getTrade(tradeId);
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
t.getState().equals(SELLER_RECEIVED_PAYMENT_SENT_MSG.name())
&& (t.getPhase().equals(PAYOUT_PUBLISHED.name()) || t.getPhase().equals(PAYMENT_SENT.name()));
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
if (!tradeStateAndPhaseCorrect.test(trade)) {
log.warn("INVALID_PHASE for Alice's trade {} in STATE={} PHASE={}, cannot confirm payment received yet.",
trade.getShortId(),
trade.getState(),
trade.getPhase());
// fail("Bad trade state and phase.");
sleep(1000 * 10);
trade = aliceClient.getTrade(tradeId);
continue;
} else {
break;
}
}
if (!tradeStateAndPhaseCorrect.test(trade)) {
fail(format("INVALID_PHASE for Alice's trade %s in STATE=%s PHASE=%s, could not confirm payment received.",
trade.getShortId(),
trade.getState(),
trade.getPhase()));
}
aliceClient.confirmPaymentReceived(trade.getTradeId());
sleep(3000);
sleep(3_000);
trade = aliceClient.getTrade(tradeId);
assertEquals(OFFER_FEE_RESERVED.name(), trade.getOffer().getState());
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
.setPhase(PAYOUT_PUBLISHED)
.setPayoutPublished(true)
.setFiatReceived(true);
.setPaymentReceivedMessageSent(true);
verifyExpectedProtocolStatus(trade);
logTrade(log, testInfo, "Alice's view after confirming fiat payment received", trade);
} catch (StatusRuntimeException e) {
fail(e);
}
}
@Test
@Order(4)
public void testBobsBtcWithdrawalToExternalAddress(final TestInfo testInfo) {
try {
genBtcBlocksThenWait(1, 1000);
var trade = bobClient.getTrade(tradeId);
logTrade(log, testInfo, "Bob's view before withdrawing funds to external wallet", trade);
String toAddress = bitcoinCli.getNewBtcAddress();
bobClient.withdrawFunds(tradeId, toAddress, WITHDRAWAL_TX_MEMO);
genBtcBlocksThenWait(1, 1000);
trade = bobClient.getTrade(tradeId);
EXPECTED_PROTOCOL_STATUS.setState(WITHDRAW_COMPLETED)
.setPhase(WITHDRAWN)
.setWithdrawn(true);
verifyExpectedProtocolStatus(trade);
logTrade(log, testInfo, "Bob's view after withdrawing BTC funds to external wallet", trade);
logTrade(log, testInfo, "Alice's Maker/Buyer View (Done)", aliceClient.getTrade(tradeId), true);
logTrade(log, testInfo, "Bob's Taker/Seller View (Done)", bobClient.getTrade(tradeId), true);
var alicesBalances = aliceClient.getBalances();
log.info("{} Alice's Current Balance:\n{}",
testName(testInfo),
formatBalancesTbls(alicesBalances));
var bobsBalances = bobClient.getBalances();
log.info("{} Bob's Current Balance:\n{}",
testName(testInfo),
formatBalancesTbls(bobsBalances));
} catch (StatusRuntimeException e) {
fail(e);
}
}
}

View file

@ -0,0 +1,153 @@
/*
* This file is part of Haveno.
*
* Haveno 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.
*
* Haveno 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 Haveno. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.apitest.method.trade;
import io.grpc.StatusRuntimeException;
import lombok.extern.slf4j.Slf4j;
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.TestInfo;
import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.config.ApiTestConfig.BTC;
import static bisq.apitest.config.ApiTestConfig.XMR;
import static bisq.cli.table.builder.TableType.OFFER_TBL;
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
import static bisq.core.trade.Trade.Phase.WITHDRAWN;
import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
import static bisq.core.trade.Trade.State.WITHDRAW_COMPLETED;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static protobuf.OfferDirection.BUY;
import bisq.apitest.method.offer.AbstractOfferTest;
import bisq.cli.table.builder.TableBuilder;
@Disabled
@Slf4j
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class TakeSellXMROfferTest extends AbstractTradeTest {
// Alice is maker / xmr seller (btc buyer), Bob is taker / xmr buyer (btc seller).
// Maker and Taker fees are in BTC.
private static final String TRADE_FEE_CURRENCY_CODE = BTC;
private static final String WITHDRAWAL_TX_MEMO = "Bob's trade withdrawal";
@BeforeAll
public static void setUp() {
AbstractOfferTest.setUp();
createXmrPaymentAccounts();
EXPECTED_PROTOCOL_STATUS.init();
}
@Test
@Order(1)
public void testTakeAlicesBuyBTCForXMROffer(final TestInfo testInfo) {
try {
// Alice is going to SELL XMR, but the Offer direction = BUY because it is a
// BTC trade; Alice will BUY BTC for XMR. Alice will send Bob XMR.
// Confused me, but just need to remember there are only BTC offers.
var btcTradeDirection = BUY.name();
double priceMarginPctInput = 1.50;
var alicesOffer = aliceClient.createMarketBasedPricedOffer(btcTradeDirection,
XMR,
20_000_000L,
10_500_000L,
priceMarginPctInput,
defaultBuyerSecurityDepositPct.get(),
alicesXmrAcct.getId(),
NO_TRIGGER_PRICE);
log.debug("Alice's SELL XMR (BUY BTC) Offer:\n{}", new TableBuilder(OFFER_TBL, alicesOffer).build());
genBtcBlocksThenWait(1, 4000);
var offerId = alicesOffer.getId();
var alicesXmrOffers = aliceClient.getMyOffers(btcTradeDirection, XMR);
assertEquals(1, alicesXmrOffers.size());
var trade = takeAlicesOffer(offerId, bobsXmrAcct.getId());
alicesXmrOffers = aliceClient.getMyOffersSortedByDate(XMR);
assertEquals(0, alicesXmrOffers.size());
genBtcBlocksThenWait(1, 2_500);
waitForDepositConfirmation(log, testInfo, bobClient, trade.getTradeId());
trade = bobClient.getTrade(tradeId);
verifyTakerDepositConfirmed(trade);
logTrade(log, testInfo, "Alice's Maker/Seller View", aliceClient.getTrade(tradeId));
logTrade(log, testInfo, "Bob's Taker/Buyer View", bobClient.getTrade(tradeId));
} catch (StatusRuntimeException e) {
fail(e);
}
}
@Test
@Order(2)
public void testAlicesConfirmPaymentStarted(final TestInfo testInfo) {
try {
var trade = aliceClient.getTrade(tradeId);
waitForDepositConfirmation(log, testInfo, aliceClient, trade.getTradeId());
log.debug("Alice sends XMR payment to Bob for trade {}", trade.getTradeId());
aliceClient.confirmPaymentStarted(trade.getTradeId());
sleep(3500);
waitForBuyerSeesPaymentInitiatedMessage(log, testInfo, aliceClient, tradeId);
logTrade(log, testInfo, "Alice's Maker/Seller View (Payment Sent)", aliceClient.getTrade(tradeId));
logTrade(log, testInfo, "Bob's Taker/Buyer View (Payment Sent)", bobClient.getTrade(tradeId));
} catch (StatusRuntimeException e) {
fail(e);
}
}
@Test
@Order(3)
public void testBobsConfirmPaymentReceived(final TestInfo testInfo) {
try {
waitForSellerSeesPaymentInitiatedMessage(log, testInfo, bobClient, tradeId);
var trade = bobClient.getTrade(tradeId);
sleep(2_000);
// If we were trading BTC, Bob would verify payment has been sent to his
// Bisq wallet, but we can do no such checks for XMR payments.
// All XMR transfers are done outside Bisq.
log.debug("Bob verifies XMR payment was received from Alice, for trade {}", trade.getTradeId());
bobClient.confirmPaymentReceived(trade.getTradeId());
sleep(3_000);
trade = bobClient.getTrade(tradeId);
// Warning: trade.getOffer().getState() might be AVAILABLE, not OFFER_FEE_RESERVED.
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
.setPhase(PAYOUT_PUBLISHED)
.setPayoutPublished(true)
.setPaymentReceivedMessageSent(true);
verifyExpectedProtocolStatus(trade);
logTrade(log, testInfo, "Alice's Maker/Seller View (Payment Received)", aliceClient.getTrade(tradeId));
logTrade(log, testInfo, "Bob's Taker/Buyer View (Payment Received)", bobClient.getTrade(tradeId));
} catch (StatusRuntimeException e) {
fail(e);
}
}
}

View file

@ -56,10 +56,9 @@ public class BtcTxFeeRateTest extends MethodTest {
@Order(2)
public void testSetInvalidTxFeeRateShouldThrowException(final TestInfo testInfo) {
var currentTxFeeRateInfo = TxFeeRateInfo.fromProto(aliceClient.getTxFeeRate());
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
aliceClient.setTxFeeRate(10));
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.setTxFeeRate(1));
String expectedExceptionMessage =
format("UNKNOWN: tx fee rate preference must be >= %d sats/byte",
format("INVALID_ARGUMENT: tx fee rate preference must be >= %d sats/byte",
currentTxFeeRateInfo.getMinFeeServiceRate());
assertEquals(expectedExceptionMessage, exception.getMessage());
}

View file

@ -19,9 +19,8 @@ import static bisq.apitest.config.HavenoAppConfig.bobdaemon;
import static bisq.apitest.config.HavenoAppConfig.seednode;
import static bisq.apitest.method.wallet.WalletTestUtil.INITIAL_BTC_BALANCES;
import static bisq.apitest.method.wallet.WalletTestUtil.verifyBtcBalances;
import static bisq.cli.TableFormat.formatAddressBalanceTbl;
import static bisq.cli.TableFormat.formatBtcBalanceInfoTbl;
import static java.util.Collections.singletonList;
import static bisq.cli.table.builder.TableType.ADDRESS_BALANCE_TBL;
import static bisq.cli.table.builder.TableType.BTC_BALANCE_TBL;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -30,6 +29,7 @@ import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import bisq.apitest.method.MethodTest;
import bisq.cli.table.builder.TableBuilder;
@Disabled
@Slf4j
@ -54,10 +54,14 @@ public class BtcWalletTest extends MethodTest {
// Bob & Alice's regtest Bisq wallets were initialized with 10 BTC.
BtcBalanceInfo alicesBalances = aliceClient.getBtcBalances();
log.debug("{} Alice's BTC Balances:\n{}", testName(testInfo), formatBtcBalanceInfoTbl(alicesBalances));
log.debug("{} Alice's BTC Balances:\n{}",
testName(testInfo),
new TableBuilder(BTC_BALANCE_TBL, alicesBalances).build());
BtcBalanceInfo bobsBalances = bobClient.getBtcBalances();
log.debug("{} Bob's BTC Balances:\n{}", testName(testInfo), formatBtcBalanceInfoTbl(bobsBalances));
log.debug("{} Bob's BTC Balances:\n{}",
testName(testInfo),
new TableBuilder(BTC_BALANCE_TBL, bobsBalances).build());
assertEquals(INITIAL_BTC_BALANCES.getAvailableBalance(), alicesBalances.getAvailableBalance());
assertEquals(INITIAL_BTC_BALANCES.getAvailableBalance(), bobsBalances.getAvailableBalance());
@ -76,7 +80,8 @@ public class BtcWalletTest extends MethodTest {
log.debug("{} -> Alice's Funded Address Balance -> \n{}",
testName(testInfo),
formatAddressBalanceTbl(singletonList(aliceClient.getAddressBalance(newAddress))));
new TableBuilder(ADDRESS_BALANCE_TBL,
aliceClient.getAddressBalance(newAddress)));
// New balance is 12.5 BTC
btcBalanceInfo = aliceClient.getBtcBalances();
@ -88,7 +93,7 @@ public class BtcWalletTest extends MethodTest {
verifyBtcBalances(alicesExpectedBalances, btcBalanceInfo);
log.debug("{} -> Alice's BTC Balances After Sending 2.5 BTC -> \n{}",
testName(testInfo),
formatBtcBalanceInfoTbl(btcBalanceInfo));
new TableBuilder(BTC_BALANCE_TBL, btcBalanceInfo).build());
}
@Test
@ -115,7 +120,7 @@ public class BtcWalletTest extends MethodTest {
BtcBalanceInfo alicesBalances = aliceClient.getBtcBalances();
log.debug("{} Alice's BTC Balances:\n{}",
testName(testInfo),
formatBtcBalanceInfoTbl(alicesBalances));
new TableBuilder(BTC_BALANCE_TBL, alicesBalances).build());
bisq.core.api.model.BtcBalanceInfo alicesExpectedBalances =
bisq.core.api.model.BtcBalanceInfo.valueOf(700000000,
0,
@ -126,7 +131,7 @@ public class BtcWalletTest extends MethodTest {
BtcBalanceInfo bobsBalances = bobClient.getBtcBalances();
log.debug("{} Bob's BTC Balances:\n{}",
testName(testInfo),
formatBtcBalanceInfoTbl(bobsBalances));
new TableBuilder(BTC_BALANCE_TBL, bobsBalances).build());
// The sendbtc tx weight and size randomly varies between two distinct values
// (876 wu, 219 bytes, OR 880 wu, 220 bytes) from test run to test run, hence
// the assertion of an available balance range [1549978000, 1549978100].

View file

@ -48,7 +48,7 @@ public class WalletProtectionTest extends MethodTest {
@Order(2)
public void testGetBalanceOnEncryptedWalletShouldThrowException() {
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.getBtcBalances());
assertEquals("UNKNOWN: wallet is locked", exception.getMessage());
assertEquals("FAILED_PRECONDITION: wallet is locked", exception.getMessage());
}
@Test
@ -58,7 +58,7 @@ public class WalletProtectionTest extends MethodTest {
aliceClient.getBtcBalances(); // should not throw 'wallet locked' exception
sleep(4500); // let unlock timeout expire
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.getBtcBalances());
assertEquals("UNKNOWN: wallet is locked", exception.getMessage());
assertEquals("FAILED_PRECONDITION: wallet is locked", exception.getMessage());
}
@Test
@ -67,7 +67,7 @@ public class WalletProtectionTest extends MethodTest {
aliceClient.unlockWallet("first-password", 3);
sleep(4000); // let unlock timeout expire
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.getBtcBalances());
assertEquals("UNKNOWN: wallet is locked", exception.getMessage());
assertEquals("FAILED_PRECONDITION: wallet is locked", exception.getMessage());
}
@Test
@ -76,14 +76,14 @@ public class WalletProtectionTest extends MethodTest {
aliceClient.unlockWallet("first-password", 60);
aliceClient.lockWallet();
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.getBtcBalances());
assertEquals("UNKNOWN: wallet is locked", exception.getMessage());
assertEquals("FAILED_PRECONDITION: wallet is locked", exception.getMessage());
}
@Test
@Order(6)
public void testLockWalletWhenWalletAlreadyLockedShouldThrowException() {
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.lockWallet());
assertEquals("UNKNOWN: wallet is already locked", exception.getMessage());
assertEquals("ALREADY_EXISTS: wallet is already locked", exception.getMessage());
}
@Test
@ -110,7 +110,7 @@ public class WalletProtectionTest extends MethodTest {
public void testSetNewWalletPasswordWithIncorrectNewPasswordShouldThrowException() {
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
aliceClient.setWalletPassword("bad old password", "irrelevant"));
assertEquals("UNKNOWN: incorrect old password", exception.getMessage());
assertEquals("INVALID_ARGUMENT: incorrect old password", exception.getMessage());
}
@Test

View file

@ -9,7 +9,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
@Slf4j
public class WalletTestUtil {
// All api tests depend on the regtest environment, and Bob & Alice's wallets
// All api tests depend on the DAO / regtest environment, and Bob & Alice's wallets
// are initialized with 10 BTC during the scaffolding setup.
public static final bisq.core.api.model.BtcBalanceInfo INITIAL_BTC_BALANCES =
bisq.core.api.model.BtcBalanceInfo.valueOf(1000000000,
@ -17,7 +17,6 @@ public class WalletTestUtil {
1000000000,
0);
public static void verifyBtcBalances(bisq.core.api.model.BtcBalanceInfo expected,
BtcBalanceInfo actual) {
assertEquals(expected.getAvailableBalance(), actual.getAvailableBalance());

View file

@ -0,0 +1,161 @@
/*
* This file is part of Haveno.
*
* Haveno 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.
*
* Haveno 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 Haveno. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.apitest.scenario;
import bisq.core.payment.PaymentAccount;
import bisq.proto.grpc.OfferInfo;
import lombok.extern.slf4j.Slf4j;
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 org.junit.jupiter.api.condition.EnabledIf;
import static java.lang.System.getenv;
import static org.junit.jupiter.api.Assertions.fail;
import static protobuf.OfferDirection.BUY;
import static protobuf.OfferDirection.SELL;
import bisq.apitest.method.offer.AbstractOfferTest;
/**
* Used to verify trigger based, automatic offer deactivation works.
* Disabled by default.
* Set ENV or IDE-ENV LONG_RUNNING_OFFER_DEACTIVATION_TEST_ENABLED=true to run.
*/
@EnabledIf("envLongRunningTestEnabled")
@Slf4j
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class LongRunningOfferDeactivationTest extends AbstractOfferTest {
private static final int MAX_ITERATIONS = 500;
@Test
@Order(1)
public void testSellOfferAutoDisable(final TestInfo testInfo) {
PaymentAccount paymentAcct = createDummyF2FAccount(aliceClient, "US");
double mktPriceAsDouble = aliceClient.getBtcPrice("USD");
String triggerPrice = calcPriceAsString(mktPriceAsDouble, -50.0000, 4);
log.info("Current USD mkt price = {} Trigger Price = {}", mktPriceAsDouble, triggerPrice);
OfferInfo offer = aliceClient.createMarketBasedPricedOffer(SELL.name(),
"USD",
1_000_000,
1_000_000,
0.00,
defaultBuyerSecurityDepositPct.get(),
paymentAcct.getId(),
triggerPrice);
log.info("SELL offer {} created with margin based price {}.",
offer.getId(),
offer.getPrice());
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
offer = aliceClient.getOffer(offer.getId()); // Offer has trigger price now.
log.info("SELL offer should be automatically disabled when mkt price falls below {}.", offer.getTriggerPrice());
int numIterations = 0;
while (++numIterations < MAX_ITERATIONS) {
offer = aliceClient.getOffer(offer.getId());
var mktPrice = aliceClient.getBtcPrice("USD");
if (offer.getIsActivated()) {
log.info("Offer still enabled at mkt price {} > {} trigger price",
mktPrice,
offer.getTriggerPrice());
sleep(1000 * 60); // 60s
} else {
log.info("Successful test completion after offer disabled at mkt price {} < {} trigger price.",
mktPrice,
offer.getTriggerPrice());
break;
}
if (numIterations == MAX_ITERATIONS)
fail("Offer never disabled");
genBtcBlocksThenWait(1, 0);
}
}
@Test
@Order(2)
public void testBuyOfferAutoDisable(final TestInfo testInfo) {
PaymentAccount paymentAcct = createDummyF2FAccount(aliceClient, "US");
double mktPriceAsDouble = aliceClient.getBtcPrice("USD");
String triggerPrice = calcPriceAsString(mktPriceAsDouble, 50.0000, 4);
log.info("Current USD mkt price = {} Trigger Price = {}", mktPriceAsDouble, triggerPrice);
OfferInfo offer = aliceClient.createMarketBasedPricedOffer(BUY.name(),
"USD",
1_000_000,
1_000_000,
0.00,
defaultBuyerSecurityDepositPct.get(),
paymentAcct.getId(),
triggerPrice);
log.info("BUY offer {} created with margin based price {}.",
offer.getId(),
offer.getPrice());
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
offer = aliceClient.getOffer(offer.getId()); // Offer has trigger price now.
log.info("BUY offer should be automatically disabled when mkt price rises above {}.",
offer.getTriggerPrice());
int numIterations = 0;
while (++numIterations < MAX_ITERATIONS) {
offer = aliceClient.getOffer(offer.getId());
var mktPrice = aliceClient.getBtcPrice("USD");
if (offer.getIsActivated()) {
log.info("Offer still enabled at mkt price {} < {} trigger price",
mktPrice,
offer.getTriggerPrice());
sleep(1000 * 60); // 60s
} else {
log.info("Successful test completion after offer disabled at mkt price {} > {} trigger price.",
mktPrice,
offer.getTriggerPrice());
break;
}
if (numIterations == MAX_ITERATIONS)
fail("Offer never disabled");
genBtcBlocksThenWait(1, 0);
}
}
protected static boolean envLongRunningTestEnabled() {
String envName = "LONG_RUNNING_OFFER_DEACTIVATION_TEST_ENABLED";
String envX = getenv(envName);
if (envX != null) {
log.info("Enabled, found {}.", envName);
return true;
} else {
log.info("Skipped, no environment variable {} defined.", envName);
log.info("To enable on Mac OS or Linux:"
+ "\tIf running in terminal, export LONG_RUNNING_OFFER_DEACTIVATION_TEST_ENABLED=true in bash shell."
+ "\tIf running in Intellij, set LONG_RUNNING_OFFER_DEACTIVATION_TEST_ENABLED=true in launcher's Environment variables field.");
return false;
}
}
}

View file

@ -71,7 +71,6 @@ public class LongRunningTradesTest extends AbstractTradeTest {
test.testTakeAlicesBuyOffer(testInfo);
test.testAlicesConfirmPaymentStarted(testInfo);
test.testBobsConfirmPaymentReceived(testInfo);
test.testAlicesKeepFunds(testInfo);
}
public void testTakeSellBTCOffer(final TestInfo testInfo) {
@ -80,7 +79,6 @@ public class LongRunningTradesTest extends AbstractTradeTest {
test.testTakeAlicesSellOffer(testInfo);
test.testBobsConfirmPaymentStarted(testInfo);
test.testAlicesConfirmPaymentReceived(testInfo);
test.testBobsBtcWithdrawalToExternalAddress(testInfo);
}
protected static boolean envLongRunningTestEnabled() {

View file

@ -20,6 +20,7 @@ package bisq.apitest.scenario;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
@ -31,15 +32,21 @@ import bisq.apitest.method.offer.AbstractOfferTest;
import bisq.apitest.method.offer.CancelOfferTest;
import bisq.apitest.method.offer.CreateOfferUsingFixedPriceTest;
import bisq.apitest.method.offer.CreateOfferUsingMarketPriceMarginTest;
import bisq.apitest.method.offer.CreateXMROffersTest;
import bisq.apitest.method.offer.ValidateCreateOfferTest;
@Slf4j
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class OfferTest extends AbstractOfferTest {
@BeforeAll
public static void setUp() {
setUp(false); // Use setUp(true) for running API daemons in remote debug mode.
}
@Test
@Order(1)
public void testAmtTooLargeShouldThrowException() {
public void testCreateOfferValidation() {
ValidateCreateOfferTest test = new ValidateCreateOfferTest();
test.testAmtTooLargeShouldThrowException();
test.testNoMatchingEURPaymentAccountShouldThrowException();
@ -57,18 +64,32 @@ public class OfferTest extends AbstractOfferTest {
@Order(3)
public void testCreateOfferUsingFixedPrice() {
CreateOfferUsingFixedPriceTest test = new CreateOfferUsingFixedPriceTest();
test.testCreateAUDXMRBuyOfferUsingFixedPrice16000();
test.testCreateUSDXMRBuyOfferUsingFixedPrice100001234();
test.testCreateEURXMRSellOfferUsingFixedPrice95001234();
test.testCreateAUDBTCBuyOfferUsingFixedPrice16000();
test.testCreateUSDBTCBuyOfferUsingFixedPrice100001234();
test.testCreateEURBTCSellOfferUsingFixedPrice95001234();
}
@Test
@Order(4)
public void testCreateOfferUsingMarketPriceMargin() {
public void testCreateOfferUsingMarketPriceMarginPct() {
CreateOfferUsingMarketPriceMarginTest test = new CreateOfferUsingMarketPriceMarginTest();
test.testCreateUSDXMRBuyOffer5PctPriceMargin();
test.testCreateNZDXMRBuyOfferMinus2PctPriceMargin();
test.testCreateGBPXMRSellOfferMinus1Point5PctPriceMargin();
test.testCreateBRLXMRSellOffer6Point55PctPriceMargin();
test.testCreateUSDBTCBuyOffer5PctPriceMargin();
test.testCreateNZDBTCBuyOfferMinus2PctPriceMargin();
test.testCreateGBPBTCSellOfferMinus1Point5PctPriceMargin();
test.testCreateBRLBTCSellOffer6Point55PctPriceMargin();
test.testCreateUSDBTCBuyOfferWithTriggerPrice();
}
@Test
@Order(6)
public void testCreateXMROffers() {
CreateXMROffersTest test = new CreateXMROffersTest();
CreateXMROffersTest.createXmrPaymentAccounts();
test.testCreateFixedPriceBuy1BTCFor200KXMROffer();
test.testCreateFixedPriceSell1BTCFor200KXMROffer();
test.testCreatePriceMarginBasedBuy1BTCOfferWithTriggerPrice();
test.testCreatePriceMarginBasedSell1BTCOffer();
test.testGetAllMyXMROffers();
test.testGetAvailableXMROffers();
}
}

View file

@ -49,9 +49,9 @@ public class PaymentAccountTest extends AbstractPaymentAccountTest {
test.testCreateAdvancedCashAccount(testInfo);
test.testCreateAliPayAccount(testInfo);
test.testCreateAustraliaPayidAccount(testInfo);
test.testCreateCapitualAccount(testInfo);
test.testCreateCashDepositAccount(testInfo);
test.testCreateBrazilNationalBankAccount(testInfo);
test.testCreateChaseQuickPayAccount(testInfo);
test.testCreateClearXChangeAccount(testInfo);
test.testCreateF2FAccount(testInfo);
test.testCreateFasterPaymentsAccount(testInfo);
@ -61,6 +61,8 @@ public class PaymentAccountTest extends AbstractPaymentAccountTest {
test.testCreateMoneyBeamAccount(testInfo);
test.testCreateMoneyGramAccount(testInfo);
test.testCreatePerfectMoneyAccount(testInfo);
test.testCreatePaxumAccount(testInfo);
test.testCreatePayseraAccount(testInfo);
test.testCreatePopmoneyAccount(testInfo);
test.testCreatePromptPayAccount(testInfo);
test.testCreateRevolutAccount(testInfo);
@ -68,12 +70,12 @@ public class PaymentAccountTest extends AbstractPaymentAccountTest {
test.testCreateSepaInstantAccount(testInfo);
test.testCreateSepaAccount(testInfo);
test.testCreateSpecificBanksAccount(testInfo);
test.testCreateSwiftAccount(testInfo);
test.testCreateSwishAccount(testInfo);
// TransferwiseAccount is only PaymentAccount with a
// tradeCurrencies field in the json form.
test.testCreateTransferwiseAccountWith1TradeCurrency(testInfo);
test.testCreateTransferwiseAccountWith10TradeCurrencies(testInfo);
test.testCreateTransferwiseAccountWithSupportedTradeCurrencies(testInfo);
test.testCreateTransferwiseAccountWithInvalidBrlTradeCurrencyShouldThrowException(testInfo);
test.testCreateTransferwiseAccountWithoutTradeCurrenciesShouldThrowException(testInfo);

View file

@ -30,6 +30,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
import static bisq.apitest.config.ApiTestRateMeterInterceptorConfig.getTestRateMeterInterceptorConfig;
import static bisq.apitest.config.HavenoAppConfig.alicedaemon;
import static bisq.apitest.config.HavenoAppConfig.arbdaemon;
import static bisq.apitest.config.HavenoAppConfig.seednode;
@ -54,7 +55,7 @@ public class StartupTest extends MethodTest {
@BeforeAll
public static void setUp() {
try {
callRateMeteringConfigFile = defaultRateMeterInterceptorConfig();
callRateMeteringConfigFile = getTestRateMeterInterceptorConfig();
startSupportingApps(callRateMeteringConfigFile,
false,
false,

View file

@ -30,7 +30,10 @@ import org.junit.jupiter.api.TestMethodOrder;
import bisq.apitest.method.trade.AbstractTradeTest;
import bisq.apitest.method.trade.TakeBuyBTCOfferTest;
import bisq.apitest.method.trade.TakeBuyBTCOfferWithNationalBankAcctTest;
import bisq.apitest.method.trade.TakeBuyXMROfferTest;
import bisq.apitest.method.trade.TakeSellBTCOfferTest;
import bisq.apitest.method.trade.TakeSellXMROfferTest;
@Slf4j
@ -49,7 +52,6 @@ public class TradeTest extends AbstractTradeTest {
test.testTakeAlicesBuyOffer(testInfo);
test.testAlicesConfirmPaymentStarted(testInfo);
test.testBobsConfirmPaymentReceived(testInfo);
test.testAlicesKeepFunds(testInfo);
}
@Test
@ -59,6 +61,35 @@ public class TradeTest extends AbstractTradeTest {
test.testTakeAlicesSellOffer(testInfo);
test.testBobsConfirmPaymentStarted(testInfo);
test.testAlicesConfirmPaymentReceived(testInfo);
test.testBobsBtcWithdrawalToExternalAddress(testInfo);
}
@Test
@Order(4)
public void testTakeBuyBTCOfferWithNationalBankAcct(final TestInfo testInfo) {
TakeBuyBTCOfferWithNationalBankAcctTest test = new TakeBuyBTCOfferWithNationalBankAcctTest();
test.testTakeAlicesBuyOffer(testInfo);
test.testBankAcctDetailsIncludedInContracts(testInfo);
test.testAlicesConfirmPaymentStarted(testInfo);
test.testBobsConfirmPaymentReceived(testInfo);
}
@Test
@Order(6)
public void testTakeBuyXMROffer(final TestInfo testInfo) {
TakeBuyXMROfferTest test = new TakeBuyXMROfferTest();
TakeBuyXMROfferTest.createXmrPaymentAccounts();
test.testTakeAlicesSellBTCForXMROffer(testInfo);
test.testBobsConfirmPaymentStarted(testInfo);
test.testAlicesConfirmPaymentReceived(testInfo);
}
@Test
@Order(7)
public void testTakeSellXMROffer(final TestInfo testInfo) {
TakeSellXMROfferTest test = new TakeSellXMROfferTest();
TakeBuyXMROfferTest.createXmrPaymentAccounts();
test.testTakeAlicesBuyBTCForXMROffer(testInfo);
test.testAlicesConfirmPaymentStarted(testInfo);
test.testBobsConfirmPaymentReceived(testInfo);
}
}

View file

@ -32,7 +32,7 @@ import lombok.extern.slf4j.Slf4j;
import static bisq.core.locale.CountryUtil.findCountryByCode;
import static bisq.core.payment.payload.PaymentMethod.CLEAR_X_CHANGE_ID;
import static bisq.core.payment.payload.PaymentMethod.getPaymentMethodById;
import static bisq.core.payment.payload.PaymentMethod.getPaymentMethod;
import static java.lang.String.format;
import static java.lang.System.getProperty;
import static java.nio.file.Files.readAllBytes;
@ -74,7 +74,7 @@ public abstract class AbstractBotTest extends MethodTest {
} else {
throw new UnsupportedOperationException(
format("This test harness bot does not work with %s payment accounts yet.",
getPaymentMethodById(paymentMethodId).getDisplayString()));
getPaymentMethod(paymentMethodId).getDisplayString()));
}
} else {
String countryCode = botScript.getCountryCode();

View file

@ -8,7 +8,7 @@ import lombok.extern.slf4j.Slf4j;
import static bisq.core.locale.CountryUtil.findCountryByCode;
import static bisq.core.payment.payload.PaymentMethod.CLEAR_X_CHANGE_ID;
import static bisq.core.payment.payload.PaymentMethod.getPaymentMethodById;
import static bisq.core.payment.payload.PaymentMethod.getPaymentMethod;
import static java.lang.String.format;
import static java.util.concurrent.TimeUnit.MINUTES;
@ -62,7 +62,7 @@ class Bot {
} else {
throw new UnsupportedOperationException(
format("This bot test does not work with %s payment accounts yet.",
getPaymentMethodById(paymentMethodId).getDisplayString()));
getPaymentMethod(paymentMethodId).getDisplayString()));
}
} else {
Country country = findCountry(botScript.getCountryCode());

View file

@ -39,11 +39,6 @@ import bisq.cli.GrpcClient;
/**
* Convenience GrpcClient wrapper for bots using gRPC services.
*
* TODO Consider if the duplication smell is bad enough to force a BotClient user
* to use the GrpcClient instead (and delete this class). But right now, I think it is
* OK because moving some of the non-gRPC related methods to GrpcClient is even smellier.
*
*/
@SuppressWarnings({"JavaDoc", "unused"})
@Slf4j
@ -124,6 +119,8 @@ public class BotClient {
* @param minAmountInSatoshis
* @param priceMarginAsPercent
* @param securityDepositAsPercent
* @param feeCurrency
* @param triggerPrice
* @return OfferInfo
*/
public OfferInfo createOfferAtMarketBasedPrice(PaymentAccount paymentAccount,
@ -132,14 +129,16 @@ public class BotClient {
long amountInSatoshis,
long minAmountInSatoshis,
double priceMarginAsPercent,
double securityDepositAsPercent) {
double securityDepositAsPercent,
String triggerPrice) {
return grpcClient.createMarketBasedPricedOffer(direction,
currencyCode,
amountInSatoshis,
minAmountInSatoshis,
priceMarginAsPercent,
securityDepositAsPercent,
paymentAccount.getId());
paymentAccount.getId(),
triggerPrice);
}
/**
@ -151,6 +150,7 @@ public class BotClient {
* @param minAmountInSatoshis
* @param fixedOfferPriceAsString
* @param securityDepositAsPercent
* @param feeCurrency
* @return OfferInfo
*/
public OfferInfo createOfferAtFixedPrice(PaymentAccount paymentAccount,
@ -225,11 +225,11 @@ public class BotClient {
}
/**
* Returns true if the trade's taker deposit fee transaction is unlocked.
* Returns true if the trade's taker deposit fee transaction has been confirmed.
* @param tradeId a valid trade id
* @return boolean
*/
public boolean isTakerDepositFeeTxUnlocked(String tradeId) {
public boolean isTakerDepositFeeTxConfirmed(String tradeId) {
return grpcClient.getTrade(tradeId).getIsDepositUnlocked();
}
@ -278,15 +278,6 @@ public class BotClient {
grpcClient.confirmPaymentReceived(tradeId);
}
/**
* Sends a 'keep funds in wallet message' for a trade with the given tradeId,
* or throws an exception.
* @param tradeId
*/
public void sendKeepFundsMessage(String tradeId) {
grpcClient.keepFunds(tradeId);
}
/**
* Create and save a new PaymentAccount with details in the given json.
* @param json

View file

@ -33,10 +33,10 @@ import java.util.function.Supplier;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import static bisq.cli.CurrencyFormat.formatMarketPrice;
import static bisq.apitest.method.offer.AbstractOfferTest.defaultBuyerSecurityDepositPct;
import static bisq.cli.CurrencyFormat.formatInternalFiatPrice;
import static bisq.cli.CurrencyFormat.formatSatoshis;
import static bisq.common.util.MathUtils.scaleDownByPowerOf10;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static bisq.core.payment.payload.PaymentMethod.F2F_ID;
import static java.lang.String.format;
import static java.math.RoundingMode.HALF_UP;
@ -124,7 +124,8 @@ public class RandomOffer {
amount,
minAmount,
priceMargin,
getDefaultBuyerSecurityDepositAsPercent());
defaultBuyerSecurityDepositPct.get(),
"0" /*no trigger price*/);
} else {
this.offer = botClient.createOfferAtFixedPrice(paymentAccount,
direction,
@ -132,7 +133,7 @@ public class RandomOffer {
amount,
minAmount,
fixedOfferPrice,
getDefaultBuyerSecurityDepositAsPercent());
defaultBuyerSecurityDepositPct.get());
}
this.id = offer.getId();
return this;
@ -162,11 +163,11 @@ public class RandomOffer {
log.info(description);
if (useMarketBasedPrice) {
log.info("Offer Price Margin = {}%", priceMargin);
log.info("Expected Offer Price = {} {}", formatMarketPrice(Double.parseDouble(fixedOfferPrice)), currencyCode);
log.info("Expected Offer Price = {} {}", formatInternalFiatPrice(Double.parseDouble(fixedOfferPrice)), currencyCode);
} else {
log.info("Fixed Offer Price = {} {}", fixedOfferPrice, currencyCode);
}
log.info("Current Market Price = {} {}", formatMarketPrice(currentMarketPrice), currencyCode);
log.info("Current Market Price = {} {}", formatInternalFiatPrice(currentMarketPrice), currencyCode);
}
}

View file

@ -22,7 +22,7 @@ import lombok.extern.slf4j.Slf4j;
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.DONE;
import static bisq.apitest.scenario.bot.shutdown.ManualShutdown.isShutdownCalled;
import static bisq.cli.TableFormat.formatBalancesTbls;
import static bisq.cli.table.builder.TableType.BTC_BALANCE_TBL;
import static java.util.concurrent.TimeUnit.SECONDS;
@ -34,6 +34,7 @@ import bisq.apitest.scenario.bot.protocol.TakerBotProtocol;
import bisq.apitest.scenario.bot.script.BashScriptGenerator;
import bisq.apitest.scenario.bot.script.BotScript;
import bisq.apitest.scenario.bot.shutdown.ManualBotShutdownException;
import bisq.cli.table.builder.TableBuilder;
@Slf4j
public
@ -74,10 +75,14 @@ class RobotBob extends Bot {
throw new IllegalStateException(botProtocol.getClass().getSimpleName() + " failed to complete.");
}
StringBuilder balancesBuilder = new StringBuilder();
balancesBuilder.append("BTC").append("\n");
balancesBuilder.append(new TableBuilder(BTC_BALANCE_TBL, botClient.getBalance().getBtc()).build().toString()).append("\n");
log.info("Completed {} successful trade{}. Current Balance:\n{}",
++numTrades,
numTrades == 1 ? "" : "s",
formatBalancesTbls(botClient.getBalance()));
balancesBuilder);
if (numTrades < actions.length) {
try {

View file

@ -39,6 +39,7 @@ import lombok.extern.slf4j.Slf4j;
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.*;
import static bisq.apitest.scenario.bot.shutdown.ManualShutdown.checkIfShutdownCalled;
import static bisq.cli.table.builder.TableType.TRADE_DETAIL_TBL;
import static java.lang.String.format;
import static java.lang.System.currentTimeMillis;
import static java.util.Arrays.stream;
@ -50,7 +51,7 @@ import bisq.apitest.method.BitcoinCliHelper;
import bisq.apitest.scenario.bot.BotClient;
import bisq.apitest.scenario.bot.script.BashScriptGenerator;
import bisq.apitest.scenario.bot.shutdown.ManualBotShutdownException;
import bisq.cli.TradeFormat;
import bisq.cli.table.builder.TableBuilder;
@Slf4j
public abstract class BotProtocol {
@ -110,7 +111,7 @@ public abstract class BotProtocol {
log.info("Starting protocol step {}. Bot will shutdown if step not completed within {} minutes.",
currentProtocolStep.name(), MILLISECONDS.toMinutes(protocolStepTimeLimitInMs));
if (currentProtocolStep.equals(WAIT_FOR_TAKER_DEPOSIT_TX_UNLOCKED)) {
if (currentProtocolStep.equals(WAIT_FOR_TAKER_DEPOSIT_TX_CONFIRMED)) {
log.info("Generate a btc block to trigger taker's deposit fee tx confirmation.");
createGenerateBtcBlockScript();
}
@ -133,7 +134,8 @@ public abstract class BotProtocol {
try {
var t = this.getBotClient().getTrade(trade.getTradeId());
if (t.getIsPaymentSent()) {
log.info("Buyer has started payment for trade:\n{}", TradeFormat.format(t));
log.info("Buyer has started payment for trade:\n{}",
new TableBuilder(TRADE_DETAIL_TBL, t).build().toString());
return t;
}
} catch (Exception ex) {
@ -167,7 +169,8 @@ public abstract class BotProtocol {
try {
var t = this.getBotClient().getTrade(trade.getTradeId());
if (t.getIsPaymentReceived()) {
log.info("Seller has received payment for trade:\n{}", TradeFormat.format(t));
log.info("Seller has received payment for trade:\n{}",
new TableBuilder(TRADE_DETAIL_TBL, t).build().toString());
return t;
}
} catch (Exception ex) {
@ -202,7 +205,7 @@ public abstract class BotProtocol {
if (t.getIsPayoutPublished()) {
log.info("Payout tx {} has been published for trade:\n{}",
t.getPayoutTxId(),
TradeFormat.format(t));
new TableBuilder(TRADE_DETAIL_TBL, t).build().toString());
return t;
}
} catch (Exception ex) {
@ -219,21 +222,6 @@ public abstract class BotProtocol {
}
};
protected final Function<TradeInfo, TradeInfo> keepFundsFromTrade = (trade) -> {
initProtocolStep.accept(KEEP_FUNDS);
var isBuy = trade.getOffer().getDirection().equalsIgnoreCase(BUY);
var isSell = trade.getOffer().getDirection().equalsIgnoreCase(SELL);
var cliUserIsSeller = (this instanceof MakerBotProtocol && isBuy) || (this instanceof TakerBotProtocol && isSell);
if (cliUserIsSeller) {
createKeepFundsScript(trade);
} else {
createGetBalanceScript();
}
checkIfShutdownCalled("Interrupted before closing trade with 'keep funds' command.");
this.getBotClient().sendKeepFundsMessage(trade.getTradeId());
return trade;
};
protected void createPaymentStartedScript(TradeInfo trade) {
File script = bashScriptGenerator.createPaymentStartedScript(trade);
printCliHintAndOrScript(script, "The manual CLI side can send a 'payment started' message");
@ -281,12 +269,12 @@ public abstract class BotProtocol {
}
private void waitForTakerFeeTxConfirmed(String tradeId) {
waitForTakerDepositFee(tradeId, WAIT_FOR_TAKER_DEPOSIT_TX_UNLOCKED);
waitForTakerDepositFee(tradeId, WAIT_FOR_TAKER_DEPOSIT_TX_CONFIRMED);
}
private void waitForTakerDepositFee(String tradeId, ProtocolStep depositTxProtocolStep) {
initProtocolStep.accept(depositTxProtocolStep);
validateCurrentProtocolStep(WAIT_FOR_TAKER_DEPOSIT_TX_PUBLISHED, WAIT_FOR_TAKER_DEPOSIT_TX_UNLOCKED);
validateCurrentProtocolStep(WAIT_FOR_TAKER_DEPOSIT_TX_PUBLISHED, WAIT_FOR_TAKER_DEPOSIT_TX_CONFIRMED);
try {
log.info(waitingForDepositFeeTxMsg(tradeId));
while (isWithinProtocolStepTimeLimit()) {
@ -316,8 +304,8 @@ public abstract class BotProtocol {
if (currentProtocolStep.equals(WAIT_FOR_TAKER_DEPOSIT_TX_PUBLISHED) && trade.getIsDepositPublished()) {
log.info("Taker deposit fee tx {} has been published.", trade.getTakerDepositTxId());
return true;
} else if (currentProtocolStep.equals(WAIT_FOR_TAKER_DEPOSIT_TX_UNLOCKED) && trade.getIsDepositUnlocked()) {
log.info("Taker deposit fee tx {} is unlocked.", trade.getTakerDepositTxId());
} else if (currentProtocolStep.equals(WAIT_FOR_TAKER_DEPOSIT_TX_CONFIRMED) && trade.getIsDepositUnlocked()) {
log.info("Taker deposit fee tx {} has been confirmed.", trade.getTakerDepositTxId());
return true;
} else {
return false;

View file

@ -16,8 +16,8 @@ import lombok.extern.slf4j.Slf4j;
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.DONE;
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.WAIT_FOR_OFFER_TAKER;
import static bisq.apitest.scenario.bot.shutdown.ManualShutdown.checkIfShutdownCalled;
import static bisq.cli.TableFormat.formatOfferTable;
import static java.util.Collections.singletonList;
import static bisq.cli.table.builder.TableType.OFFER_TBL;
import static bisq.cli.table.builder.TableType.TRADE_DETAIL_TBL;
@ -26,7 +26,7 @@ import bisq.apitest.scenario.bot.BotClient;
import bisq.apitest.scenario.bot.RandomOffer;
import bisq.apitest.scenario.bot.script.BashScriptGenerator;
import bisq.apitest.scenario.bot.shutdown.ManualBotShutdownException;
import bisq.cli.TradeFormat;
import bisq.cli.table.builder.TableBuilder;
@Slf4j
public class MakerBotProtocol extends BotProtocol {
@ -56,16 +56,13 @@ public class MakerBotProtocol extends BotProtocol {
: waitForPaymentStartedMessage.andThen(sendPaymentReceivedMessage);
completeFiatTransaction.apply(trade);
Function<TradeInfo, TradeInfo> closeTrade = waitForPayoutTx.andThen(keepFundsFromTrade);
closeTrade.apply(trade);
currentProtocolStep = DONE;
}
private final Supplier<OfferInfo> randomOffer = () -> {
checkIfShutdownCalled("Interrupted before creating random offer.");
OfferInfo offer = new RandomOffer(botClient, paymentAccount).create().getOffer();
log.info("Created random {} offer\n{}", currencyCode, formatOfferTable(singletonList(offer), currencyCode));
log.info("Created random {} offer\n{}", currencyCode, new TableBuilder(OFFER_TBL, offer).build());
return offer;
};
@ -98,7 +95,9 @@ public class MakerBotProtocol extends BotProtocol {
private Optional<TradeInfo> getNewTrade(String offerId) {
try {
var trade = botClient.getTrade(offerId);
log.info("Offer {} was taken, new trade:\n{}", offerId, TradeFormat.format(trade));
log.info("Offer {} was taken, new trade:\n{}",
offerId,
new TableBuilder(TRADE_DETAIL_TBL, trade).build().toString());
return Optional.of(trade);
} catch (Exception ex) {
// Get trade will throw a non-fatal gRPC exception if not found.

View file

@ -6,12 +6,12 @@ public enum ProtocolStep {
TAKE_OFFER,
WAIT_FOR_OFFER_TAKER,
WAIT_FOR_TAKER_DEPOSIT_TX_PUBLISHED,
WAIT_FOR_TAKER_DEPOSIT_TX_UNLOCKED,
WAIT_FOR_TAKER_DEPOSIT_TX_CONFIRMED,
SEND_PAYMENT_STARTED_MESSAGE,
WAIT_FOR_PAYMENT_STARTED_MESSAGE,
SEND_PAYMENT_RECEIVED_CONFIRMATION_MESSAGE,
WAIT_FOR_PAYMENT_RECEIVED_CONFIRMATION_MESSAGE,
WAIT_FOR_PAYOUT_TX,
KEEP_FUNDS,
CLOSE_TRADE,
DONE
}

View file

@ -17,7 +17,7 @@ import static bisq.apitest.scenario.bot.protocol.ProtocolStep.DONE;
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.FIND_OFFER;
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.TAKE_OFFER;
import static bisq.apitest.scenario.bot.shutdown.ManualShutdown.checkIfShutdownCalled;
import static bisq.cli.TableFormat.formatOfferTable;
import static bisq.cli.table.builder.TableType.OFFER_TBL;
import static bisq.core.payment.payload.PaymentMethod.F2F_ID;
@ -26,6 +26,7 @@ import bisq.apitest.method.BitcoinCliHelper;
import bisq.apitest.scenario.bot.BotClient;
import bisq.apitest.scenario.bot.script.BashScriptGenerator;
import bisq.apitest.scenario.bot.shutdown.ManualBotShutdownException;
import bisq.cli.table.builder.TableBuilder;
@Slf4j
public class TakerBotProtocol extends BotProtocol {
@ -55,16 +56,13 @@ public class TakerBotProtocol extends BotProtocol {
: sendPaymentStartedMessage.andThen(waitForPaymentReceivedConfirmation);
completeFiatTransaction.apply(trade);
Function<TradeInfo, TradeInfo> closeTrade = waitForPayoutTx.andThen(keepFundsFromTrade);
closeTrade.apply(trade);
currentProtocolStep = DONE;
}
private final Supplier<Optional<OfferInfo>> firstOffer = () -> {
var offers = botClient.getOffers(currencyCode);
if (offers.size() > 0) {
log.info("Offers found:\n{}", formatOfferTable(offers, currencyCode));
log.info("Offers found:\n{}", new TableBuilder(OFFER_TBL, offers).build());
OfferInfo offer = offers.get(0);
log.info("Will take first offer {}", offer.getId());
return Optional.of(offer);
@ -107,7 +105,6 @@ public class TakerBotProtocol extends BotProtocol {
private void createMakeOfferScript() {
String direction = RANDOM.nextBoolean() ? "BUY" : "SELL";
String feeCurrency = "BTC";
boolean createMarginPricedOffer = RANDOM.nextBoolean();
// If not using an F2F account, don't go over possible 0.01 BTC
// limit if account is not signed.
@ -120,15 +117,13 @@ public class TakerBotProtocol extends BotProtocol {
currencyCode,
amount,
"0.0",
"15.0",
feeCurrency);
"15.0");
} else {
script = bashScriptGenerator.createMakeFixedPricedOfferScript(direction,
currencyCode,
amount,
botClient.getCurrentBTCMarketPriceAsIntegerString(currencyCode),
"15.0",
feeCurrency);
"15.0");
}
printCliHintAndOrScript(script, "The manual CLI side can create an offer");
}

View file

@ -67,8 +67,7 @@ public class BashScriptGenerator {
String currencyCode,
String amount,
String marketPriceMargin,
String securityDeposit,
String feeCurrency) {
String securityDeposit) {
String makeOfferCmd = format("%s createoffer --payment-account=%s "
+ " --direction=%s"
+ " --currency-code=%s"
@ -82,8 +81,7 @@ public class BashScriptGenerator {
currencyCode,
amount,
marketPriceMargin,
securityDeposit,
feeCurrency);
securityDeposit);
String getOffersCmd = format("%s getmyoffers --direction=%s --currency-code=%s",
cliBase,
direction,
@ -98,8 +96,7 @@ public class BashScriptGenerator {
String currencyCode,
String amount,
String fixedPrice,
String securityDeposit,
String feeCurrency) {
String securityDeposit) {
String makeOfferCmd = format("%s createoffer --payment-account=%s "
+ " --direction=%s"
+ " --currency-code=%s"
@ -113,8 +110,7 @@ public class BashScriptGenerator {
currencyCode,
amount,
fixedPrice,
securityDeposit,
feeCurrency);
securityDeposit);
String getOffersCmd = format("%s getmyoffers --direction=%s --currency-code=%s",
cliBase,
direction,
@ -167,10 +163,10 @@ public class BashScriptGenerator {
}
public File createKeepFundsScript(TradeInfo trade) {
String paymentStartedCmd = format("%s keepfunds --trade-id=%s", cliBase, trade.getTradeId());
String paymentStartedCmd = format("%s closetrade --trade-id=%s", cliBase, trade.getTradeId());
String getTradeCmd = format("%s gettrade --trade-id=%s", cliBase, trade.getTradeId());
String getBalanceCmd = format("%s getbalance", cliBase);
return createCliScript("keepfunds.sh",
return createCliScript("closetrade.sh",
paymentStartedCmd,
"sleep 2",
getTradeCmd,

View file

@ -17,8 +17,9 @@
package bisq.apitest.scenario.bot.script;
import bisq.core.util.JsonUtil;
import bisq.common.file.JsonFileManager;
import bisq.common.util.Utilities;
import joptsimple.BuiltinHelpFormatter;
import joptsimple.OptionParser;
@ -214,7 +215,7 @@ public class BotScriptGenerator {
}
private String generateBotScriptTemplate() {
return Utilities.objectToJson(new BotScript(
return JsonUtil.objectToJson(new BotScript(
useTestHarness,
botPaymentMethodId,
countryCode,

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--
The :daemon & :cli jars contain their own logback.xml config files, which causes chatty logback startup.
To avoid chatty logback msgs during its configuration, pass logback.configurationFile as a system property:
-Dlogback.configurationFile=apitest/build/resources/main/logback.xml
The gradle build file takes care of adding this system property to the bisq-apitest script.
-->
<appender name="CONSOLE_APPENDER" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%highlight(%d{MMM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{30}: %msg %xEx%n)</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE_APPENDER"/>
</root>
<logger name="io.grpc.netty" level="WARN"/>
</configuration>