Merge pull request #5812 from ghubstan/02-cli-console-formatting-api

Deprecate and replace hard-coded CLI console output formatters
This commit is contained in:
Christoph Atteneder 2021-11-09 21:21:52 +01:00 committed by GitHub
commit 1578f45ffc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
87 changed files with 4735 additions and 1348 deletions

View File

@ -57,6 +57,9 @@ public class ApiTestConfig {
// Global constants
public static final String BSQ = "BSQ";
public static final String BTC = "BTC";
public static final String EUR = "EUR";
public static final String USD = "USD";
public static final String XMR = "XMR";
public static final String ARBITRATOR = "arbitrator";
public static final String MEDIATOR = "mediator";
public static final String REFUND_AGENT = "refundagent";

View File

@ -24,6 +24,8 @@ import bisq.core.proto.CoreProtoResolver;
import bisq.common.util.Utilities;
import bisq.proto.grpc.BalancesInfo;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
@ -35,7 +37,11 @@ import org.slf4j.Logger;
import javax.annotation.Nullable;
import static bisq.apitest.config.ApiTestConfig.BSQ;
import static bisq.apitest.config.ApiTestConfig.BTC;
import static bisq.apitest.config.ApiTestRateMeterInterceptorConfig.getTestRateMeterInterceptorConfig;
import static bisq.cli.table.builder.TableType.BSQ_BALANCE_TBL;
import static bisq.cli.table.builder.TableType.BTC_BALANCE_TBL;
import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Arrays.stream;
@ -46,6 +52,7 @@ 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 {
@ -155,6 +162,15 @@ public class MethodTest extends ApiTestCase {
return bisq.core.payment.PaymentAccount.fromProto(paymentAccount, CORE_PROTO_RESOLVER);
}
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");
balances.append(BSQ).append("\n");
balances.append(new TableBuilder(BSQ_BALANCE_TBL, allBalances.getBsq()).build());
return balances.toString();
}
protected static String encodeToHex(String s) {
return Utilities.bytesAsHexString(s.getBytes(UTF_8));
}

View File

@ -17,10 +17,14 @@
package bisq.apitest.method.offer;
import bisq.proto.grpc.BsqSwapOfferInfo;
import bisq.proto.grpc.OfferInfo;
import protobuf.PaymentAccount;
import java.math.BigDecimal;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
@ -36,11 +40,13 @@ import static bisq.apitest.config.BisqAppConfig.alicedaemon;
import static bisq.apitest.config.BisqAppConfig.arbdaemon;
import static bisq.apitest.config.BisqAppConfig.bobdaemon;
import static bisq.apitest.config.BisqAppConfig.seednode;
import static bisq.cli.table.builder.TableType.OFFER_TBL;
import static bisq.common.util.MathUtils.exactMultiply;
import bisq.apitest.method.MethodTest;
import bisq.cli.table.builder.TableBuilder;
@Slf4j
public abstract class AbstractOfferTest extends MethodTest {
@ -52,8 +58,11 @@ public abstract class AbstractOfferTest extends MethodTest {
@Setter
protected static boolean isLongRunningTest;
protected static PaymentAccount alicesBsqAcct;
protected static PaymentAccount bobsBsqAcct;
protected static PaymentAccount alicesBsqSwapAcct;
protected static PaymentAccount bobsBsqSwapAcct;
// TODO Deprecate legacy BSQ accounts when no longer in use.
protected static PaymentAccount alicesLegacyBsqAcct;
protected static PaymentAccount bobsLegacyBsqAcct;
@BeforeAll
public static void setUp() {
@ -64,17 +73,9 @@ public abstract class AbstractOfferTest extends MethodTest {
arbdaemon,
alicedaemon,
bobdaemon);
}
public static void createBsqSwapBsqPaymentAccounts() {
alicesBsqAcct = aliceClient.createCryptoCurrencyPaymentAccount("Alice's BsqSwap Account",
BSQ,
aliceClient.getUnusedBsqAddress(), // TODO refactor, bsq address not needed for atom acct
false);
bobsBsqAcct = bobClient.createCryptoCurrencyPaymentAccount("Bob's BsqSwap Account",
BSQ,
bobClient.getUnusedBsqAddress(), // TODO refactor, bsq address not needed for atom acct
false);
initSwapPaymentAccounts();
createLegacyBsqPaymentAccounts();
}
// Mkt Price Margin value of offer returned from server is scaled down by 10^-2.
@ -105,13 +106,31 @@ public abstract class AbstractOfferTest extends MethodTest {
return priceAsBigDecimal.toPlainString();
};
protected final Function<OfferInfo, String> toOfferTable = (offer) ->
new TableBuilder(OFFER_TBL, offer).build().toString();
protected final Function<List<OfferInfo>, String> toOffersTable = (offers) ->
new TableBuilder(OFFER_TBL, offers).build().toString();
// TODO
protected final Function<BsqSwapOfferInfo, String> toBsqSwapOfferTable = (offer) ->
new TableBuilder(OFFER_TBL, offer).build().toString();
public static void initSwapPaymentAccounts() {
// A bot may not know what the default 'BSQ Swap' account name is,
// but API test cases do: the value of the i18n property 'BSQ_SWAP'.
alicesBsqSwapAcct = aliceClient.getPaymentAccount("BSQ Swap");
bobsBsqSwapAcct = bobClient.getPaymentAccount("BSQ Swap");
}
@SuppressWarnings("ConstantConditions")
public static void createBsqPaymentAccounts() {
alicesBsqAcct = aliceClient.createCryptoCurrencyPaymentAccount("Alice's BSQ Account",
public static void createLegacyBsqPaymentAccounts() {
alicesLegacyBsqAcct = aliceClient.createCryptoCurrencyPaymentAccount("Alice's Legacy BSQ Account",
BSQ,
aliceClient.getUnusedBsqAddress(),
false);
bobsBsqAcct = bobClient.createCryptoCurrencyPaymentAccount("Bob's BSQ Account",
bobsLegacyBsqAcct = bobClient.createCryptoCurrencyPaymentAccount("Bob's Legacy BSQ Account",
BSQ,
bobClient.getUnusedBsqAddress(),
false);

View File

@ -31,7 +31,6 @@ import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.config.ApiTestConfig.BSQ;
import static bisq.apitest.config.ApiTestConfig.BTC;
import static bisq.cli.TableFormat.formatBalancesTbls;
import static java.lang.String.format;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
@ -46,7 +45,6 @@ public class BsqSwapOfferTest extends AbstractOfferTest {
@BeforeAll
public static void setUp() {
AbstractOfferTest.setUp();
createBsqSwapBsqPaymentAccounts();
}
@BeforeEach
@ -115,7 +113,7 @@ public class BsqSwapOfferTest extends AbstractOfferTest {
1_000_000L,
1_000_000L,
"0.00005",
alicesBsqAcct.getId());
alicesBsqSwapAcct.getId());
log.debug("BsqSwap Sell BSQ (Buy BTC) OFFER:\n{}", bsqSwapOffer);
var newOfferId = bsqSwapOffer.getId();
assertNotEquals("", newOfferId);

View File

@ -32,10 +32,7 @@ import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.config.ApiTestConfig.BSQ;
import static bisq.apitest.config.ApiTestConfig.BTC;
import static bisq.cli.TableFormat.formatBalancesTbls;
import static bisq.cli.TableFormat.formatOfferTable;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static java.util.Collections.singletonList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
@ -53,7 +50,6 @@ public class CreateBSQOffersTest extends AbstractOfferTest {
@BeforeAll
public static void setUp() {
AbstractOfferTest.setUp();
createBsqPaymentAccounts();
}
@Test
@ -68,9 +64,9 @@ public class CreateBSQOffersTest extends AbstractOfferTest {
100_000_000L,
"0.00005", // FIXED PRICE IN BTC (satoshis) FOR 1 BSQ
getDefaultBuyerSecurityDepositAsPercent(),
alicesBsqAcct.getId(),
alicesLegacyBsqAcct.getId(),
MAKER_FEE_CURRENCY_CODE);
log.info("Sell BSQ (Buy BTC) OFFER:\n{}", formatOfferTable(singletonList(newOffer), BSQ));
log.debug("Sell BSQ (Buy BTC) OFFER:\n{}", toOfferTable.apply(newOffer));
assertTrue(newOffer.getIsMyOffer());
assertTrue(newOffer.getIsMyPendingOffer());
@ -82,7 +78,7 @@ public class CreateBSQOffersTest extends AbstractOfferTest {
assertEquals(100_000_000L, newOffer.getAmount());
assertEquals(100_000_000L, newOffer.getMinAmount());
assertEquals(15_000_000, newOffer.getBuyerSecurityDeposit());
assertEquals(alicesBsqAcct.getId(), newOffer.getPaymentAccountId());
assertEquals(alicesLegacyBsqAcct.getId(), newOffer.getPaymentAccountId());
assertEquals(BSQ, newOffer.getBaseCurrencyCode());
assertEquals(BTC, newOffer.getCounterCurrencyCode());
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
@ -99,7 +95,7 @@ public class CreateBSQOffersTest extends AbstractOfferTest {
assertEquals(100_000_000L, newOffer.getAmount());
assertEquals(100_000_000L, newOffer.getMinAmount());
assertEquals(15_000_000, newOffer.getBuyerSecurityDeposit());
assertEquals(alicesBsqAcct.getId(), newOffer.getPaymentAccountId());
assertEquals(alicesLegacyBsqAcct.getId(), newOffer.getPaymentAccountId());
assertEquals(BSQ, newOffer.getBaseCurrencyCode());
assertEquals(BTC, newOffer.getCounterCurrencyCode());
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
@ -115,9 +111,9 @@ public class CreateBSQOffersTest extends AbstractOfferTest {
100_000_000L,
"0.00005", // FIXED PRICE IN BTC (satoshis) FOR 1 BSQ
getDefaultBuyerSecurityDepositAsPercent(),
alicesBsqAcct.getId(),
alicesLegacyBsqAcct.getId(),
MAKER_FEE_CURRENCY_CODE);
log.info("SELL 20K BSQ OFFER:\n{}", formatOfferTable(singletonList(newOffer), BSQ));
log.debug("SELL 20K BSQ OFFER:\n{}", toOfferTable.apply(newOffer));
assertTrue(newOffer.getIsMyOffer());
assertTrue(newOffer.getIsMyPendingOffer());
@ -129,7 +125,7 @@ public class CreateBSQOffersTest extends AbstractOfferTest {
assertEquals(100_000_000L, newOffer.getAmount());
assertEquals(100_000_000L, newOffer.getMinAmount());
assertEquals(15_000_000, newOffer.getBuyerSecurityDeposit());
assertEquals(alicesBsqAcct.getId(), newOffer.getPaymentAccountId());
assertEquals(alicesLegacyBsqAcct.getId(), newOffer.getPaymentAccountId());
assertEquals(BSQ, newOffer.getBaseCurrencyCode());
assertEquals(BTC, newOffer.getCounterCurrencyCode());
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
@ -146,7 +142,7 @@ public class CreateBSQOffersTest extends AbstractOfferTest {
assertEquals(100_000_000L, newOffer.getAmount());
assertEquals(100_000_000L, newOffer.getMinAmount());
assertEquals(15_000_000, newOffer.getBuyerSecurityDeposit());
assertEquals(alicesBsqAcct.getId(), newOffer.getPaymentAccountId());
assertEquals(alicesLegacyBsqAcct.getId(), newOffer.getPaymentAccountId());
assertEquals(BSQ, newOffer.getBaseCurrencyCode());
assertEquals(BTC, newOffer.getCounterCurrencyCode());
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
@ -162,9 +158,9 @@ public class CreateBSQOffersTest extends AbstractOfferTest {
5_000_000L,
"0.00005", // FIXED PRICE IN BTC sats FOR 1 BSQ
getDefaultBuyerSecurityDepositAsPercent(),
alicesBsqAcct.getId(),
alicesLegacyBsqAcct.getId(),
MAKER_FEE_CURRENCY_CODE);
log.info("BUY 1-2K BSQ OFFER:\n{}", formatOfferTable(singletonList(newOffer), BSQ));
log.debug("BUY 1-2K BSQ OFFER:\n{}", toOfferTable.apply(newOffer));
assertTrue(newOffer.getIsMyOffer());
assertTrue(newOffer.getIsMyPendingOffer());
@ -176,7 +172,7 @@ public class CreateBSQOffersTest extends AbstractOfferTest {
assertEquals(10_000_000L, newOffer.getAmount());
assertEquals(5_000_000L, newOffer.getMinAmount());
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
assertEquals(alicesBsqAcct.getId(), newOffer.getPaymentAccountId());
assertEquals(alicesLegacyBsqAcct.getId(), newOffer.getPaymentAccountId());
assertEquals(BSQ, newOffer.getBaseCurrencyCode());
assertEquals(BTC, newOffer.getCounterCurrencyCode());
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
@ -193,7 +189,7 @@ public class CreateBSQOffersTest extends AbstractOfferTest {
assertEquals(10_000_000L, newOffer.getAmount());
assertEquals(5_000_000L, newOffer.getMinAmount());
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
assertEquals(alicesBsqAcct.getId(), newOffer.getPaymentAccountId());
assertEquals(alicesLegacyBsqAcct.getId(), newOffer.getPaymentAccountId());
assertEquals(BSQ, newOffer.getBaseCurrencyCode());
assertEquals(BTC, newOffer.getCounterCurrencyCode());
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
@ -209,9 +205,9 @@ public class CreateBSQOffersTest extends AbstractOfferTest {
25_000_000L,
"0.00005", // FIXED PRICE IN BTC sats FOR 1 BSQ
getDefaultBuyerSecurityDepositAsPercent(),
alicesBsqAcct.getId(),
alicesLegacyBsqAcct.getId(),
MAKER_FEE_CURRENCY_CODE);
log.info("SELL 5-10K BSQ OFFER:\n{}", formatOfferTable(singletonList(newOffer), BSQ));
log.debug("SELL 5-10K BSQ OFFER:\n{}", toOfferTable.apply(newOffer));
assertTrue(newOffer.getIsMyOffer());
assertTrue(newOffer.getIsMyPendingOffer());
@ -223,7 +219,7 @@ public class CreateBSQOffersTest extends AbstractOfferTest {
assertEquals(50_000_000L, newOffer.getAmount());
assertEquals(25_000_000L, newOffer.getMinAmount());
assertEquals(7_500_000, newOffer.getBuyerSecurityDeposit());
assertEquals(alicesBsqAcct.getId(), newOffer.getPaymentAccountId());
assertEquals(alicesLegacyBsqAcct.getId(), newOffer.getPaymentAccountId());
assertEquals(BSQ, newOffer.getBaseCurrencyCode());
assertEquals(BTC, newOffer.getCounterCurrencyCode());
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
@ -240,7 +236,7 @@ public class CreateBSQOffersTest extends AbstractOfferTest {
assertEquals(50_000_000L, newOffer.getAmount());
assertEquals(25_000_000L, newOffer.getMinAmount());
assertEquals(7_500_000, newOffer.getBuyerSecurityDeposit());
assertEquals(alicesBsqAcct.getId(), newOffer.getPaymentAccountId());
assertEquals(alicesLegacyBsqAcct.getId(), newOffer.getPaymentAccountId());
assertEquals(BSQ, newOffer.getBaseCurrencyCode());
assertEquals(BTC, newOffer.getCounterCurrencyCode());
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
@ -250,18 +246,18 @@ public class CreateBSQOffersTest extends AbstractOfferTest {
@Order(5)
public void testGetAllMyBsqOffers() {
List<OfferInfo> offers = aliceClient.getMyBsqOffersSortedByDate();
log.info("ALL ALICE'S BSQ OFFERS:\n{}", formatOfferTable(offers, BSQ));
log.debug("ALL ALICE'S BSQ OFFERS:\n{}", toOffersTable.apply(offers));
assertEquals(4, offers.size());
log.info("ALICE'S BALANCES\n{}", formatBalancesTbls(aliceClient.getBalances()));
log.debug("ALICE'S BALANCES\n{}", formatBalancesTbls(aliceClient.getBalances()));
}
@Test
@Order(6)
public void testGetAvailableBsqOffers() {
List<OfferInfo> offers = bobClient.getBsqOffersSortedByDate();
log.info("ALL BOB'S AVAILABLE BSQ OFFERS:\n{}", formatOfferTable(offers, BSQ));
log.debug("ALL BOB'S AVAILABLE BSQ OFFERS:\n{}", toOffersTable.apply(offers));
assertEquals(4, offers.size());
log.info("BOB'S BALANCES\n{}", formatBalancesTbls(bobClient.getBalances()));
log.debug("BOB'S BALANCES\n{}", formatBalancesTbls(bobClient.getBalances()));
}
private void genBtcBlockAndWaitForOfferPreparation() {

View File

@ -29,9 +29,9 @@ import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.config.ApiTestConfig.BSQ;
import static bisq.apitest.config.ApiTestConfig.BTC;
import static bisq.cli.TableFormat.formatOfferTable;
import static bisq.apitest.config.ApiTestConfig.EUR;
import static bisq.apitest.config.ApiTestConfig.USD;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static java.util.Collections.singletonList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
@ -58,7 +58,7 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
getDefaultBuyerSecurityDepositAsPercent(),
audAccount.getId(),
MAKER_FEE_CURRENCY_CODE);
log.info("OFFER #1:\n{}", formatOfferTable(singletonList(newOffer), "AUD"));
log.debug("OFFER #1:\n{}", toOfferTable.apply(newOffer));
assertTrue(newOffer.getIsMyOffer());
assertTrue(newOffer.getIsMyPendingOffer());
@ -103,7 +103,7 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
getDefaultBuyerSecurityDepositAsPercent(),
usdAccount.getId(),
MAKER_FEE_CURRENCY_CODE);
log.info("OFFER #2:\n{}", formatOfferTable(singletonList(newOffer), "USD"));
log.debug("OFFER #2:\n{}", toOfferTable.apply(newOffer));
assertTrue(newOffer.getIsMyOffer());
assertTrue(newOffer.getIsMyPendingOffer());
@ -117,7 +117,7 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
assertEquals(BTC, newOffer.getBaseCurrencyCode());
assertEquals("USD", newOffer.getCounterCurrencyCode());
assertEquals(USD, newOffer.getCounterCurrencyCode());
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
newOffer = aliceClient.getMyOffer(newOfferId);
@ -132,7 +132,7 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
assertEquals(BTC, newOffer.getBaseCurrencyCode());
assertEquals("USD", newOffer.getCounterCurrencyCode());
assertEquals(USD, newOffer.getCounterCurrencyCode());
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
}
@ -148,7 +148,7 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
getDefaultBuyerSecurityDepositAsPercent(),
eurAccount.getId(),
MAKER_FEE_CURRENCY_CODE);
log.info("OFFER #3:\n{}", formatOfferTable(singletonList(newOffer), "EUR"));
log.debug("OFFER #3:\n{}", toOfferTable.apply(newOffer));
assertTrue(newOffer.getIsMyOffer());
assertTrue(newOffer.getIsMyPendingOffer());
@ -162,7 +162,7 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
assertEquals(eurAccount.getId(), newOffer.getPaymentAccountId());
assertEquals(BTC, newOffer.getBaseCurrencyCode());
assertEquals("EUR", newOffer.getCounterCurrencyCode());
assertEquals(EUR, newOffer.getCounterCurrencyCode());
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
newOffer = aliceClient.getMyOffer(newOfferId);
@ -177,7 +177,7 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
assertEquals(eurAccount.getId(), newOffer.getPaymentAccountId());
assertEquals(BTC, newOffer.getBaseCurrencyCode());
assertEquals("EUR", newOffer.getCounterCurrencyCode());
assertEquals(EUR, newOffer.getCounterCurrencyCode());
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
}
}

View File

@ -38,7 +38,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.config.ApiTestConfig.BTC;
import static bisq.cli.TableFormat.formatOfferTable;
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;
@ -47,7 +47,6 @@ import static bisq.core.locale.CurrencyUtil.isCryptoCurrency;
import static java.lang.Math.abs;
import static java.lang.String.format;
import static java.math.RoundingMode.HALF_UP;
import static java.util.Collections.singletonList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
@ -81,7 +80,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
usdAccount.getId(),
MAKER_FEE_CURRENCY_CODE,
NO_TRIGGER_PRICE);
log.info("OFFER #1:\n{}", formatOfferTable(singletonList(newOffer), "usd"));
log.debug("OFFER #1:\n{}", toOfferTable.apply(newOffer));
assertTrue(newOffer.getIsMyOffer());
assertTrue(newOffer.getIsMyPendingOffer());
@ -94,7 +93,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
assertEquals(BTC, newOffer.getBaseCurrencyCode());
assertEquals("USD", newOffer.getCounterCurrencyCode());
assertEquals(USD, newOffer.getCounterCurrencyCode());
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
newOffer = aliceClient.getMyOffer(newOfferId);
@ -108,7 +107,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
assertEquals(BTC, newOffer.getBaseCurrencyCode());
assertEquals("USD", newOffer.getCounterCurrencyCode());
assertEquals(USD, newOffer.getCounterCurrencyCode());
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
@ -128,7 +127,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
nzdAccount.getId(),
MAKER_FEE_CURRENCY_CODE,
NO_TRIGGER_PRICE);
log.info("OFFER #2:\n{}", formatOfferTable(singletonList(newOffer), "nzd"));
log.debug("OFFER #2:\n{}", toOfferTable.apply(newOffer));
assertTrue(newOffer.getIsMyOffer());
assertTrue(newOffer.getIsMyPendingOffer());
@ -175,7 +174,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
gbpAccount.getId(),
MAKER_FEE_CURRENCY_CODE,
NO_TRIGGER_PRICE);
log.info("OFFER #3:\n{}", formatOfferTable(singletonList(newOffer), "gbp"));
log.debug("OFFER #3:\n{}", toOfferTable.apply(newOffer));
assertTrue(newOffer.getIsMyOffer());
assertTrue(newOffer.getIsMyPendingOffer());
@ -222,7 +221,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
brlAccount.getId(),
MAKER_FEE_CURRENCY_CODE,
NO_TRIGGER_PRICE);
log.info("OFFER #4:\n{}", formatOfferTable(singletonList(newOffer), "brl"));
log.debug("OFFER #4:\n{}", toOfferTable.apply(newOffer));
assertTrue(newOffer.getIsMyOffer());
assertTrue(newOffer.getIsMyPendingOffer());
@ -262,7 +261,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
double mktPriceAsDouble = aliceClient.getBtcPrice("usd");
BigDecimal mktPrice = new BigDecimal(Double.toString(mktPriceAsDouble));
BigDecimal triggerPrice = mktPrice.add(new BigDecimal("1000.9999"));
long triggerPriceAsLong = Price.parse("USD", triggerPrice.toString()).getValue();
long triggerPriceAsLong = Price.parse(USD, triggerPrice.toString()).getValue();
var newOffer = aliceClient.createMarketBasedPricedOffer(BUY.name(),
"usd",
@ -278,7 +277,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
genBtcBlocksThenWait(1, 4000); // give time to add to offer book
newOffer = aliceClient.getMyOffer(newOffer.getId());
log.info("OFFER #5:\n{}", formatOfferTable(singletonList(newOffer), "usd"));
log.debug("OFFER #5:\n{}", toOfferTable.apply(newOffer));
assertTrue(newOffer.getIsMyOffer());
assertFalse(newOffer.getIsMyPendingOffer());
assertEquals(triggerPriceAsLong, newOffer.getTriggerPrice());

View File

@ -38,11 +38,11 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.config.ApiTestConfig.BSQ;
import static bisq.cli.TableFormat.formatOfferTable;
import static bisq.apitest.config.ApiTestConfig.EUR;
import static bisq.apitest.config.ApiTestConfig.USD;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static bisq.proto.grpc.EditOfferRequest.EditType.*;
import static java.lang.String.format;
import static java.util.Collections.singletonList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
@ -58,7 +58,6 @@ public class EditOfferTest extends AbstractOfferTest {
// Some test fixtures to reduce duplication.
private static final Map<String, PaymentAccount> paymentAcctCache = new HashMap<>();
private static final String DOLLAR = "USD";
private static final String RUBLE = "RUB";
private static final long AMOUNT = 10000000L;
@ -67,11 +66,11 @@ public class EditOfferTest extends AbstractOfferTest {
public void testOfferDisableAndEnable() {
PaymentAccount paymentAcct = getOrCreatePaymentAccount("DE");
OfferInfo originalOffer = createMktPricedOfferForEdit(BUY.name(),
"EUR",
EUR,
paymentAcct.getId(),
0.0,
NO_TRIGGER_PRICE);
log.info("ORIGINAL EUR OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "EUR"));
log.debug("ORIGINAL EUR OFFER:\n{}", toOfferTable.apply(originalOffer));
assertFalse(originalOffer.getIsActivated()); // Not activated until prep is done.
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
originalOffer = aliceClient.getMyOffer(originalOffer.getId());
@ -80,13 +79,13 @@ public class EditOfferTest extends AbstractOfferTest {
aliceClient.editOfferActivationState(originalOffer.getId(), DEACTIVATE_OFFER);
genBtcBlocksThenWait(1, 1500); // Wait for offer book removal.
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
log.info("EDITED EUR OFFER:\n{}", formatOfferTable(singletonList(editedOffer), "EUR"));
log.debug("EDITED EUR OFFER:\n{}", toOfferTable.apply(editedOffer));
assertFalse(editedOffer.getIsActivated());
// Re-enable offer
aliceClient.editOfferActivationState(editedOffer.getId(), ACTIVATE_OFFER);
genBtcBlocksThenWait(1, 1500); // Wait for offer book re-entry.
editedOffer = aliceClient.getMyOffer(originalOffer.getId());
log.info("EDITED EUR OFFER:\n{}", formatOfferTable(singletonList(editedOffer), "EUR"));
log.debug("EDITED EUR OFFER:\n{}", toOfferTable.apply(editedOffer));
assertTrue(editedOffer.getIsActivated());
doSanityCheck(originalOffer, editedOffer);
@ -97,24 +96,24 @@ public class EditOfferTest extends AbstractOfferTest {
public void testEditTriggerPrice() {
PaymentAccount paymentAcct = getOrCreatePaymentAccount("FI");
OfferInfo originalOffer = createMktPricedOfferForEdit(SELL.name(),
"EUR",
EUR,
paymentAcct.getId(),
0.0,
NO_TRIGGER_PRICE);
log.info("ORIGINAL EUR OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "EUR"));
log.debug("ORIGINAL EUR OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
originalOffer = aliceClient.getMyOffer(originalOffer.getId());
assertEquals(0 /*no trigger price*/, originalOffer.getTriggerPrice());
// Edit the offer's trigger price, nothing else.
var mktPrice = aliceClient.getBtcPrice("EUR");
var mktPrice = aliceClient.getBtcPrice(EUR);
var delta = 5_000.00;
var newTriggerPriceAsLong = calcPriceAsLong.apply(mktPrice, delta);
aliceClient.editOfferTriggerPrice(originalOffer.getId(), newTriggerPriceAsLong);
sleep(2500); // Wait for offer book re-entry.
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
log.info("EDITED EUR OFFER:\n{}", formatOfferTable(singletonList(editedOffer), "EUR"));
log.debug("EDITED EUR OFFER:\n{}", toOfferTable.apply(editedOffer));
assertEquals(newTriggerPriceAsLong, editedOffer.getTriggerPrice());
assertTrue(editedOffer.getUseMarketBasedPrice());
@ -126,11 +125,11 @@ public class EditOfferTest extends AbstractOfferTest {
public void testSetTriggerPriceToNegativeValueShouldThrowException() {
PaymentAccount paymentAcct = getOrCreatePaymentAccount("FI");
final OfferInfo originalOffer = createMktPricedOfferForEdit(SELL.name(),
"EUR",
EUR,
paymentAcct.getId(),
0.0,
NO_TRIGGER_PRICE);
log.info("ORIGINAL EUR OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "EUR"));
log.debug("ORIGINAL EUR OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
// Edit the offer's trigger price, set to -1, check error.
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
@ -147,18 +146,18 @@ public class EditOfferTest extends AbstractOfferTest {
PaymentAccount paymentAcct = getOrCreatePaymentAccount("US");
var originalMktPriceMargin = new BigDecimal("0.1").doubleValue();
OfferInfo originalOffer = createMktPricedOfferForEdit(SELL.name(),
DOLLAR,
USD,
paymentAcct.getId(),
originalMktPriceMargin,
NO_TRIGGER_PRICE);
log.info("ORIGINAL USD OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "USD"));
log.debug("ORIGINAL USD OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
assertEquals(scaledDownMktPriceMargin.apply(originalMktPriceMargin), originalOffer.getMarketPriceMargin());
// Edit the offer's price margin, nothing else.
var newMktPriceMargin = new BigDecimal("0.5").doubleValue();
aliceClient.editOfferPriceMargin(originalOffer.getId(), newMktPriceMargin);
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
log.info("EDITED USD OFFER:\n{}", formatOfferTable(singletonList(editedOffer), "USD"));
log.debug("EDITED USD OFFER:\n{}", toOfferTable.apply(editedOffer));
assertEquals(scaledDownMktPriceMargin.apply(newMktPriceMargin), editedOffer.getMarketPriceMargin());
doSanityCheck(originalOffer, editedOffer);
@ -174,7 +173,7 @@ public class EditOfferTest extends AbstractOfferTest {
RUBLE,
paymentAcct.getId(),
fixedPriceAsString);
log.info("ORIGINAL RUB OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "RUB"));
log.debug("ORIGINAL RUB OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
// Edit the offer's fixed price, nothing else.
String editedFixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 100_000.0000);
@ -182,7 +181,7 @@ public class EditOfferTest extends AbstractOfferTest {
// Wait for edited offer to be removed from offer-book, edited, and re-published.
genBtcBlocksThenWait(1, 2500);
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
log.info("EDITED RUB OFFER:\n{}", formatOfferTable(singletonList(editedOffer), "RUB"));
log.debug("EDITED RUB OFFER:\n{}", toOfferTable.apply(editedOffer));
var expectedNewFixedPrice = scaledUpFiatOfferPrice.apply(new BigDecimal(editedFixedPriceAsString));
assertEquals(expectedNewFixedPrice, editedOffer.getPrice());
assertFalse(editedOffer.getUseMarketBasedPrice());
@ -200,7 +199,7 @@ public class EditOfferTest extends AbstractOfferTest {
RUBLE,
paymentAcct.getId(),
fixedPriceAsString);
log.info("ORIGINAL RUB OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "RUB"));
log.debug("ORIGINAL RUB OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
// Edit the offer's fixed price and deactivate it.
String editedFixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 100_000.0000);
@ -214,7 +213,7 @@ public class EditOfferTest extends AbstractOfferTest {
// Wait for edited offer to be removed from offer-book, edited, and re-published.
genBtcBlocksThenWait(1, 2500);
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
log.info("EDITED RUB OFFER:\n{}", formatOfferTable(singletonList(editedOffer), "RUB"));
log.debug("EDITED RUB OFFER:\n{}", toOfferTable.apply(editedOffer));
var expectedNewFixedPrice = scaledUpFiatOfferPrice.apply(new BigDecimal(editedFixedPriceAsString));
assertEquals(expectedNewFixedPrice, editedOffer.getPrice());
assertFalse(editedOffer.getIsActivated());
@ -229,11 +228,11 @@ public class EditOfferTest extends AbstractOfferTest {
var originalMktPriceMargin = new BigDecimal("0.0").doubleValue();
OfferInfo originalOffer = createMktPricedOfferForEdit(SELL.name(),
DOLLAR,
USD,
paymentAcct.getId(),
originalMktPriceMargin,
0);
log.info("ORIGINAL USD OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "USD"));
log.debug("ORIGINAL USD OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
originalOffer = aliceClient.getMyOffer(originalOffer.getId());
assertEquals(scaledDownMktPriceMargin.apply(originalMktPriceMargin), originalOffer.getMarketPriceMargin());
@ -250,7 +249,7 @@ public class EditOfferTest extends AbstractOfferTest {
// Wait for edited offer to be removed from offer-book, edited, and re-published.
genBtcBlocksThenWait(1, 2500);
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
log.info("EDITED USD OFFER:\n{}", formatOfferTable(singletonList(editedOffer), "USD"));
log.debug("EDITED USD OFFER:\n{}", toOfferTable.apply(editedOffer));
assertEquals(scaledDownMktPriceMargin.apply(newMktPriceMargin), editedOffer.getMarketPriceMargin());
assertEquals(0, editedOffer.getTriggerPrice());
assertFalse(editedOffer.getIsActivated());
@ -264,15 +263,15 @@ public class EditOfferTest extends AbstractOfferTest {
PaymentAccount paymentAcct = getOrCreatePaymentAccount("US");
var originalMktPriceMargin = new BigDecimal("0.0").doubleValue();
var mktPriceAsDouble = aliceClient.getBtcPrice(DOLLAR);
var mktPriceAsDouble = aliceClient.getBtcPrice(USD);
var originalTriggerPriceAsLong = calcPriceAsLong.apply(mktPriceAsDouble, -5_000.0000);
OfferInfo originalOffer = createMktPricedOfferForEdit(SELL.name(),
DOLLAR,
USD,
paymentAcct.getId(),
originalMktPriceMargin,
originalTriggerPriceAsLong);
log.info("ORIGINAL USD OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "USD"));
log.debug("ORIGINAL USD OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
originalOffer = aliceClient.getMyOffer(originalOffer.getId());
assertEquals(scaledDownMktPriceMargin.apply(originalMktPriceMargin), originalOffer.getMarketPriceMargin());
@ -291,7 +290,7 @@ public class EditOfferTest extends AbstractOfferTest {
// Wait for edited offer to be removed from offer-book, edited, and re-published.
genBtcBlocksThenWait(1, 2500);
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
log.info("EDITED USD OFFER:\n{}", formatOfferTable(singletonList(editedOffer), "USD"));
log.debug("EDITED USD OFFER:\n{}", toOfferTable.apply(editedOffer));
assertEquals(scaledDownMktPriceMargin.apply(newMktPriceMargin), editedOffer.getMarketPriceMargin());
assertEquals(newTriggerPriceAsLong, editedOffer.getTriggerPrice());
assertFalse(editedOffer.getIsActivated());
@ -305,11 +304,11 @@ public class EditOfferTest extends AbstractOfferTest {
PaymentAccount paymentAcct = getOrCreatePaymentAccount("US");
var originalMktPriceMargin = new BigDecimal("0.0").doubleValue();
final OfferInfo originalOffer = createMktPricedOfferForEdit(SELL.name(),
DOLLAR,
USD,
paymentAcct.getId(),
originalMktPriceMargin,
NO_TRIGGER_PRICE);
log.info("ORIGINAL USD OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "USD"));
log.debug("ORIGINAL USD OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
// Try to edit both the fixed price and mkt price margin.
var newMktPriceMargin = new BigDecimal("0.25").doubleValue();
@ -340,7 +339,7 @@ public class EditOfferTest extends AbstractOfferTest {
RUBLE,
paymentAcct.getId(),
fixedPriceAsString);
log.info("ORIGINAL RUB OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "RUB"));
log.debug("ORIGINAL RUB OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
long newTriggerPrice = 1000000L;
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
@ -363,7 +362,7 @@ public class EditOfferTest extends AbstractOfferTest {
"MXN",
paymentAcct.getId(),
fixedPriceAsString);
log.info("ORIGINAL MXN OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "MXN"));
log.debug("ORIGINAL MXN OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
// Change the offer to mkt price based and set a trigger price.
@ -380,7 +379,7 @@ public class EditOfferTest extends AbstractOfferTest {
// Wait for edited offer to be removed from offer-book, edited, and re-published.
genBtcBlocksThenWait(1, 2500);
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
log.info("EDITED MXN OFFER:\n{}", formatOfferTable(singletonList(editedOffer), "MXN"));
log.debug("EDITED MXN OFFER:\n{}", toOfferTable.apply(editedOffer));
assertTrue(editedOffer.getUseMarketBasedPrice());
assertEquals(scaledDownMktPriceMargin.apply(newMktPriceMargin), editedOffer.getMarketPriceMargin());
assertEquals(newTriggerPriceAsLong, editedOffer.getTriggerPrice());
@ -402,7 +401,7 @@ public class EditOfferTest extends AbstractOfferTest {
paymentAcct.getId(),
originalMktPriceMargin,
originalTriggerPriceAsLong);
log.info("ORIGINAL GBP OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "GBP"));
log.debug("ORIGINAL GBP OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
String fixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 0.00);
@ -416,7 +415,7 @@ public class EditOfferTest extends AbstractOfferTest {
// Wait for edited offer to be removed from offer-book, edited, and re-published.
genBtcBlocksThenWait(1, 2500);
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
log.info("EDITED GBP OFFER:\n{}", formatOfferTable(singletonList(editedOffer), "GBP"));
log.debug("EDITED GBP OFFER:\n{}", toOfferTable.apply(editedOffer));
assertEquals(scaledUpFiatOfferPrice.apply(new BigDecimal(fixedPriceAsString)), editedOffer.getPrice());
assertFalse(editedOffer.getUseMarketBasedPrice());
assertEquals(0.00, editedOffer.getMarketPriceMargin());
@ -427,16 +426,15 @@ public class EditOfferTest extends AbstractOfferTest {
@Test
@Order(13)
public void testChangeFixedPricedBsqOfferToPriceMarginBasedOfferShouldThrowException() {
createBsqPaymentAccounts();
OfferInfo originalOffer = aliceClient.createFixedPricedOffer(BUY.name(),
BSQ,
100_000_000L,
100_000_000L,
"0.00005", // FIXED PRICE IN BTC (satoshis) FOR 1 BSQ
getDefaultBuyerSecurityDepositAsPercent(),
alicesBsqAcct.getId(),
alicesLegacyBsqAcct.getId(),
BSQ);
log.info("ORIGINAL BSQ OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "BSQ"));
log.debug("ORIGINAL BSQ OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
aliceClient.editOffer(originalOffer.getId(),
@ -455,16 +453,15 @@ public class EditOfferTest extends AbstractOfferTest {
@Test
@Order(14)
public void testEditTriggerPriceOnFixedPriceBsqOfferShouldThrowException() {
createBsqPaymentAccounts();
OfferInfo originalOffer = aliceClient.createFixedPricedOffer(BUY.name(),
BSQ,
100_000_000L,
100_000_000L,
"0.00005", // FIXED PRICE IN BTC (satoshis) FOR 1 BSQ
getDefaultBuyerSecurityDepositAsPercent(),
alicesBsqAcct.getId(),
alicesLegacyBsqAcct.getId(),
BSQ);
log.info("ORIGINAL BSQ OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "BSQ"));
log.debug("ORIGINAL BSQ OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
var newTriggerPriceAsLong = calcPriceAsLong.apply(0.00005, 0.00);
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
@ -484,7 +481,6 @@ public class EditOfferTest extends AbstractOfferTest {
@Test
@Order(15)
public void testEditFixedPriceOnBsqOffer() {
createBsqPaymentAccounts();
String fixedPriceAsString = "0.00005"; // FIXED PRICE IN BTC (satoshis) FOR 1 BSQ
final OfferInfo originalOffer = aliceClient.createFixedPricedOffer(BUY.name(),
BSQ,
@ -492,9 +488,9 @@ public class EditOfferTest extends AbstractOfferTest {
100_000_000L,
fixedPriceAsString,
getDefaultBuyerSecurityDepositAsPercent(),
alicesBsqAcct.getId(),
alicesLegacyBsqAcct.getId(),
BSQ);
log.info("ORIGINAL BSQ OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "BSQ"));
log.debug("ORIGINAL BSQ OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
String newFixedPriceAsString = "0.00003111";
aliceClient.editOffer(originalOffer.getId(),
@ -507,7 +503,7 @@ public class EditOfferTest extends AbstractOfferTest {
// Wait for edited offer to be edited and removed from offer-book.
genBtcBlocksThenWait(1, 2500);
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
log.info("EDITED BSQ OFFER:\n{}", formatOfferTable(singletonList(editedOffer), BSQ));
log.debug("EDITED BSQ OFFER:\n{}", toOfferTable.apply(editedOffer));
assertEquals(scaledUpAltcoinOfferPrice.apply(newFixedPriceAsString), editedOffer.getPrice());
assertTrue(editedOffer.getIsActivated());
assertFalse(editedOffer.getUseMarketBasedPrice());
@ -518,7 +514,6 @@ public class EditOfferTest extends AbstractOfferTest {
@Test
@Order(16)
public void testDisableBsqOffer() {
createBsqPaymentAccounts();
String fixedPriceAsString = "0.00005"; // FIXED PRICE IN BTC (satoshis) FOR 1 BSQ
final OfferInfo originalOffer = aliceClient.createFixedPricedOffer(BUY.name(),
BSQ,
@ -526,9 +521,9 @@ public class EditOfferTest extends AbstractOfferTest {
100_000_000L,
fixedPriceAsString,
getDefaultBuyerSecurityDepositAsPercent(),
alicesBsqAcct.getId(),
alicesLegacyBsqAcct.getId(),
BSQ);
log.info("ORIGINAL BSQ OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "BSQ"));
log.debug("ORIGINAL BSQ OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
aliceClient.editOffer(originalOffer.getId(),
fixedPriceAsString,
@ -540,7 +535,7 @@ public class EditOfferTest extends AbstractOfferTest {
// Wait for edited offer to be removed from offer-book.
genBtcBlocksThenWait(1, 2500);
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
log.info("EDITED BSQ OFFER:\n{}", formatOfferTable(singletonList(editedOffer), BSQ));
log.debug("EDITED BSQ OFFER:\n{}", toOfferTable.apply(editedOffer));
assertFalse(editedOffer.getIsActivated());
assertEquals(scaledUpAltcoinOfferPrice.apply(fixedPriceAsString), editedOffer.getPrice());
assertFalse(editedOffer.getUseMarketBasedPrice());
@ -551,7 +546,6 @@ public class EditOfferTest extends AbstractOfferTest {
@Test
@Order(17)
public void testEditFixedPriceAndDisableBsqOffer() {
createBsqPaymentAccounts();
String fixedPriceAsString = "0.00005"; // FIXED PRICE IN BTC (satoshis) FOR 1 BSQ
final OfferInfo originalOffer = aliceClient.createFixedPricedOffer(BUY.name(),
BSQ,
@ -559,9 +553,9 @@ public class EditOfferTest extends AbstractOfferTest {
100_000_000L,
fixedPriceAsString,
getDefaultBuyerSecurityDepositAsPercent(),
alicesBsqAcct.getId(),
alicesLegacyBsqAcct.getId(),
BSQ);
log.info("ORIGINAL BSQ OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "BSQ"));
log.debug("ORIGINAL BSQ OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
String newFixedPriceAsString = "0.000045";
aliceClient.editOffer(originalOffer.getId(),
@ -574,7 +568,7 @@ public class EditOfferTest extends AbstractOfferTest {
// Wait for edited offer to be edited and removed from offer-book.
genBtcBlocksThenWait(1, 2500);
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
log.info("EDITED BSQ OFFER:\n{}", formatOfferTable(singletonList(editedOffer), BSQ));
log.debug("EDITED BSQ OFFER:\n{}", toOfferTable.apply(editedOffer));
assertFalse(editedOffer.getIsActivated());
assertEquals(scaledUpAltcoinOfferPrice.apply(newFixedPriceAsString), editedOffer.getPrice());
assertFalse(editedOffer.getUseMarketBasedPrice());

View File

@ -73,16 +73,21 @@ 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.BisqAppConfig.alicedaemon;
import static bisq.cli.TableFormat.formatPaymentAcctTbl;
import static bisq.cli.table.builder.TableType.PAYMENT_ACCOUNT_TBL;
import static bisq.core.locale.CurrencyUtil.*;
import static bisq.core.payment.payload.PaymentMethod.*;
import static java.util.Collections.singletonList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import bisq.cli.table.builder.TableBuilder;
@SuppressWarnings({"OptionalGetWithoutIsPresent", "ConstantConditions"})
@Disabled
@Slf4j
@ -226,7 +231,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);
@ -305,7 +310,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());
@ -378,7 +383,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);
@ -463,7 +468,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());
@ -519,7 +524,7 @@ 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());
@ -593,7 +598,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());
@ -712,7 +717,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());
@ -743,7 +748,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());
@ -813,7 +818,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "IT Swift Acct w/ DE Intermediary");
String allFiatCodes = getCommaDelimitedFiatCurrencyCodes(getAllSortedFiatCurrencies());
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_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.");
@ -927,7 +932,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());
@ -1048,7 +1053,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());
@ -1096,7 +1101,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());
@ -1115,7 +1120,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,6 +2,8 @@ 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;
@ -9,16 +11,23 @@ import org.slf4j.Logger;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.TestInfo;
import static bisq.apitest.config.ApiTestConfig.BTC;
import static bisq.cli.CurrencyFormat.formatBsqAmount;
import static bisq.cli.TradeFormat.format;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
import static bisq.cli.table.builder.TableType.TRADE_DETAIL_TBL;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.DEPOSIT_CONFIRMED;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.FIAT_SENT;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.PAYOUT_PUBLISHED;
import static bisq.core.trade.model.bisq_v1.Trade.State.BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG;
import static bisq.core.trade.model.bisq_v1.Trade.State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN;
import static bisq.core.trade.model.bisq_v1.Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG;
import static java.lang.String.format;
import static org.junit.jupiter.api.Assertions.*;
import bisq.apitest.method.offer.AbstractOfferTest;
import bisq.cli.GrpcClient;
import bisq.cli.table.builder.TableBuilder;
public class AbstractTradeTest extends AbstractOfferTest {
@ -28,6 +37,7 @@ public class AbstractTradeTest extends AbstractOfferTest {
protected static String tradeId;
protected final Supplier<Integer> maxTradeStateAndPhaseChecks = () -> isLongRunningTest ? 10 : 2;
private final Function<GrpcClient, String> toUserName = (client) -> client.equals(aliceClient) ? "Alice" : "Bob";
@BeforeAll
public static void initStaticFixtures() {
@ -36,15 +46,128 @@ public class AbstractTradeTest extends AbstractOfferTest {
protected final TradeInfo takeAlicesOffer(String offerId,
String paymentAccountId,
String takerFeeCurrencyCode) {
return bobClient.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode);
String takerFeeCurrencyCode,
boolean generateBtcBlock) {
@SuppressWarnings("ConstantConditions")
var trade = bobClient.takeOffer(offerId,
paymentAccountId,
takerFeeCurrencyCode);
assertNotNull(trade);
assertEquals(offerId, trade.getTradeId());
if (takerFeeCurrencyCode.equals(BTC))
assertTrue(trade.getIsCurrencyForTakerFeeBtc());
else
assertFalse(trade.getIsCurrencyForTakerFeeBtc());
// Cache the trade id for the other tests.
tradeId = trade.getTradeId();
if (generateBtcBlock)
genBtcBlocksThenWait(1, 6_000);
return trade;
}
@SuppressWarnings("unused")
protected final TradeInfo takeBobsOffer(String offerId,
String paymentAccountId,
String takerFeeCurrencyCode) {
return aliceClient.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode);
protected final void waitForDepositConfirmation(Logger log,
TestInfo testInfo,
GrpcClient grpcClient,
String tradeId) {
Predicate<TradeInfo> isTradeInDepositConfirmedStateAndPhase = (t) ->
t.getState().equals(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN.name())
&& t.getPhase().equals(DEPOSIT_CONFIRMED.name());
String userName = toUserName.apply(grpcClient);
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
TradeInfo trade = grpcClient.getTrade(tradeId);
if (!isTradeInDepositConfirmedStateAndPhase.test(trade)) {
log.warn("{} still waiting on trade {} tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
userName,
trade.getShortId(),
trade.getDepositTxId(),
i);
genBtcBlocksThenWait(1, 4_000);
} else {
EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN)
.setPhase(DEPOSIT_CONFIRMED)
.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.getIsDepositConfirmed()) {
fail(format("INVALID_PHASE for trade %s in STATE=%s PHASE=%s, deposit tx never confirmed.",
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.getIsFiatSent()) {
log.warn("{} still waiting for trade {} {}, attempt # {}",
userName,
trade.getShortId(),
BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG,
i);
sleep(5_000);
} else {
// Do not check trade.getOffer().getState() here because
// it might be AVAILABLE, not OFFER_FEE_PAID.
EXPECTED_PROTOCOL_STATUS.setState(BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG)
.setPhase(FIAT_SENT)
.setFiatSent(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_FIAT_PAYMENT_INITIATED_MSG.name()) &&
(t.getPhase().equals(PAYOUT_PUBLISHED.name()) || t.getPhase().equals(FIAT_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 trade %s in STATE=%s PHASE=%s, cannot confirm payment received.",
userName,
trade.getShortId(),
trade.getState(),
trade.getPhase()));
}
}
protected final void verifyExpectedProtocolStatus(TradeInfo trade) {
@ -70,7 +193,7 @@ public class AbstractTradeTest extends AbstractOfferTest {
? contract.getTakerPaymentAccountPayload().getAddress()
: contract.getMakerPaymentAccountPayload().getAddress();
String sendBsqAmount = formatBsqAmount(trade.getOffer().getVolume());
log.info("Sending {} BSQ to address {}", sendBsqAmount, receiverAddress);
log.debug("Sending {} BSQ to address {}", sendBsqAmount, receiverAddress);
grpcClient.sendBsq(receiverAddress, sendBsqAmount, "");
}
@ -85,39 +208,37 @@ public class AbstractTradeTest extends AbstractOfferTest {
: contract.getMakerPaymentAccountPayload().getAddress();
boolean receivedBsqSatoshis = grpcClient.verifyBsqSentToAddress(address, receiveAmountAsString);
if (receivedBsqSatoshis)
log.info("Payment of {} BSQ was received to address {} for trade with id {}.",
log.debug("Payment of {} BSQ was received to address {} for trade with id {}.",
receiveAmountAsString,
address,
trade.getTradeId());
else
fail(String.format("Payment of %s BSQ was was not sent to address %s for trade with id %s.",
fail(format("Payment of %s BSQ was was not sent to address %s for trade with id %s.",
receiveAmountAsString,
address,
trade.getTradeId()));
}
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)));
new TableBuilder(TRADE_DETAIL_TBL, trade).build()));
}
}
}

View File

@ -20,8 +20,6 @@ package bisq.apitest.method.trade;
import bisq.proto.grpc.BsqSwapOfferInfo;
import bisq.proto.grpc.BsqSwapTradeInfo;
import protobuf.BsqSwapTrade;
import java.util.ArrayList;
import java.util.List;
@ -38,11 +36,12 @@ import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.config.ApiTestConfig.BSQ;
import static bisq.apitest.config.ApiTestConfig.BTC;
import static bisq.cli.TableFormat.formatBalancesTbls;
import static java.lang.String.format;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static protobuf.BsqSwapTrade.State.COMPLETED;
import static protobuf.BsqSwapTrade.State.PREPARATION;
import static protobuf.OfferDirection.BUY;
@ -63,7 +62,6 @@ public class BsqSwapTradeTest extends AbstractOfferTest {
@BeforeAll
public static void setUp() {
AbstractOfferTest.setUp();
createBsqSwapBsqPaymentAccounts();
}
@BeforeEach
@ -75,9 +73,9 @@ public class BsqSwapTradeTest extends AbstractOfferTest {
@Order(1)
public void testGetBalancesBeforeTrade() {
var alicesBalances = aliceClient.getBalances();
log.info("Alice's Before Trade Balance:\n{}", formatBalancesTbls(alicesBalances));
log.debug("Alice's Before Trade Balance:\n{}", formatBalancesTbls(alicesBalances));
var bobsBalances = bobClient.getBalances();
log.info("Bob's Before Trade Balance:\n{}", formatBalancesTbls(bobsBalances));
log.debug("Bob's Before Trade Balance:\n{}", formatBalancesTbls(bobsBalances));
}
@Test
@ -87,7 +85,7 @@ public class BsqSwapTradeTest extends AbstractOfferTest {
1_000_000L,
1_000_000L,
"0.00005",
alicesBsqAcct.getId());
alicesBsqSwapAcct.getId());
log.debug("BsqSwap Sell BSQ (Buy BTC) OFFER:\n{}", bsqSwapOffer);
var newOfferId = bsqSwapOffer.getId();
assertNotEquals("", newOfferId);
@ -105,24 +103,25 @@ public class BsqSwapTradeTest extends AbstractOfferTest {
public void testBobTakesBsqSwapOffer() {
var bsqSwapOffer = getAvailableBsqSwapOffer();
var bsqSwapTradeInfo = bobClient.takeBsqSwapOffer(bsqSwapOffer.getId(),
bobsBsqAcct.getId(),
bobsBsqSwapAcct.getId(),
BISQ_FEE_CURRENCY_CODE);
log.debug("Trade at t1: {}", bsqSwapTradeInfo);
assertEquals(BsqSwapTrade.State.PREPARATION.name(), bsqSwapTradeInfo.getState());
assertEquals(PREPARATION.name(), bsqSwapTradeInfo.getState());
genBtcBlocksThenWait(1, 3_000);
bsqSwapTradeInfo = getBsqSwapTrade(bsqSwapTradeInfo.getTradeId());
log.debug("Trade at t2: {}", bsqSwapTradeInfo);
assertEquals(BsqSwapTrade.State.COMPLETED.name(), bsqSwapTradeInfo.getState());
assertEquals(COMPLETED.name(), bsqSwapTradeInfo.getState());
}
@Test
@Order(4)
public void testGetBalancesAfterTrade() {
sleep(2_500); // Give wallet time to finish processing TX.
var alicesBalances = aliceClient.getBalances();
log.info("Alice's After Trade Balance:\n{}", formatBalancesTbls(alicesBalances));
log.debug("Alice's After Trade Balance:\n{}", formatBalancesTbls(alicesBalances));
var bobsBalances = bobClient.getBalances();
log.info("Bob's After Trade Balance:\n{}", formatBalancesTbls(bobsBalances));
log.debug("Bob's After Trade Balance:\n{}", formatBalancesTbls(bobsBalances));
}
private BsqSwapOfferInfo getAvailableBsqSwapOffer() {

View File

@ -1,65 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.apitest.method.trade;
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;
import org.junit.jupiter.api.TestMethodOrder;
import bisq.apitest.method.offer.AbstractOfferTest;
// @Disabled
@Slf4j
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BsqSwapTradeTestLoop extends AbstractOfferTest {
@BeforeAll
public static void setUp() {
AbstractOfferTest.setUp();
createBsqSwapBsqPaymentAccounts();
}
@Test
@Order(1)
public void testGetBalancesBeforeTrade() {
BsqSwapTradeTest test = new BsqSwapTradeTest();
runTradeLoop(test);
}
private void runTradeLoop(BsqSwapTradeTest test) {
// TODO Fix wallet inconsistency bugs after 2nd trades.
for (int tradeCount = 1; tradeCount <= 2; tradeCount++) {
log.warn("================================ Trade # {} ================================", tradeCount);
test.testGetBalancesBeforeTrade();
test.testAliceCreateBsqSwapBuyOffer();
genBtcBlocksThenWait(1, 8000);
test.testBobTakesBsqSwapOffer();
genBtcBlocksThenWait(1, 8000);
test.testGetBalancesAfterTrade();
}
}
}

View File

@ -17,12 +17,8 @@
package bisq.apitest.method.trade;
import bisq.proto.grpc.TradeInfo;
import io.grpc.StatusRuntimeException;
import java.util.function.Predicate;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeAll;
@ -34,18 +30,10 @@ import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.config.ApiTestConfig.BSQ;
import static bisq.cli.TableFormat.formatBalancesTbls;
import static bisq.cli.TableFormat.formatOfferTable;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.DEPOSIT_CONFIRMED;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.FIAT_SENT;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.PAYOUT_PUBLISHED;
import static bisq.core.trade.model.bisq_v1.Trade.State.BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG;
import static bisq.core.trade.model.bisq_v1.Trade.State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN;
import static bisq.core.trade.model.bisq_v1.Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG;
import static bisq.core.trade.model.bisq_v1.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
import static java.lang.String.format;
import static java.util.Collections.singletonList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@ -72,7 +60,6 @@ public class TakeBuyBSQOfferTest extends AbstractTradeTest {
@BeforeAll
public static void setUp() {
AbstractOfferTest.setUp();
createBsqPaymentAccounts();
EXPECTED_PROTOCOL_STATUS.init();
}
@ -90,9 +77,9 @@ public class TakeBuyBSQOfferTest extends AbstractTradeTest {
7_500_000L,
"0.000035", // FIXED PRICE IN BTC (satoshis) FOR 1 BSQ
getDefaultBuyerSecurityDepositAsPercent(),
alicesBsqAcct.getId(),
alicesLegacyBsqAcct.getId(),
TRADE_FEE_CURRENCY_CODE);
log.info("ALICE'S BUY BSQ (SELL BTC) OFFER:\n{}", formatOfferTable(singletonList(alicesOffer), BSQ));
log.debug("ALICE'S BUY BSQ (SELL BTC) OFFER:\n{}", toOfferTable.apply(alicesOffer));
genBtcBlocksThenWait(1, 5000);
var offerId = alicesOffer.getId();
assertFalse(alicesOffer.getIsCurrencyForMakerFeeBtc());
@ -100,50 +87,27 @@ public class TakeBuyBSQOfferTest extends AbstractTradeTest {
var alicesBsqOffers = aliceClient.getMyCryptoCurrencyOffers(btcTradeDirection, BSQ);
assertEquals(1, alicesBsqOffers.size());
var trade = takeAlicesOffer(offerId, bobsBsqAcct.getId(), TRADE_FEE_CURRENCY_CODE);
var trade = takeAlicesOffer(offerId,
bobsLegacyBsqAcct.getId(),
TRADE_FEE_CURRENCY_CODE,
false);
assertNotNull(trade);
assertEquals(offerId, trade.getTradeId());
assertFalse(trade.getIsCurrencyForTakerFeeBtc());
// Cache the trade id for the other tests.
tradeId = trade.getTradeId();
genBtcBlocksThenWait(1, 6000);
genBtcBlocksThenWait(1, 2_500);
alicesBsqOffers = aliceClient.getMyBsqOffersSortedByDate();
assertEquals(0, alicesBsqOffers.size());
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
trade = bobClient.getTrade(trade.getTradeId());
if (!trade.getIsDepositConfirmed()) {
log.warn("Bob still waiting on trade {} tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
trade.getShortId(),
trade.getDepositTxId(),
i);
genBtcBlocksThenWait(1, 4000);
continue;
} else {
EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN)
.setPhase(DEPOSIT_CONFIRMED)
.setDepositPublished(true)
.setDepositConfirmed(true);
verifyExpectedProtocolStatus(trade);
logTrade(log, testInfo, "Bob's view after taking offer and deposit confirmed", trade);
break;
}
}
genBtcBlocksThenWait(1, 2500);
if (!trade.getIsDepositConfirmed()) {
fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, deposit tx was never confirmed.",
trade.getShortId(),
trade.getState(),
trade.getPhase()));
}
logTrade(log, testInfo, "Alice's Maker/Buyer View", aliceClient.getTrade(tradeId), true);
logTrade(log, testInfo, "Bob's Taker/Seller View", bobClient.getTrade(tradeId), true);
waitForDepositConfirmation(log, testInfo, bobClient, trade.getTradeId());
genBtcBlocksThenWait(1, 2_500);
trade = bobClient.getTrade(tradeId);
verifyTakerDepositConfirmed(trade);
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);
}
@ -154,60 +118,14 @@ public class TakeBuyBSQOfferTest extends AbstractTradeTest {
public void testBobsConfirmPaymentStarted(final TestInfo testInfo) {
try {
var trade = bobClient.getTrade(tradeId);
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
t.getState().equals(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN.name())
&& t.getPhase().equals(DEPOSIT_CONFIRMED.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 send payment started msg yet.",
trade.getShortId(),
trade.getState(),
trade.getPhase());
sleep(10_000);
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 send payment started msg.",
trade.getShortId(),
trade.getState(),
trade.getPhase()));
}
verifyTakerDepositConfirmed(trade);
sendBsqPayment(log, bobClient, trade);
genBtcBlocksThenWait(1, 2500);
genBtcBlocksThenWait(1, 2_500);
bobClient.confirmPaymentStarted(trade.getTradeId());
sleep(6000);
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
trade = aliceClient.getTrade(tradeId);
if (!trade.getIsFiatSent()) {
log.warn("Alice still waiting for trade {} SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG, attempt # {}",
trade.getShortId(),
i);
sleep(5000);
continue;
} else {
// Warning: trade.getOffer().getState() might be AVAILABLE, not OFFER_FEE_PAID.
EXPECTED_PROTOCOL_STATUS.setState(SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG)
.setPhase(FIAT_SENT)
.setFiatSent(true);
verifyExpectedProtocolStatus(trade);
logTrade(log, testInfo, "Alice's view after confirming fiat payment received", trade);
break;
}
}
logTrade(log, testInfo, "Alice's Maker/Buyer View (Payment Sent)", aliceClient.getTrade(tradeId), true);
logTrade(log, testInfo, "Bob's Taker/Seller View (Payment Sent)", bobClient.getTrade(tradeId), true);
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);
}
@ -217,39 +135,12 @@ public class TakeBuyBSQOfferTest extends AbstractTradeTest {
@Order(3)
public void testAlicesConfirmPaymentReceived(final TestInfo testInfo) {
try {
waitForSellerSeesPaymentInitiatedMessage(log, testInfo, aliceClient, tradeId);
sleep(2_000);
var trade = aliceClient.getTrade(tradeId);
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
t.getState().equals(SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG.name())
&& (t.getPhase().equals(PAYOUT_PUBLISHED.name()) || t.getPhase().equals(FIAT_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());
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, cannot confirm payment received.",
trade.getShortId(),
trade.getState(),
trade.getPhase()));
}
sleep(2000);
verifyBsqPaymentHasBeenReceived(log, aliceClient, trade);
aliceClient.confirmPaymentReceived(trade.getTradeId());
sleep(3000);
sleep(3_000);
trade = aliceClient.getTrade(tradeId);
assertEquals(OFFER_FEE_PAID.name(), trade.getOffer().getState());
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
@ -257,11 +148,8 @@ public class TakeBuyBSQOfferTest extends AbstractTradeTest {
.setPayoutPublished(true)
.setFiatReceived(true);
verifyExpectedProtocolStatus(trade);
logTrade(log, testInfo, "Alice's view after confirming fiat payment received", trade);
logTrade(log, testInfo, "Alice's Maker/Buyer View (Payment Received)", aliceClient.getTrade(tradeId), true);
logTrade(log, testInfo, "Bob's Taker/Seller View (Payment Received)", bobClient.getTrade(tradeId), true);
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);
}
@ -269,34 +157,23 @@ public class TakeBuyBSQOfferTest extends AbstractTradeTest {
@Test
@Order(4)
public void testBobsKeepFunds(final TestInfo testInfo) {
public void testKeepFunds(final TestInfo testInfo) {
try {
genBtcBlocksThenWait(1, 1000);
genBtcBlocksThenWait(1, 1_000);
var trade = bobClient.getTrade(tradeId);
logTrade(log, testInfo, "Alice's view before keeping funds", trade);
aliceClient.keepFunds(tradeId);
bobClient.keepFunds(tradeId);
genBtcBlocksThenWait(1, 1000);
genBtcBlocksThenWait(1, 1_000);
trade = bobClient.getTrade(tradeId);
EXPECTED_PROTOCOL_STATUS.setState(BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG)
.setPhase(PAYOUT_PUBLISHED);
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));
logTrade(log, testInfo, "Alice's Maker/Buyer View (Done)", aliceClient.getTrade(tradeId));
logTrade(log, testInfo, "Bob's Taker/Seller View (Done)", bobClient.getTrade(tradeId));
logBalances(log, testInfo);
} 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,18 +31,14 @@ import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.config.ApiTestConfig.BSQ;
import static bisq.cli.TableFormat.formatBalancesTbls;
import static bisq.apitest.config.ApiTestConfig.USD;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.DEPOSIT_CONFIRMED;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.FIAT_SENT;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.PAYOUT_PUBLISHED;
import static bisq.core.trade.model.bisq_v1.Trade.State.*;
import static java.lang.String.format;
import static bisq.core.trade.model.bisq_v1.Trade.State.BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG;
import static bisq.core.trade.model.bisq_v1.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
import static protobuf.Offer.State.OFFER_FEE_PAID;
import static protobuf.OfferDirection.BUY;
import static protobuf.OpenOffer.State.AVAILABLE;
@ -66,7 +58,7 @@ 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,
@ -78,53 +70,26 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
assertFalse(alicesOffer.getIsCurrencyForMakerFeeBtc());
// 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(), TRADE_FEE_CURRENCY_CODE);
assertNotNull(trade);
assertEquals(offerId, trade.getTradeId());
assertFalse(trade.getIsCurrencyForTakerFeeBtc());
// 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(),
TRADE_FEE_CURRENCY_CODE,
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.getIsDepositConfirmed()) {
log.warn("Bob still waiting on trade {} tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
trade.getShortId(),
trade.getDepositTxId(),
i);
genBtcBlocksThenWait(1, 4000);
continue;
} else {
EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN)
.setPhase(DEPOSIT_CONFIRMED)
.setDepositPublished(true)
.setDepositConfirmed(true);
verifyExpectedProtocolStatus(trade);
logTrade(log, testInfo, "Bob's view after deposit is confirmed", trade, true);
break;
}
}
if (!trade.getIsDepositConfirmed()) {
fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, deposit tx was never confirmed.",
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);
}
@ -135,56 +100,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_CONFIRMED_IN_BLOCK_CHAIN.name())
&& t.getPhase().equals(DEPOSIT_CONFIRMED.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.getIsFiatSent()) {
log.warn("Alice still waiting for trade {} BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG, attempt # {}",
trade.getShortId(),
i);
sleep(5000);
continue;
} else {
assertEquals(OFFER_FEE_PAID.name(), trade.getOffer().getState());
EXPECTED_PROTOCOL_STATUS.setState(BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG)
.setPhase(FIAT_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);
}
@ -194,37 +113,10 @@ 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_FIAT_PAYMENT_INITIATED_MSG.name())
&& (t.getPhase().equals(PAYOUT_PUBLISHED.name()) || t.getPhase().equals(FIAT_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());
@ -234,7 +126,6 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
.setFiatReceived(true);
verifyExpectedProtocolStatus(trade);
logTrade(log, testInfo, "Bob's view after confirming fiat payment received", trade);
} catch (StatusRuntimeException e) {
fail(e);
}
@ -242,34 +133,20 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
@Test
@Order(4)
public void testAlicesKeepFunds(final TestInfo testInfo) {
public void testKeepFunds(final TestInfo testInfo) {
try {
genBtcBlocksThenWait(1, 1000);
genBtcBlocksThenWait(1, 1_000);
var trade = aliceClient.getTrade(tradeId);
logTrade(log, testInfo, "Alice's view before keeping funds", trade);
aliceClient.keepFunds(tradeId);
genBtcBlocksThenWait(1, 1000);
bobClient.keepFunds(tradeId);
genBtcBlocksThenWait(1, 1_000);
trade = aliceClient.getTrade(tradeId);
EXPECTED_PROTOCOL_STATUS.setState(BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG)
.setPhase(PAYOUT_PUBLISHED);
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));
logTrade(log, testInfo, "Alice's Maker/Buyer View (Done)", aliceClient.getTrade(tradeId));
logTrade(log, testInfo, "Bob's Taker/Seller View (Done)", bobClient.getTrade(tradeId));
logBalances(log, testInfo);
} catch (StatusRuntimeException e) {
fail(e);
}

View File

@ -37,12 +37,8 @@ package bisq.apitest.method.trade;
import bisq.core.payment.PaymentAccount;
import bisq.core.payment.payload.NationalBankAccountPayload;
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;
@ -53,13 +49,10 @@ import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.config.ApiTestConfig.BSQ;
import static bisq.cli.TableFormat.formatBalancesTbls;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.DEPOSIT_CONFIRMED;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.FIAT_SENT;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.PAYOUT_PUBLISHED;
import static bisq.core.trade.model.bisq_v1.Trade.State.*;
import static java.lang.String.format;
import static bisq.core.trade.model.bisq_v1.Trade.State.BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG;
import static bisq.core.trade.model.bisq_v1.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
import static org.junit.jupiter.api.Assertions.*;
import static protobuf.Offer.State.OFFER_FEE_PAID;
import static protobuf.OfferDirection.BUY;
@ -110,17 +103,15 @@ public class TakeBuyBTCOfferWithNationalBankAcctTest extends AbstractTradeTest {
// 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
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(), TRADE_FEE_CURRENCY_CODE);
assertNotNull(trade);
assertEquals(offerId, trade.getTradeId());
assertFalse(trade.getIsCurrencyForTakerFeeBtc());
// Cache the trade id for the other tests.
tradeId = trade.getTradeId();
var trade = takeAlicesOffer(offerId,
bobsPaymentAccount.getId(),
TRADE_FEE_CURRENCY_CODE,
false);
// Before generating a blk and confirming deposit tx, make sure there
// are no bank acct details in the either side's contract.
@ -148,36 +139,13 @@ public class TakeBuyBTCOfferWithNationalBankAcctTest extends AbstractTradeTest {
genBtcBlocksThenWait(1, 4000);
alicesOffers = aliceClient.getMyOffersSortedByDate(BUY.name(), BRL);
assertEquals(0, alicesOffers.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.getIsDepositConfirmed()) {
log.warn("Bob still waiting on trade {} tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
trade.getShortId(),
trade.getDepositTxId(),
i);
genBtcBlocksThenWait(1, 4000);
} else {
EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN)
.setPhase(DEPOSIT_CONFIRMED)
.setDepositPublished(true)
.setDepositConfirmed(true);
verifyExpectedProtocolStatus(trade);
logTrade(log, testInfo, "Bob's view after deposit is confirmed", trade, true);
break;
}
}
if (!trade.getIsDepositConfirmed()) {
fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, deposit tx was never confirmed.",
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);
}
@ -203,53 +171,14 @@ public class TakeBuyBTCOfferWithNationalBankAcctTest extends AbstractTradeTest {
public void testAlicesConfirmPaymentStarted(final TestInfo testInfo) {
try {
var trade = aliceClient.getTrade(tradeId);
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
t.getState().equals(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN.name())
&& t.getPhase().equals(DEPOSIT_CONFIRMED.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());
sleep(1000 * 10);
trade = aliceClient.getTrade(tradeId);
} 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.getIsFiatSent()) {
log.warn("Alice still waiting for trade {} BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG, attempt # {}",
trade.getShortId(),
i);
sleep(5000);
} else {
assertEquals(OFFER_FEE_PAID.name(), trade.getOffer().getState());
EXPECTED_PROTOCOL_STATUS.setState(BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG)
.setPhase(FIAT_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);
trade = aliceClient.getTrade(tradeId);
assertEquals(OFFER_FEE_PAID.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);
}
@ -259,35 +188,10 @@ public class TakeBuyBTCOfferWithNationalBankAcctTest extends AbstractTradeTest {
@Order(4)
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_FIAT_PAYMENT_INITIATED_MSG.name())
&& (t.getPhase().equals(PAYOUT_PUBLISHED.name()) || t.getPhase().equals(FIAT_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());
sleep(1000 * 10);
trade = bobClient.getTrade(tradeId);
} 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());
@ -297,7 +201,6 @@ public class TakeBuyBTCOfferWithNationalBankAcctTest extends AbstractTradeTest {
.setFiatReceived(true);
verifyExpectedProtocolStatus(trade);
logTrade(log, testInfo, "Bob's view after confirming fiat payment received", trade);
} catch (StatusRuntimeException e) {
fail(e);
}
@ -305,34 +208,22 @@ public class TakeBuyBTCOfferWithNationalBankAcctTest extends AbstractTradeTest {
@Test
@Order(5)
public void testAlicesKeepFunds(final TestInfo testInfo) {
public void testKeepFunds(final TestInfo testInfo) {
try {
genBtcBlocksThenWait(1, 1000);
genBtcBlocksThenWait(1, 1_000);
var trade = aliceClient.getTrade(tradeId);
logTrade(log, testInfo, "Alice's view before keeping funds", trade);
aliceClient.keepFunds(tradeId);
genBtcBlocksThenWait(1, 1000);
bobClient.keepFunds(tradeId);
genBtcBlocksThenWait(1, 1_000);
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));
logTrade(log, testInfo, "Alice's Maker/Buyer View (Done)", aliceClient.getTrade(tradeId));
logTrade(log, testInfo, "Bob's Taker/Seller View (Done)", bobClient.getTrade(tradeId));
logBalances(log, testInfo);
} catch (StatusRuntimeException e) {
fail(e);
}

View File

@ -17,12 +17,8 @@
package bisq.apitest.method.trade;
import bisq.proto.grpc.TradeInfo;
import io.grpc.StatusRuntimeException;
import java.util.function.Predicate;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeAll;
@ -35,21 +31,13 @@ import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.config.ApiTestConfig.BSQ;
import static bisq.apitest.config.ApiTestConfig.BTC;
import static bisq.cli.TableFormat.formatBalancesTbls;
import static bisq.cli.TableFormat.formatOfferTable;
import static bisq.cli.table.builder.TableType.OFFER_TBL;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.DEPOSIT_CONFIRMED;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.FIAT_SENT;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.PAYOUT_PUBLISHED;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.WITHDRAWN;
import static bisq.core.trade.model.bisq_v1.Trade.State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN;
import static bisq.core.trade.model.bisq_v1.Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG;
import static bisq.core.trade.model.bisq_v1.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
import static bisq.core.trade.model.bisq_v1.Trade.State.WITHDRAW_COMPLETED;
import static java.lang.String.format;
import static java.util.Collections.singletonList;
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.OfferDirection.BUY;
@ -57,6 +45,7 @@ import static protobuf.OfferDirection.BUY;
import bisq.apitest.method.offer.AbstractOfferTest;
import bisq.cli.table.builder.TableBuilder;
@Disabled
@Slf4j
@ -73,7 +62,6 @@ public class TakeSellBSQOfferTest extends AbstractTradeTest {
@BeforeAll
public static void setUp() {
AbstractOfferTest.setUp();
createBsqPaymentAccounts();
EXPECTED_PROTOCOL_STATUS.init();
}
@ -91,60 +79,27 @@ public class TakeSellBSQOfferTest extends AbstractTradeTest {
7_500_000L,
"0.000035", // FIXED PRICE IN BTC (satoshis) FOR 1 BSQ
getDefaultBuyerSecurityDepositAsPercent(),
alicesBsqAcct.getId(),
alicesLegacyBsqAcct.getId(),
TRADE_FEE_CURRENCY_CODE);
log.info("ALICE'S SELL BSQ (BUY BTC) OFFER:\n{}", formatOfferTable(singletonList(alicesOffer), BSQ));
genBtcBlocksThenWait(1, 4000);
log.debug("ALICE'S SELL BSQ (BUY BTC) OFFER:\n{}", new TableBuilder(OFFER_TBL, alicesOffer).build());
genBtcBlocksThenWait(1, 4_000);
var offerId = alicesOffer.getId();
assertTrue(alicesOffer.getIsCurrencyForMakerFeeBtc());
var alicesBsqOffers = aliceClient.getMyCryptoCurrencyOffers(btcTradeDirection, BSQ);
assertEquals(1, alicesBsqOffers.size());
var trade = takeAlicesOffer(offerId, bobsBsqAcct.getId(), TRADE_FEE_CURRENCY_CODE);
assertNotNull(trade);
assertEquals(offerId, trade.getTradeId());
assertTrue(trade.getIsCurrencyForTakerFeeBtc());
// Cache the trade id for the other tests.
tradeId = trade.getTradeId();
genBtcBlocksThenWait(1, 6000);
alicesBsqOffers = aliceClient.getMyBsqOffersSortedByDate();
var trade = takeAlicesOffer(offerId,
bobsLegacyBsqAcct.getId(),
TRADE_FEE_CURRENCY_CODE,
false);
sleep(2_500); // Allow available offer to be removed from offer book.
alicesBsqOffers = aliceClient.getMyCryptoCurrencyOffersSortedByDate(BSQ);
assertEquals(0, alicesBsqOffers.size());
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
trade = bobClient.getTrade(trade.getTradeId());
if (!trade.getIsDepositConfirmed()) {
log.warn("Bob still waiting on trade {} tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
trade.getShortId(),
trade.getDepositTxId(),
i);
genBtcBlocksThenWait(1, 4000);
continue;
} else {
EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN)
.setPhase(DEPOSIT_CONFIRMED)
.setDepositPublished(true)
.setDepositConfirmed(true);
verifyExpectedProtocolStatus(trade);
logTrade(log, testInfo, "Bob's view after taking offer and deposit confirmed", trade);
break;
}
}
genBtcBlocksThenWait(1, 2500);
if (!trade.getIsDepositConfirmed()) {
fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, deposit tx was never confirmed.",
trade.getShortId(),
trade.getState(),
trade.getPhase()));
}
logTrade(log, testInfo, "Alice's Maker/Seller View", aliceClient.getTrade(tradeId), true);
logTrade(log, testInfo, "Bob's Taker/Buyer View", bobClient.getTrade(tradeId), true);
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);
}
@ -155,60 +110,14 @@ public class TakeSellBSQOfferTest extends AbstractTradeTest {
public void testAlicesConfirmPaymentStarted(final TestInfo testInfo) {
try {
var trade = aliceClient.getTrade(tradeId);
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
t.getState().equals(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN.name())
&& t.getPhase().equals(DEPOSIT_CONFIRMED.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 send payment started msg yet.",
trade.getShortId(),
trade.getState(),
trade.getPhase());
sleep(10_000);
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 send payment started msg.",
trade.getShortId(),
trade.getState(),
trade.getPhase()));
}
waitForDepositConfirmation(log, testInfo, aliceClient, trade.getTradeId());
sendBsqPayment(log, aliceClient, trade);
genBtcBlocksThenWait(1, 2500);
genBtcBlocksThenWait(1, 2_500);
aliceClient.confirmPaymentStarted(trade.getTradeId());
sleep(6000);
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
trade = bobClient.getTrade(tradeId);
if (!trade.getIsFiatSent()) {
log.warn("Bob still waiting for trade {} SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG, attempt # {}",
trade.getShortId(),
i);
sleep(5000);
continue;
} else {
// Warning: trade.getOffer().getState() might be AVAILABLE, not OFFER_FEE_PAID.
EXPECTED_PROTOCOL_STATUS.setState(SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG)
.setPhase(FIAT_SENT)
.setFiatSent(true);
verifyExpectedProtocolStatus(trade);
logTrade(log, testInfo, "Alice's view after confirming fiat payment received", trade);
break;
}
}
logTrade(log, testInfo, "Alice's Maker/Seller View (Payment Sent)", aliceClient.getTrade(tradeId), true);
logTrade(log, testInfo, "Bob's Taker/Buyer View (Payment Sent)", bobClient.getTrade(tradeId), true);
sleep(6_000);
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);
}
@ -218,39 +127,13 @@ public class TakeSellBSQOfferTest extends AbstractTradeTest {
@Order(3)
public void testBobsConfirmPaymentReceived(final TestInfo testInfo) {
try {
waitForSellerSeesPaymentInitiatedMessage(log, testInfo, bobClient, tradeId);
sleep(2_000);
var trade = bobClient.getTrade(tradeId);
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
t.getState().equals(SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG.name())
&& (t.getPhase().equals(PAYOUT_PUBLISHED.name()) || t.getPhase().equals(FIAT_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());
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()));
}
sleep(2000);
verifyBsqPaymentHasBeenReceived(log, bobClient, trade);
bobClient.confirmPaymentReceived(trade.getTradeId());
sleep(3000);
sleep(3_000);
trade = bobClient.getTrade(tradeId);
// Warning: trade.getOffer().getState() might be AVAILABLE, not OFFER_FEE_PAID.
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
@ -258,11 +141,8 @@ public class TakeSellBSQOfferTest extends AbstractTradeTest {
.setPayoutPublished(true)
.setFiatReceived(true);
verifyExpectedProtocolStatus(trade);
logTrade(log, testInfo, "Alice's view after confirming fiat payment received", trade);
logTrade(log, testInfo, "Alice's Maker/Seller View (Payment Received)", aliceClient.getTrade(tradeId), true);
logTrade(log, testInfo, "Bob's Taker/Buyer View (Payment Received)", bobClient.getTrade(tradeId), true);
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);
}
@ -272,36 +152,23 @@ public class TakeSellBSQOfferTest extends AbstractTradeTest {
@Order(4)
public void testAlicesBtcWithdrawalToExternalAddress(final TestInfo testInfo) {
try {
genBtcBlocksThenWait(1, 1000);
genBtcBlocksThenWait(1, 1_000);
var trade = aliceClient.getTrade(tradeId);
logTrade(log, testInfo, "Alice's view before withdrawing BTC funds to external wallet", trade);
String toAddress = bitcoinCli.getNewBtcAddress();
aliceClient.withdrawFunds(tradeId, toAddress, WITHDRAWAL_TX_MEMO);
genBtcBlocksThenWait(1, 1000);
// Bob keeps funds.
bobClient.keepFunds(tradeId);
genBtcBlocksThenWait(1, 1_000);
trade = aliceClient.getTrade(tradeId);
EXPECTED_PROTOCOL_STATUS.setState(WITHDRAW_COMPLETED)
.setPhase(WITHDRAWN)
.setWithdrawn(true);
verifyExpectedProtocolStatus(trade);
logTrade(log, testInfo, "Alice's view after withdrawing funds to external wallet", trade);
logTrade(log, testInfo, "Alice's Maker/Seller View (Done)", aliceClient.getTrade(tradeId), true);
logTrade(log, testInfo, "Bob's Taker/Buyer 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));
logTrade(log, testInfo, "Alice's Maker/Seller View (Done)", aliceClient.getTrade(tradeId));
logTrade(log, testInfo, "Bob's Taker/Buyer View (Done)", bobClient.getTrade(tradeId));
logBalances(log, testInfo);
} 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,21 +31,17 @@ 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.apitest.config.ApiTestConfig.USD;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.DEPOSIT_CONFIRMED;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.FIAT_SENT;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.PAYOUT_PUBLISHED;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.WITHDRAWN;
import static bisq.core.trade.model.bisq_v1.Trade.State.*;
import static java.lang.String.format;
import static bisq.core.trade.model.bisq_v1.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
import static bisq.core.trade.model.bisq_v1.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_PAID;
import static protobuf.OfferDirection.SELL;
import static protobuf.OpenOffer.State.AVAILABLE;
@Disabled
@Slf4j
@ -69,7 +61,7 @@ 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,
@ -81,54 +73,26 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
assertTrue(alicesOffer.getIsCurrencyForMakerFeeBtc());
// 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(), TRADE_FEE_CURRENCY_CODE);
assertNotNull(trade);
assertEquals(offerId, trade.getTradeId());
assertTrue(trade.getIsCurrencyForTakerFeeBtc());
// 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(),
TRADE_FEE_CURRENCY_CODE,
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.getIsDepositConfirmed()) {
log.warn("Bob still waiting on trade {} tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
trade.getShortId(),
trade.getDepositTxId(),
i);
genBtcBlocksThenWait(1, 4000);
continue;
} else {
EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN)
.setPhase(DEPOSIT_CONFIRMED)
.setDepositPublished(true)
.setDepositConfirmed(true);
verifyExpectedProtocolStatus(trade);
logTrade(log, testInfo, "Bob's view after deposit is confirmed", trade, true);
break;
}
}
if (!trade.getIsDepositConfirmed()) {
fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, deposit tx was never confirmed.",
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);
}
@ -139,54 +103,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_CONFIRMED_IN_BLOCK_CHAIN.name()) && t.getPhase().equals(DEPOSIT_CONFIRMED.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.getIsFiatSent()) {
log.warn("Bob still waiting for trade {} BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_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_FIAT_PAYMENT_INITIATED_MSG)
.setPhase(FIAT_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);
}
@ -196,36 +116,11 @@ 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_FIAT_PAYMENT_INITIATED_MSG.name())
&& (t.getPhase().equals(PAYOUT_PUBLISHED.name()) || t.getPhase().equals(FIAT_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_PAID.name(), trade.getOffer().getState());
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
@ -243,34 +138,22 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
@Order(4)
public void testBobsBtcWithdrawalToExternalAddress(final TestInfo testInfo) {
try {
genBtcBlocksThenWait(1, 1000);
genBtcBlocksThenWait(1, 1_000);
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);
aliceClient.keepFunds(tradeId);
genBtcBlocksThenWait(1, 1_000);
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));
logTrade(log, testInfo, "Alice's Maker/Buyer View (Done)", aliceClient.getTrade(tradeId));
logTrade(log, testInfo, "Bob's Taker/Seller View (Done)", bobClient.getTrade(tradeId));
logBalances(log, testInfo);
} catch (StatusRuntimeException e) {
fail(e);
}

View File

@ -24,7 +24,7 @@ import static bisq.apitest.method.wallet.WalletTestUtil.ALICES_INITIAL_BSQ_BALAN
import static bisq.apitest.method.wallet.WalletTestUtil.BOBS_INITIAL_BSQ_BALANCES;
import static bisq.apitest.method.wallet.WalletTestUtil.bsqBalanceModel;
import static bisq.apitest.method.wallet.WalletTestUtil.verifyBsqBalances;
import static bisq.cli.TableFormat.formatBsqBalanceInfoTbl;
import static bisq.cli.table.builder.TableType.BSQ_BALANCE_TBL;
import static org.bitcoinj.core.NetworkParameters.ID_REGTEST;
import static org.bitcoinj.core.NetworkParameters.PAYMENT_PROTOCOL_ID_REGTEST;
import static org.junit.jupiter.api.Assertions.assertFalse;
@ -36,6 +36,7 @@ import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import bisq.apitest.config.BisqAppConfig;
import bisq.apitest.method.MethodTest;
import bisq.cli.GrpcClient;
import bisq.cli.table.builder.TableBuilder;
@Disabled
@Slf4j
@ -55,6 +56,7 @@ public class BsqWalletTest extends MethodTest {
bobdaemon);
}
@Test
@Order(1)
public void testGetUnusedBsqAddress() {
@ -73,13 +75,13 @@ public class BsqWalletTest extends MethodTest {
BsqBalanceInfo alicesBsqBalances = aliceClient.getBsqBalances();
log.debug("{} -> Alice's BSQ Initial Balances -> \n{}",
testName(testInfo),
formatBsqBalanceInfoTbl(alicesBsqBalances));
new TableBuilder(BSQ_BALANCE_TBL, alicesBsqBalances).build());
verifyBsqBalances(ALICES_INITIAL_BSQ_BALANCES, alicesBsqBalances);
BsqBalanceInfo bobsBsqBalances = bobClient.getBsqBalances();
log.debug("{} -> Bob's BSQ Initial Balances -> \n{}",
testName(testInfo),
formatBsqBalanceInfoTbl(bobsBsqBalances));
new TableBuilder(BSQ_BALANCE_TBL, bobsBsqBalances).build());
verifyBsqBalances(BOBS_INITIAL_BSQ_BALANCES, bobsBsqBalances);
}
@ -100,19 +102,19 @@ public class BsqWalletTest extends MethodTest {
alicedaemon);
verifyBsqBalances(bsqBalanceModel(150000000,
2500050,
0,
0,
0,
0),
2500050,
0,
0,
0,
0),
bobsBsqBalances);
verifyBsqBalances(bsqBalanceModel(97499950,
97499950,
97499950,
0,
0,
0),
97499950,
97499950,
0,
0,
0),
alicesBsqBalances);
}
@ -124,7 +126,7 @@ public class BsqWalletTest extends MethodTest {
genBtcBlocksThenWait(1, 4000);
BsqBalanceInfo alicesBsqBalances = aliceClient.getBalances().getBsq();
BsqBalanceInfo bobsBsqBalances = waitForBsqNewAvailableBalance(bobClient, 150000000);
BsqBalanceInfo bobsBsqBalances = waitForBsqNewAvailableConfirmedBalance(bobClient, 150000000);
log.debug("See Available Confirmed BSQ Balances...");
printBobAndAliceBsqBalances(testInfo,
@ -133,19 +135,19 @@ public class BsqWalletTest extends MethodTest {
alicedaemon);
verifyBsqBalances(bsqBalanceModel(152500050,
0,
0,
0,
0,
0),
0,
0,
0,
0,
0),
bobsBsqBalances);
verifyBsqBalances(bsqBalanceModel(97499950,
0,
0,
0,
0,
0),
0,
0,
0,
0,
0),
alicesBsqBalances);
}
@ -166,8 +168,8 @@ public class BsqWalletTest extends MethodTest {
return bsqBalance;
}
private BsqBalanceInfo waitForBsqNewAvailableBalance(GrpcClient grpcClient,
long staleBalance) {
private BsqBalanceInfo waitForBsqNewAvailableConfirmedBalance(GrpcClient grpcClient,
long staleBalance) {
BsqBalanceInfo bsqBalance = grpcClient.getBsqBalances();
for (int numRequests = 1;
numRequests <= 15 && bsqBalance.getAvailableConfirmedBalance() == staleBalance;
@ -187,12 +189,12 @@ public class BsqWalletTest extends MethodTest {
testName(testInfo),
senderApp.equals(bobdaemon) ? "Sending" : "Receiving",
SEND_BSQ_AMOUNT,
formatBsqBalanceInfoTbl(bobsBsqBalances));
new TableBuilder(BSQ_BALANCE_TBL, bobsBsqBalances).build());
log.debug("{} -> Alice's Balances After {} {} BSQ-> \n{}",
testName(testInfo),
senderApp.equals(alicedaemon) ? "Sending" : "Receiving",
SEND_BSQ_AMOUNT,
formatBsqBalanceInfoTbl(alicesBsqBalances));
new TableBuilder(BSQ_BALANCE_TBL, alicesBsqBalances).build());
}
}

View File

@ -19,9 +19,8 @@ import static bisq.apitest.config.BisqAppConfig.bobdaemon;
import static bisq.apitest.config.BisqAppConfig.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

@ -43,7 +43,6 @@ public class LongRunningBsqSwapTest extends AbstractOfferTest {
@BeforeAll
public static void setUp() {
AbstractOfferTest.setUp();
createBsqSwapBsqPaymentAccounts();
}
@Test

View File

@ -71,7 +71,7 @@ public class LongRunningTradesTest extends AbstractTradeTest {
test.testTakeAlicesBuyOffer(testInfo);
test.testAlicesConfirmPaymentStarted(testInfo);
test.testBobsConfirmPaymentReceived(testInfo);
test.testAlicesKeepFunds(testInfo);
test.testKeepFunds(testInfo);
}
public void testTakeSellBTCOffer(final TestInfo testInfo) {

View File

@ -80,7 +80,6 @@ public class OfferTest extends AbstractOfferTest {
@Order(5)
public void testCreateBSQOffers() {
CreateBSQOffersTest test = new CreateBSQOffersTest();
CreateBSQOffersTest.createBsqPaymentAccounts();
test.testCreateBuy1BTCFor20KBSQOffer();
test.testCreateSell1BTCFor20KBSQOffer();
test.testCreateBuyBTCWith1To2KBSQOffer();
@ -93,7 +92,6 @@ public class OfferTest extends AbstractOfferTest {
@Order(6)
public void testCreateBSQSwapOffers() {
BsqSwapOfferTest test = new BsqSwapOfferTest();
BsqSwapOfferTest.createBsqSwapBsqPaymentAccounts();
test.testAliceCreateBsqSwapBuyOffer1();
test.testAliceCreateBsqSwapBuyOffer2();
test.testAliceCreateBsqSwapBuyOffer3();

View File

@ -53,7 +53,7 @@ public class TradeTest extends AbstractTradeTest {
test.testTakeAlicesBuyOffer(testInfo);
test.testAlicesConfirmPaymentStarted(testInfo);
test.testBobsConfirmPaymentReceived(testInfo);
test.testAlicesKeepFunds(testInfo);
test.testKeepFunds(testInfo);
}
@Test
@ -70,11 +70,10 @@ public class TradeTest extends AbstractTradeTest {
@Order(3)
public void testTakeBuyBSQOffer(final TestInfo testInfo) {
TakeBuyBSQOfferTest test = new TakeBuyBSQOfferTest();
TakeBuyBSQOfferTest.createBsqPaymentAccounts();
test.testTakeAlicesSellBTCForBSQOffer(testInfo);
test.testBobsConfirmPaymentStarted(testInfo);
test.testAlicesConfirmPaymentReceived(testInfo);
test.testBobsKeepFunds(testInfo);
test.testKeepFunds(testInfo);
}
@Test
@ -85,14 +84,13 @@ public class TradeTest extends AbstractTradeTest {
test.testBankAcctDetailsIncludedInContracts(testInfo);
test.testAlicesConfirmPaymentStarted(testInfo);
test.testBobsConfirmPaymentReceived(testInfo);
test.testAlicesKeepFunds(testInfo);
test.testKeepFunds(testInfo);
}
@Test
@Order(5)
public void testTakeSellBSQOffer(final TestInfo testInfo) {
TakeSellBSQOfferTest test = new TakeSellBSQOfferTest();
TakeSellBSQOfferTest.createBsqPaymentAccounts();
test.testTakeAlicesBuyBTCForBSQOffer(testInfo);
test.testAlicesConfirmPaymentStarted(testInfo);
test.testBobsConfirmPaymentReceived(testInfo);
@ -103,7 +101,6 @@ public class TradeTest extends AbstractTradeTest {
@Order(6)
public void testBsqSwapTradeTest(final TestInfo testInfo) {
BsqSwapTradeTest test = new BsqSwapTradeTest();
test.createBsqSwapBsqPaymentAccounts();
test.testAliceCreateBsqSwapBuyOffer();
test.testBobTakesBsqSwapOffer();
}

View File

@ -16,7 +16,7 @@ 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 bisq.cli.OfferFormat.formatOfferTable;
import static java.util.Collections.singletonList;

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.OfferFormat.formatOfferTable;
import static bisq.core.payment.payload.PaymentMethod.F2F_ID;

View File

@ -34,6 +34,7 @@ configure(subprojects) {
bcVersion = '1.63'
bitcoinjVersion = '42bbae9'
codecVersion = '1.13'
cowwocVersion = '1.2'
easybindVersion = '1.0.3'
easyVersion = '4.0.1'
findbugsVersion = '3.0.2'
@ -395,6 +396,7 @@ configure(project(':cli')) {
testAnnotationProcessor "org.projectlombok:lombok:$lombokVersion"
testCompileOnly "org.projectlombok:lombok:$lombokVersion"
testRuntime "javax.annotation:javax.annotation-api:$javaxAnnotationVersion"
testImplementation "org.bitbucket.cowwoc:diff-match-patch:$cowwocVersion"
}
test {

View File

@ -34,6 +34,8 @@ import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
@ -41,13 +43,13 @@ import lombok.extern.slf4j.Slf4j;
import static bisq.cli.CurrencyFormat.*;
import static bisq.cli.Method.*;
import static bisq.cli.TableFormat.*;
import static bisq.cli.opts.OptLabel.*;
import static bisq.cli.table.builder.TableType.*;
import static java.lang.String.format;
import static java.lang.System.err;
import static java.lang.System.exit;
import static java.lang.System.out;
import static java.util.Collections.singletonList;
import static java.math.BigDecimal.ZERO;
@ -76,6 +78,7 @@ import bisq.cli.opts.TakeOfferOptionParser;
import bisq.cli.opts.UnlockWalletOptionParser;
import bisq.cli.opts.VerifyBsqSentToAddressOptionParser;
import bisq.cli.opts.WithdrawFundsOptionParser;
import bisq.cli.table.builder.TableBuilder;
/**
* A command-line client for the Bisq gRPC API.
@ -167,15 +170,19 @@ public class CliMain {
var balances = client.getBalances(currencyCode);
switch (currencyCode.toUpperCase()) {
case "BSQ":
out.println(formatBsqBalanceInfoTbl(balances.getBsq()));
new TableBuilder(BSQ_BALANCE_TBL, balances.getBsq()).build().print(out);
break;
case "BTC":
out.println(formatBtcBalanceInfoTbl(balances.getBtc()));
new TableBuilder(BTC_BALANCE_TBL, balances.getBtc()).build().print(out);
break;
case "":
default:
out.println(formatBalancesTbls(balances));
default: {
out.println("BTC");
new TableBuilder(BTC_BALANCE_TBL, balances.getBtc()).build().print(out);
out.println("BSQ");
new TableBuilder(BSQ_BALANCE_TBL, balances.getBsq()).build().print(out);
break;
}
}
return;
}
@ -187,7 +194,7 @@ public class CliMain {
}
var address = opts.getAddress();
var addressBalance = client.getAddressBalance(address);
out.println(formatAddressBalanceTbl(singletonList(addressBalance)));
new TableBuilder(ADDRESS_BALANCE_TBL, addressBalance).build().print(out);
return;
}
case getbtcprice: {
@ -207,7 +214,7 @@ public class CliMain {
return;
}
var fundingAddresses = client.getFundingAddresses();
out.println(formatAddressBalanceTbl(fundingAddresses));
new TableBuilder(ADDRESS_BALANCE_TBL, fundingAddresses).build().print(out);
return;
}
case getunusedbsqaddress: {
@ -316,7 +323,7 @@ public class CliMain {
}
var txId = opts.getTxId();
var tx = client.getTransaction(txId);
out.println(TransactionFormat.format(tx));
new TableBuilder(TRANSACTION_TBL, tx).build().print(out);
return;
}
case createoffer: {
@ -347,7 +354,7 @@ public class CliMain {
paymentAcctId,
makerFeeCurrencyCode,
triggerPrice);
out.println(formatOfferTable(singletonList(offer), currencyCode));
new TableBuilder(OFFER_TBL, offer).build().print(out);
return;
}
case editoffer: {
@ -360,7 +367,7 @@ public class CliMain {
var fixedPrice = opts.getFixedPrice();
var isUsingMktPriceMargin = opts.isUsingMktPriceMargin();
var marketPriceMargin = opts.getMktPriceMarginAsBigDecimal();
var triggerPrice = toInternalFiatPrice(opts.getTriggerPriceAsBigDecimal());
var triggerPrice = toInternalTriggerPrice(client, offerId, opts.getTriggerPriceAsBigDecimal());
var enable = opts.getEnableAsSignedInt();
var editOfferType = opts.getOfferEditType();
client.editOffer(offerId,
@ -392,7 +399,7 @@ public class CliMain {
}
var offerId = opts.getOfferId();
var offer = client.getOffer(offerId);
out.println(formatOfferTable(singletonList(offer), offer.getCounterCurrencyCode()));
new TableBuilder(OFFER_TBL, offer).build().print(out);
return;
}
case getmyoffer: {
@ -403,7 +410,7 @@ public class CliMain {
}
var offerId = opts.getOfferId();
var offer = client.getMyOffer(offerId);
out.println(formatOfferTable(singletonList(offer), offer.getCounterCurrencyCode()));
new TableBuilder(OFFER_TBL, offer).build().print(out);
return;
}
case getoffers: {
@ -418,7 +425,7 @@ public class CliMain {
if (offers.isEmpty())
out.printf("no %s %s offers found%n", direction, currencyCode);
else
out.println(formatOfferTable(offers, currencyCode));
new TableBuilder(OFFER_TBL, offers).build().print(out);
return;
}
@ -434,7 +441,7 @@ public class CliMain {
if (offers.isEmpty())
out.printf("no %s %s offers found%n", direction, currencyCode);
else
out.println(formatOfferTable(offers, currencyCode));
new TableBuilder(OFFER_TBL, offers).build().print(out);
return;
}
@ -464,7 +471,7 @@ public class CliMain {
if (showContract)
out.println(trade.getContractAsJson());
else
out.println(TradeFormat.format(trade));
new TableBuilder(TRADE_DETAIL_TBL, trade).build().print(out);
return;
}
@ -556,11 +563,12 @@ public class CliMain {
}
var paymentAccount = client.createPaymentAccount(jsonString);
out.println("payment account saved");
out.println(formatPaymentAcctTbl(singletonList(paymentAccount)));
new TableBuilder(PAYMENT_ACCOUNT_TBL, paymentAccount).build().print(out);
return;
}
case createcryptopaymentacct: {
var opts = new CreateCryptoCurrencyPaymentAcctOptionParser(args).parse();
var opts =
new CreateCryptoCurrencyPaymentAcctOptionParser(args).parse();
if (opts.isForHelp()) {
out.println(client.getMethodHelp(method));
return;
@ -574,7 +582,7 @@ public class CliMain {
address,
isTradeInstant);
out.println("payment account saved");
out.println(formatPaymentAcctTbl(singletonList(paymentAccount)));
new TableBuilder(PAYMENT_ACCOUNT_TBL, paymentAccount).build().print(out);
return;
}
case getpaymentaccts: {
@ -584,7 +592,7 @@ public class CliMain {
}
var paymentAccounts = client.getPaymentAccounts();
if (paymentAccounts.size() > 0)
out.println(formatPaymentAcctTbl(paymentAccounts));
new TableBuilder(PAYMENT_ACCOUNT_TBL, paymentAccounts).build().print(out);
else
out.println("no payment accounts are saved");
@ -708,6 +716,25 @@ public class CliMain {
}
}
private static long toInternalTriggerPrice(GrpcClient client,
String offerId,
BigDecimal unscaledTriggerPrice) {
if (unscaledTriggerPrice.compareTo(ZERO) >= 0) {
// Unfortunately, the EditOffer proto triggerPrice field was declared as
// a long instead of a string, so the CLI has to look at the offer to know
// how to scale the trigger-price (for a fiat or altcoin offer) param sent
// to the server in its 'editoffer' request. That means a preliminary round
// trip to the server: a 'getmyoffer' request.
var offer = client.getMyOffer(offerId);
if (offer.getCounterCurrencyCode().equals("BTC"))
return toInternalCryptoCurrencyPrice(unscaledTriggerPrice);
else
return toInternalFiatPrice(unscaledTriggerPrice);
} else {
return 0L;
}
}
private static File saveFileToDisk(String prefix,
@SuppressWarnings("SameParameterValue") String suffix,
String text) {

View File

@ -20,6 +20,7 @@ package bisq.cli;
import static com.google.common.base.Strings.padEnd;
import static com.google.common.base.Strings.padStart;
@Deprecated
class ColumnHeaderConstants {
// For inserting 2 spaces between column headers.

View File

@ -20,15 +20,16 @@ package bisq.cli;
import java.util.ArrayList;
import java.util.List;
class CryptoCurrencyUtil {
public class CryptoCurrencyUtil {
public static boolean isSupportedCryptoCurrency(String currencyCode) {
public static boolean apiDoesSupportCryptoCurrency(String currencyCode) {
return getSupportedCryptoCurrencies().contains(currencyCode.toUpperCase());
}
public static List<String> getSupportedCryptoCurrencies() {
final List<String> result = new ArrayList<>();
result.add("BSQ");
// result.add("XMR"); // TODO Uncomment when XMR support is added.
result.sort(String::compareTo);
return result;
}

View File

@ -39,14 +39,15 @@ public class CurrencyFormat {
// Use the US locale for all DecimalFormat objects.
private static final DecimalFormatSymbols DECIMAL_FORMAT_SYMBOLS = DecimalFormatSymbols.getInstance(Locale.US);
// Formats numbers in US locale, human friendly style.
// Format numbers in US locale for CLI console.
private static final NumberFormat FRIENDLY_NUMBER_FORMAT = NumberFormat.getInstance(Locale.US);
// Formats numbers for internal use, i.e., grpc request parameters.
private static final DecimalFormat INTERNAL_FIAT_DECIMAL_FORMAT = new DecimalFormat("##############0.0000");
static final BigDecimal SATOSHI_DIVISOR = new BigDecimal(100_000_000);
static final DecimalFormat BTC_FORMAT = new DecimalFormat("###,##0.00000000", DECIMAL_FORMAT_SYMBOLS);
static final DecimalFormat SATOSHI_FORMAT = new DecimalFormat("###,##0.00000000", DECIMAL_FORMAT_SYMBOLS);
static final DecimalFormat BTC_FORMAT = new DecimalFormat("###,##0.########", DECIMAL_FORMAT_SYMBOLS);
static final DecimalFormat BTC_TX_FEE_FORMAT = new DecimalFormat("###,###,##0", DECIMAL_FORMAT_SYMBOLS);
static final BigDecimal BSQ_SATOSHI_DIVISOR = new BigDecimal(100);
@ -57,6 +58,11 @@ public class CurrencyFormat {
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
public static String formatSatoshis(long sats) {
return SATOSHI_FORMAT.format(BigDecimal.valueOf(sats).divide(SATOSHI_DIVISOR));
}
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
public static String formatBtc(long sats) {
return BTC_FORMAT.format(BigDecimal.valueOf(sats).divide(SATOSHI_DIVISOR));
}
@ -84,22 +90,25 @@ public class CurrencyFormat {
formatFeeSatoshis(txFeeRateInfo.getMinFeeServiceRate()));
}
@Deprecated
public static String formatAmountRange(long minAmount, long amount) {
return minAmount != amount
? formatSatoshis(minAmount) + " - " + formatSatoshis(amount)
: formatSatoshis(amount);
}
@Deprecated
public static String formatVolumeRange(long minVolume, long volume) {
return minVolume != volume
? formatOfferVolume(minVolume) + " - " + formatOfferVolume(volume)
: formatOfferVolume(volume);
? formatFiatVolume(minVolume) + " - " + formatFiatVolume(volume)
: formatFiatVolume(volume);
}
@Deprecated
public static String formatCryptoCurrencyVolumeRange(long minVolume, long volume) {
return minVolume != volume
? formatCryptoCurrencyOfferVolume(minVolume) + " - " + formatCryptoCurrencyOfferVolume(volume)
: formatCryptoCurrencyOfferVolume(volume);
? formatCryptoCurrencyVolume(minVolume) + " - " + formatCryptoCurrencyVolume(volume)
: formatCryptoCurrencyVolume(volume);
}
public static String formatInternalFiatPrice(BigDecimal price) {
@ -128,22 +137,31 @@ public class CurrencyFormat {
return FRIENDLY_NUMBER_FORMAT.format((double) price / SATOSHI_DIVISOR.doubleValue());
}
public static String formatOfferVolume(long volume) {
public static String formatFiatVolume(long volume) {
FRIENDLY_NUMBER_FORMAT.setMinimumFractionDigits(0);
FRIENDLY_NUMBER_FORMAT.setMaximumFractionDigits(0);
FRIENDLY_NUMBER_FORMAT.setRoundingMode(HALF_UP);
return FRIENDLY_NUMBER_FORMAT.format((double) volume / 10_000);
}
public static String formatCryptoCurrencyOfferVolume(long volume) {
FRIENDLY_NUMBER_FORMAT.setMinimumFractionDigits(2);
FRIENDLY_NUMBER_FORMAT.setMaximumFractionDigits(2);
public static String formatCryptoCurrencyVolume(long volume) {
int defaultPrecision = 2;
return formatCryptoCurrencyVolume(volume, defaultPrecision);
}
public static String formatCryptoCurrencyVolume(long volume, int precision) {
FRIENDLY_NUMBER_FORMAT.setMinimumFractionDigits(precision);
FRIENDLY_NUMBER_FORMAT.setMaximumFractionDigits(precision);
FRIENDLY_NUMBER_FORMAT.setRoundingMode(HALF_UP);
return FRIENDLY_NUMBER_FORMAT.format((double) volume / SATOSHI_DIVISOR.doubleValue());
}
public static long toInternalFiatPrice(BigDecimal humanFriendlyFiatPrice) {
return humanFriendlyFiatPrice.multiply(new BigDecimal(10_000)).longValue();
public static long toInternalFiatPrice(BigDecimal fiatPrice) {
return fiatPrice.multiply(new BigDecimal(10_000)).longValue();
}
public static long toInternalCryptoCurrencyPrice(BigDecimal altcoinPrice) {
return altcoinPrice.multiply(new BigDecimal(100_000_000)).longValue();
}
public static long toSatoshis(String btc) {

View File

@ -27,6 +27,7 @@ import static java.lang.String.format;
import static protobuf.OfferDirection.BUY;
import static protobuf.OfferDirection.SELL;
@Deprecated
class DirectionFormat {
static int getLongestDirectionColWidth(List<OfferInfo> offers) {

View File

@ -327,6 +327,10 @@ public final class GrpcClient {
return offersServiceRequest.getMyOffersSortedByDate(currencyCode);
}
public List<OfferInfo> getMyCryptoCurrencyOffersSortedByDate(String currencyCode) {
return offersServiceRequest.getMyCryptoCurrencyOffersSortedByDate(currencyCode);
}
public List<OfferInfo> getMyBsqOffersSortedByDate() {
return offersServiceRequest.getMyBsqOffersSortedByDate();
}
@ -414,6 +418,10 @@ public final class GrpcClient {
return paymentAccountsServiceRequest.getPaymentAccounts();
}
public PaymentAccount getPaymentAccount(String accountName) {
return paymentAccountsServiceRequest.getPaymentAccount(accountName);
}
public PaymentAccount createCryptoCurrencyPaymentAccount(String accountName,
String currencyCode,
String address,

View File

@ -0,0 +1,309 @@
package bisq.cli;
import bisq.proto.grpc.OfferInfo;
import com.google.common.annotations.VisibleForTesting;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static bisq.cli.ColumnHeaderConstants.*;
import static bisq.cli.CurrencyFormat.*;
import static bisq.cli.DirectionFormat.directionFormat;
import static bisq.cli.DirectionFormat.getLongestDirectionColWidth;
import static bisq.cli.TableFormat.formatTimestamp;
import static bisq.cli.TableFormat.getLongestColumnSize;
import static com.google.common.base.Strings.padEnd;
import static com.google.common.base.Strings.padStart;
import static java.lang.String.format;
@Deprecated
@VisibleForTesting
public class OfferFormat {
public static String formatOfferTable(List<OfferInfo> offers, String currencyCode) {
if (offers == null || offers.isEmpty())
throw new IllegalArgumentException(format("%s offer list is empty", currencyCode.toLowerCase()));
String baseCurrencyCode = offers.get(0).getBaseCurrencyCode();
boolean isMyOffer = offers.get(0).getIsMyOffer();
return baseCurrencyCode.equalsIgnoreCase("BTC")
? formatFiatOfferTable(offers, currencyCode, isMyOffer)
: formatCryptoCurrencyOfferTable(offers, baseCurrencyCode, isMyOffer);
}
private static String formatFiatOfferTable(List<OfferInfo> offers,
String fiatCurrencyCode,
boolean isMyOffer) {
// Some column values might be longer than header, so we need to calculate them.
int amountColWith = getLongestAmountColWidth(offers);
int volumeColWidth = getLongestVolumeColWidth(offers);
int paymentMethodColWidth = getLongestPaymentMethodColWidth(offers);
// "Enabled" and "Trigger Price" columns are displayed for my offers only.
String enabledHeaderFormat = isMyOffer ?
COL_HEADER_ENABLED + COL_HEADER_DELIMITER
: "";
String triggerPriceHeaderFormat = isMyOffer ?
// COL_HEADER_TRIGGER_PRICE includes %s -> fiatCurrencyCode
COL_HEADER_TRIGGER_PRICE + COL_HEADER_DELIMITER
: "";
String headersFormat = enabledHeaderFormat
+ COL_HEADER_DIRECTION + COL_HEADER_DELIMITER
// COL_HEADER_PRICE includes %s -> fiatCurrencyCode
+ COL_HEADER_PRICE + COL_HEADER_DELIMITER
+ padStart(COL_HEADER_AMOUNT, amountColWith, ' ') + COL_HEADER_DELIMITER
// COL_HEADER_VOLUME includes %s -> fiatCurrencyCode
+ padStart(COL_HEADER_VOLUME, volumeColWidth, ' ') + COL_HEADER_DELIMITER
+ triggerPriceHeaderFormat
+ padEnd(COL_HEADER_PAYMENT_METHOD, paymentMethodColWidth, ' ') + COL_HEADER_DELIMITER
+ COL_HEADER_CREATION_DATE + COL_HEADER_DELIMITER
+ COL_HEADER_UUID.trim() + "%n";
String headerLine = format(headersFormat,
fiatCurrencyCode.toUpperCase(),
fiatCurrencyCode.toUpperCase(),
// COL_HEADER_TRIGGER_PRICE includes %s -> fiatCurrencyCode
isMyOffer ? fiatCurrencyCode.toUpperCase() : "");
String colDataFormat = getFiatOfferColDataFormat(isMyOffer,
amountColWith,
volumeColWidth,
paymentMethodColWidth);
return formattedFiatOfferTable(offers, isMyOffer, headerLine, colDataFormat);
}
private static String formattedFiatOfferTable(List<OfferInfo> offers,
boolean isMyOffer,
String headerLine,
String colDataFormat) {
if (isMyOffer) {
return headerLine
+ offers.stream()
.map(o -> format(colDataFormat,
formatEnabled(o),
o.getDirection(),
formatPrice(o.getPrice()),
formatAmountRange(o.getMinAmount(), o.getAmount()),
formatVolumeRange(o.getMinVolume(), o.getVolume()),
o.getTriggerPrice() == 0 ? "" : formatPrice(o.getTriggerPrice()),
o.getPaymentMethodShortName(),
formatTimestamp(o.getDate()),
o.getId()))
.collect(Collectors.joining("\n"));
} else {
return headerLine
+ offers.stream()
.map(o -> format(colDataFormat,
o.getDirection(),
formatPrice(o.getPrice()),
formatAmountRange(o.getMinAmount(), o.getAmount()),
formatVolumeRange(o.getMinVolume(), o.getVolume()),
o.getPaymentMethodShortName(),
formatTimestamp(o.getDate()),
o.getId()))
.collect(Collectors.joining("\n"));
}
}
private static String getFiatOfferColDataFormat(boolean isMyOffer,
int amountColWith,
int volumeColWidth,
int paymentMethodColWidth) {
if (isMyOffer) {
return "%-" + (COL_HEADER_ENABLED.length() + COL_HEADER_DELIMITER.length()) + "s"
+ "%-" + (COL_HEADER_DIRECTION.length() + COL_HEADER_DELIMITER.length()) + "s"
+ "%" + (COL_HEADER_PRICE.length() - 1) + "s"
+ " %" + amountColWith + "s"
+ " %" + (volumeColWidth - 1) + "s"
+ " %" + (COL_HEADER_TRIGGER_PRICE.length() - 1) + "s"
+ " %-" + paymentMethodColWidth + "s"
+ " %-" + (COL_HEADER_CREATION_DATE.length()) + "s"
+ " %-" + COL_HEADER_UUID.length() + "s";
} else {
return "%-" + (COL_HEADER_DIRECTION.length() + COL_HEADER_DELIMITER.length()) + "s"
+ "%" + (COL_HEADER_PRICE.length() - 1) + "s"
+ " %" + amountColWith + "s"
+ " %" + (volumeColWidth - 1) + "s"
+ " %-" + paymentMethodColWidth + "s"
+ " %-" + (COL_HEADER_CREATION_DATE.length()) + "s"
+ " %-" + COL_HEADER_UUID.length() + "s";
}
}
private static String formatCryptoCurrencyOfferTable(List<OfferInfo> offers,
String cryptoCurrencyCode,
boolean isMyOffer) {
// Some column values might be longer than header, so we need to calculate them.
int directionColWidth = getLongestDirectionColWidth(offers);
int amountColWith = getLongestAmountColWidth(offers);
int volumeColWidth = getLongestCryptoCurrencyVolumeColWidth(offers);
int paymentMethodColWidth = getLongestPaymentMethodColWidth(offers);
// "Enabled" column is displayed for my offers only.
String enabledHeaderFormat = isMyOffer ?
COL_HEADER_ENABLED + COL_HEADER_DELIMITER
: "";
Supplier<Boolean> shouldShowTriggerPrice = () -> isMyOffer && !cryptoCurrencyCode.equalsIgnoreCase("BSQ");
String triggerPriceHeaderFormat = shouldShowTriggerPrice.get()
? COL_HEADER_TRIGGER_PRICE + COL_HEADER_DELIMITER
: "";
// TODO use memoize function to avoid duplicate the formatting done above?
String headersFormat = enabledHeaderFormat
+ padEnd(COL_HEADER_DIRECTION, directionColWidth, ' ') + COL_HEADER_DELIMITER
+ COL_HEADER_PRICE_OF_ALTCOIN + COL_HEADER_DELIMITER // includes %s -> cryptoCurrencyCode
+ padStart(COL_HEADER_AMOUNT, amountColWith, ' ') + COL_HEADER_DELIMITER
// COL_HEADER_VOLUME includes %s -> cryptoCurrencyCode
+ padStart(COL_HEADER_VOLUME, volumeColWidth, ' ') + COL_HEADER_DELIMITER
+ triggerPriceHeaderFormat // COL_HEADER_TRIGGER_PRICE includes %s -> cryptoCurrencyCode
+ padEnd(COL_HEADER_PAYMENT_METHOD, paymentMethodColWidth, ' ') + COL_HEADER_DELIMITER
+ COL_HEADER_CREATION_DATE + COL_HEADER_DELIMITER
+ COL_HEADER_UUID.trim() + "%n";
String headerLine = format(headersFormat,
cryptoCurrencyCode.toUpperCase(),
cryptoCurrencyCode.toUpperCase(),
shouldShowTriggerPrice.get() ? cryptoCurrencyCode.toUpperCase() : "");
String colDataFormat = getCryptoCurrencyOfferColDataFormat(isMyOffer,
shouldShowTriggerPrice.get(),
directionColWidth,
amountColWith,
volumeColWidth,
paymentMethodColWidth);
if (isMyOffer) {
if (shouldShowTriggerPrice.get()) {
// Is my non-BSQ altcoin offer. Show ENABLED and TRIGGER_PRICE data.
return headerLine
+ offers.stream()
.map(o -> format(colDataFormat,
formatEnabled(o),
directionFormat.apply(o),
formatCryptoCurrencyPrice(o.getPrice()),
formatAmountRange(o.getMinAmount(), o.getAmount()),
formatCryptoCurrencyVolumeRange(o.getMinVolume(), o.getVolume()),
o.getTriggerPrice() == 0 ? "" : formatCryptoCurrencyPrice(o.getTriggerPrice()),
o.getPaymentMethodShortName(),
formatTimestamp(o.getDate()),
o.getId()))
.collect(Collectors.joining("\n"));
} else {
// Is my BSQ altcoin offer. Show ENABLED, but not TRIGGER_PRICE data.
return headerLine
+ offers.stream()
.map(o -> format(colDataFormat,
formatEnabled(o),
directionFormat.apply(o),
formatCryptoCurrencyPrice(o.getPrice()),
formatAmountRange(o.getMinAmount(), o.getAmount()),
formatCryptoCurrencyVolumeRange(o.getMinVolume(), o.getVolume()),
o.getPaymentMethodShortName(),
formatTimestamp(o.getDate()),
o.getId()))
.collect(Collectors.joining("\n"));
}
} else {
// Not my offer. Do not show ENABLED and TRIGGER_PRICE cols.
return headerLine
+ offers.stream()
.map(o -> format(colDataFormat,
directionFormat.apply(o),
formatCryptoCurrencyPrice(o.getPrice()),
formatAmountRange(o.getMinAmount(), o.getAmount()),
formatCryptoCurrencyVolumeRange(o.getMinVolume(), o.getVolume()),
o.getPaymentMethodShortName(),
formatTimestamp(o.getDate()),
o.getId()))
.collect(Collectors.joining("\n"));
}
}
private static String getCryptoCurrencyOfferColDataFormat(boolean isMyOffer,
boolean shouldShowTriggerPrice,
int directionColWidth,
int amountColWith,
int volumeColWidth,
int paymentMethodColWidth) {
if (isMyOffer) {
return getMyCryptoOfferColDataFormat(shouldShowTriggerPrice,
directionColWidth,
amountColWith,
volumeColWidth,
paymentMethodColWidth);
} else {
// Not my offer. Do not show ENABLED and TRIGGER_PRICE cols.
return "%-" + directionColWidth + "s"
+ "%" + (COL_HEADER_PRICE_OF_ALTCOIN.length() + 1) + "s"
+ " %" + amountColWith + "s"
+ " %" + (volumeColWidth - 1) + "s"
+ " %-" + paymentMethodColWidth + "s"
+ " %-" + (COL_HEADER_CREATION_DATE.length()) + "s"
+ " %-" + COL_HEADER_UUID.length() + "s";
}
}
private static String getMyCryptoOfferColDataFormat(boolean shouldShowTriggerPrice,
int directionColWidth,
int amountColWith,
int volumeColWidth,
int paymentMethodColWidth) {
if (shouldShowTriggerPrice) {
// Is my non-BSQ altcoin offer. Show ENABLED and TRIGGER_PRICE cols.
return "%-" + (COL_HEADER_ENABLED.length() + COL_HEADER_DELIMITER.length()) + "s"
+ "%-" + directionColWidth + "s"
+ "%" + (COL_HEADER_PRICE_OF_ALTCOIN.length() + 1) + "s"
+ " %" + amountColWith + "s"
+ " %" + (volumeColWidth - 1) + "s"
+ " %" + (COL_HEADER_TRIGGER_PRICE.length() - 1) + "s"
+ " %-" + paymentMethodColWidth + "s"
+ " %-" + (COL_HEADER_CREATION_DATE.length()) + "s"
+ " %-" + COL_HEADER_UUID.length() + "s";
} else {
// Is my BSQ altcoin offer. Show ENABLED, but not TRIGGER_PRICE col.
return "%-" + (COL_HEADER_ENABLED.length() + COL_HEADER_DELIMITER.length()) + "s"
+ "%-" + directionColWidth + "s"
+ "%" + (COL_HEADER_PRICE_OF_ALTCOIN.length() + 1) + "s"
+ " %" + amountColWith + "s"
+ " %" + (volumeColWidth - 1) + "s"
+ " %-" + paymentMethodColWidth + "s"
+ " %-" + (COL_HEADER_CREATION_DATE.length()) + "s"
+ " %-" + COL_HEADER_UUID.length() + "s";
}
}
private static String formatEnabled(OfferInfo offerInfo) {
if (offerInfo.getIsMyOffer() && offerInfo.getIsMyPendingOffer())
return "PENDING";
else
return offerInfo.getIsActivated() ? "YES" : "NO";
}
private static int getLongestPaymentMethodColWidth(List<OfferInfo> offers) {
return getLongestColumnSize(
COL_HEADER_PAYMENT_METHOD.length(),
offers.stream()
.map(OfferInfo::getPaymentMethodShortName)
.collect(Collectors.toList()));
}
private static int getLongestAmountColWidth(List<OfferInfo> offers) {
return getLongestColumnSize(
COL_HEADER_AMOUNT.length(),
offers.stream()
.map(o -> formatAmountRange(o.getMinAmount(), o.getAmount()))
.collect(Collectors.toList()));
}
private static int getLongestVolumeColWidth(List<OfferInfo> offers) {
// Pad this col width by 1 space.
return 1 + getLongestColumnSize(
COL_HEADER_VOLUME.length(),
offers.stream()
.map(o -> formatVolumeRange(o.getMinVolume(), o.getVolume()))
.collect(Collectors.toList()));
}
private static int getLongestCryptoCurrencyVolumeColWidth(List<OfferInfo> offers) {
// Pad this col width by 1 space.
return 1 + getLongestColumnSize(
COL_HEADER_VOLUME.length(),
offers.stream()
.map(o -> formatCryptoCurrencyVolumeRange(o.getMinVolume(), o.getVolume()))
.collect(Collectors.toList()));
}
}

View File

@ -21,7 +21,6 @@ import bisq.proto.grpc.AddressBalanceInfo;
import bisq.proto.grpc.BalancesInfo;
import bisq.proto.grpc.BsqBalanceInfo;
import bisq.proto.grpc.BtcBalanceInfo;
import bisq.proto.grpc.OfferInfo;
import protobuf.PaymentAccount;
@ -35,16 +34,15 @@ import java.util.TimeZone;
import java.util.stream.Collectors;
import static bisq.cli.ColumnHeaderConstants.*;
import static bisq.cli.CurrencyFormat.*;
import static bisq.cli.DirectionFormat.directionFormat;
import static bisq.cli.DirectionFormat.getLongestDirectionColWidth;
import static bisq.cli.CurrencyFormat.formatBsq;
import static bisq.cli.CurrencyFormat.formatSatoshis;
import static com.google.common.base.Strings.padEnd;
import static com.google.common.base.Strings.padStart;
import static java.lang.String.format;
import static java.util.Collections.max;
import static java.util.Comparator.comparing;
import static java.util.TimeZone.getTimeZone;
@Deprecated
@VisibleForTesting
public class TableFormat {
@ -145,234 +143,13 @@ public class TableFormat {
.collect(Collectors.joining("\n"));
}
public static String formatOfferTable(List<OfferInfo> offers, String currencyCode) {
if (offers == null || offers.isEmpty())
throw new IllegalArgumentException(format("%s offer list is empty", currencyCode.toLowerCase()));
String baseCurrencyCode = offers.get(0).getBaseCurrencyCode();
boolean isMyOffer = offers.get(0).getIsMyOffer();
return baseCurrencyCode.equalsIgnoreCase("BTC")
? formatFiatOfferTable(offers, currencyCode, isMyOffer)
: formatCryptoCurrencyOfferTable(offers, baseCurrencyCode, isMyOffer);
}
private static String formatFiatOfferTable(List<OfferInfo> offers,
String fiatCurrencyCode,
boolean isMyOffer) {
// Some column values might be longer than header, so we need to calculate them.
int amountColWith = getLongestAmountColWidth(offers);
int volumeColWidth = getLongestVolumeColWidth(offers);
int paymentMethodColWidth = getLongestPaymentMethodColWidth(offers);
// "Enabled" and "Trigger Price" columns are displayed for my offers only.
String enabledHeaderFormat = isMyOffer ?
COL_HEADER_ENABLED + COL_HEADER_DELIMITER
: "";
String triggerPriceHeaderFormat = isMyOffer ?
// COL_HEADER_TRIGGER_PRICE includes %s -> fiatCurrencyCode
COL_HEADER_TRIGGER_PRICE + COL_HEADER_DELIMITER
: "";
String headersFormat = enabledHeaderFormat
+ COL_HEADER_DIRECTION + COL_HEADER_DELIMITER
// COL_HEADER_PRICE includes %s -> fiatCurrencyCode
+ COL_HEADER_PRICE + COL_HEADER_DELIMITER
+ padStart(COL_HEADER_AMOUNT, amountColWith, ' ') + COL_HEADER_DELIMITER
// COL_HEADER_VOLUME includes %s -> fiatCurrencyCode
+ padStart(COL_HEADER_VOLUME, volumeColWidth, ' ') + COL_HEADER_DELIMITER
+ triggerPriceHeaderFormat
+ padEnd(COL_HEADER_PAYMENT_METHOD, paymentMethodColWidth, ' ') + COL_HEADER_DELIMITER
+ COL_HEADER_CREATION_DATE + COL_HEADER_DELIMITER
+ COL_HEADER_UUID.trim() + "%n";
String headerLine = format(headersFormat,
fiatCurrencyCode.toUpperCase(),
fiatCurrencyCode.toUpperCase(),
// COL_HEADER_TRIGGER_PRICE includes %s -> fiatCurrencyCode
isMyOffer ? fiatCurrencyCode.toUpperCase() : "");
String colDataFormat = getFiatOfferColDataFormat(isMyOffer,
amountColWith,
volumeColWidth,
paymentMethodColWidth);
return formattedFiatOfferTable(offers, isMyOffer, headerLine, colDataFormat);
}
private static String formattedFiatOfferTable(List<OfferInfo> offers,
boolean isMyOffer,
String headerLine,
String colDataFormat) {
if (isMyOffer) {
return headerLine
+ offers.stream()
.map(o -> format(colDataFormat,
formatEnabled(o),
o.getDirection(),
formatPrice(o.getPrice()),
formatAmountRange(o.getMinAmount(), o.getAmount()),
formatVolumeRange(o.getMinVolume(), o.getVolume()),
o.getTriggerPrice() == 0 ? "" : formatPrice(o.getTriggerPrice()),
o.getPaymentMethodShortName(),
formatTimestamp(o.getDate()),
o.getId()))
.collect(Collectors.joining("\n"));
} else {
return headerLine
+ offers.stream()
.map(o -> format(colDataFormat,
o.getDirection(),
formatPrice(o.getPrice()),
formatAmountRange(o.getMinAmount(), o.getAmount()),
formatVolumeRange(o.getMinVolume(), o.getVolume()),
o.getPaymentMethodShortName(),
formatTimestamp(o.getDate()),
o.getId()))
.collect(Collectors.joining("\n"));
}
}
private static String getFiatOfferColDataFormat(boolean isMyOffer,
int amountColWith,
int volumeColWidth,
int paymentMethodColWidth) {
if (isMyOffer) {
return "%-" + (COL_HEADER_ENABLED.length() + COL_HEADER_DELIMITER.length()) + "s"
+ "%-" + (COL_HEADER_DIRECTION.length() + COL_HEADER_DELIMITER.length()) + "s"
+ "%" + (COL_HEADER_PRICE.length() - 1) + "s"
+ " %" + amountColWith + "s"
+ " %" + (volumeColWidth - 1) + "s"
+ " %" + (COL_HEADER_TRIGGER_PRICE.length() - 1) + "s"
+ " %-" + paymentMethodColWidth + "s"
+ " %-" + (COL_HEADER_CREATION_DATE.length()) + "s"
+ " %-" + COL_HEADER_UUID.length() + "s";
} else {
return "%-" + (COL_HEADER_DIRECTION.length() + COL_HEADER_DELIMITER.length()) + "s"
+ "%" + (COL_HEADER_PRICE.length() - 1) + "s"
+ " %" + amountColWith + "s"
+ " %" + (volumeColWidth - 1) + "s"
+ " %-" + paymentMethodColWidth + "s"
+ " %-" + (COL_HEADER_CREATION_DATE.length()) + "s"
+ " %-" + COL_HEADER_UUID.length() + "s";
}
}
private static String formatCryptoCurrencyOfferTable(List<OfferInfo> offers,
String cryptoCurrencyCode,
boolean isMyOffer) {
// Some column values might be longer than header, so we need to calculate them.
int directionColWidth = getLongestDirectionColWidth(offers);
int amountColWith = getLongestAmountColWidth(offers);
int volumeColWidth = getLongestCryptoCurrencyVolumeColWidth(offers);
int paymentMethodColWidth = getLongestPaymentMethodColWidth(offers);
// "Enabled" column is displayed for my offers only.
String enabledHeaderFormat = isMyOffer ?
COL_HEADER_ENABLED + COL_HEADER_DELIMITER
: "";
// TODO use memoize function to avoid duplicate the formatting done above?
String headersFormat = enabledHeaderFormat
+ padEnd(COL_HEADER_DIRECTION, directionColWidth, ' ') + COL_HEADER_DELIMITER
+ COL_HEADER_PRICE_OF_ALTCOIN + COL_HEADER_DELIMITER // includes %s -> cryptoCurrencyCode
+ padStart(COL_HEADER_AMOUNT, amountColWith, ' ') + COL_HEADER_DELIMITER
// COL_HEADER_VOLUME includes %s -> cryptoCurrencyCode
+ padStart(COL_HEADER_VOLUME, volumeColWidth, ' ') + COL_HEADER_DELIMITER
+ padEnd(COL_HEADER_PAYMENT_METHOD, paymentMethodColWidth, ' ') + COL_HEADER_DELIMITER
+ COL_HEADER_CREATION_DATE + COL_HEADER_DELIMITER
+ COL_HEADER_UUID.trim() + "%n";
String headerLine = format(headersFormat,
cryptoCurrencyCode.toUpperCase(),
cryptoCurrencyCode.toUpperCase());
String colDataFormat;
if (isMyOffer) {
colDataFormat = "%-" + (COL_HEADER_ENABLED.length() + COL_HEADER_DELIMITER.length()) + "s"
+ "%-" + directionColWidth + "s"
+ "%" + (COL_HEADER_PRICE_OF_ALTCOIN.length() + 1) + "s"
+ " %" + amountColWith + "s"
+ " %" + (volumeColWidth - 1) + "s"
+ " %-" + paymentMethodColWidth + "s"
+ " %-" + (COL_HEADER_CREATION_DATE.length()) + "s"
+ " %-" + COL_HEADER_UUID.length() + "s";
} else {
colDataFormat = "%-" + directionColWidth + "s"
+ "%" + (COL_HEADER_PRICE_OF_ALTCOIN.length() + 1) + "s"
+ " %" + amountColWith + "s"
+ " %" + (volumeColWidth - 1) + "s"
+ " %-" + paymentMethodColWidth + "s"
+ " %-" + (COL_HEADER_CREATION_DATE.length()) + "s"
+ " %-" + COL_HEADER_UUID.length() + "s";
}
if (isMyOffer) {
return headerLine
+ offers.stream()
.map(o -> format(colDataFormat,
formatEnabled(o),
directionFormat.apply(o),
formatCryptoCurrencyPrice(o.getPrice()),
formatAmountRange(o.getMinAmount(), o.getAmount()),
formatCryptoCurrencyVolumeRange(o.getMinVolume(), o.getVolume()),
o.getPaymentMethodShortName(),
formatTimestamp(o.getDate()),
o.getId()))
.collect(Collectors.joining("\n"));
} else {
return headerLine
+ offers.stream()
.map(o -> format(colDataFormat,
directionFormat.apply(o),
formatCryptoCurrencyPrice(o.getPrice()),
formatAmountRange(o.getMinAmount(), o.getAmount()),
formatCryptoCurrencyVolumeRange(o.getMinVolume(), o.getVolume()),
o.getPaymentMethodShortName(),
formatTimestamp(o.getDate()),
o.getId()))
.collect(Collectors.joining("\n"));
}
}
private static String formatEnabled(OfferInfo offerInfo) {
if (offerInfo.getIsMyOffer() && offerInfo.getIsMyPendingOffer())
return "PENDING";
else
return offerInfo.getIsActivated() ? "YES" : "NO";
}
private static int getLongestPaymentMethodColWidth(List<OfferInfo> offers) {
return getLongestColumnSize(
COL_HEADER_PAYMENT_METHOD.length(),
offers.stream()
.map(OfferInfo::getPaymentMethodShortName)
.collect(Collectors.toList()));
}
private static int getLongestAmountColWidth(List<OfferInfo> offers) {
return getLongestColumnSize(
COL_HEADER_AMOUNT.length(),
offers.stream()
.map(o -> formatAmountRange(o.getMinAmount(), o.getAmount()))
.collect(Collectors.toList()));
}
private static int getLongestVolumeColWidth(List<OfferInfo> offers) {
// Pad this col width by 1 space.
return 1 + getLongestColumnSize(
COL_HEADER_VOLUME.length(),
offers.stream()
.map(o -> formatVolumeRange(o.getMinVolume(), o.getVolume()))
.collect(Collectors.toList()));
}
private static int getLongestCryptoCurrencyVolumeColWidth(List<OfferInfo> offers) {
// Pad this col width by 1 space.
return 1 + getLongestColumnSize(
COL_HEADER_VOLUME.length(),
offers.stream()
.map(o -> formatCryptoCurrencyVolumeRange(o.getMinVolume(), o.getVolume()))
.collect(Collectors.toList()));
}
// Return size of the longest string value, or the header.len, whichever is greater.
private static int getLongestColumnSize(int headerLength, List<String> strings) {
static int getLongestColumnSize(int headerLength, List<String> strings) {
int longest = max(strings, comparing(String::length)).length();
return Math.max(longest, headerLength);
}
private static String formatTimestamp(long timestamp) {
static String formatTimestamp(long timestamp) {
DATE_FORMAT_ISO_8601.setTimeZone(TZ_UTC);
return DATE_FORMAT_ISO_8601.format(new Date(timestamp));
}

View File

@ -30,6 +30,7 @@ import static bisq.cli.ColumnHeaderConstants.*;
import static bisq.cli.CurrencyFormat.*;
import static com.google.common.base.Strings.padEnd;
@Deprecated
@VisibleForTesting
public class TradeFormat {
@ -164,7 +165,7 @@ public class TradeFormat {
private static final Function<TradeInfo, String> amountFormat = (t) ->
t.getOffer().getBaseCurrencyCode().equals("BTC")
? formatSatoshis(t.getTradeAmountAsLong())
: formatCryptoCurrencyOfferVolume(t.getTradeVolume());
: formatCryptoCurrencyVolume(t.getTradeVolume());
private static final BiFunction<TradeInfo, Boolean, String> makerTakerMinerTxFeeFormat = (t, isTaker) -> {
if (isTaker) {
@ -188,7 +189,7 @@ public class TradeFormat {
private static final Function<TradeInfo, String> tradeCostFormat = (t) ->
t.getOffer().getBaseCurrencyCode().equals("BTC")
? formatOfferVolume(t.getTradeVolume())
? formatFiatVolume(t.getTradeVolume())
: formatSatoshis(t.getTradeAmountAsLong());
private static final BiFunction<TradeInfo, Boolean, String> bsqReceiveAddress = (t, showBsqBuyerAddress) -> {

View File

@ -25,6 +25,7 @@ import static bisq.cli.ColumnHeaderConstants.*;
import static bisq.cli.CurrencyFormat.formatSatoshis;
import static com.google.common.base.Strings.padEnd;
@Deprecated
@VisibleForTesting
public class TransactionFormat {

View File

@ -20,7 +20,10 @@ package bisq.cli.opts;
import joptsimple.OptionSpec;
import static bisq.cli.opts.OptLabel.*;
import static bisq.cli.opts.OptLabel.OPT_ACCOUNT_NAME;
import static bisq.cli.opts.OptLabel.OPT_ADDRESS;
import static bisq.cli.opts.OptLabel.OPT_CURRENCY_CODE;
import static bisq.cli.opts.OptLabel.OPT_TRADE_INSTANT;
public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodOptionParser implements MethodOpts {
@ -38,11 +41,6 @@ public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodO
.ofType(boolean.class)
.defaultsTo(Boolean.FALSE);
final OptionSpec<Boolean> tradeBsqSwapOpt = parser.accepts(OPT_TRADE_BSQ_SWAP, "create trade bsq swap account")
.withOptionalArg()
.ofType(boolean.class)
.defaultsTo(Boolean.FALSE);
public CreateCryptoCurrencyPaymentAcctOptionParser(String[] args) {
super(args);
}
@ -84,8 +82,4 @@ public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodO
public boolean getIsTradeInstant() {
return options.valueOf(tradeInstantOpt);
}
public boolean getIsTradeBsqSwap() {
return options.valueOf(tradeBsqSwapOpt);
}
}

View File

@ -44,7 +44,6 @@ public class OptLabel {
public final static String OPT_REGISTRATION_KEY = "registration-key";
public final static String OPT_SECURITY_DEPOSIT = "security-deposit";
public final static String OPT_SHOW_CONTRACT = "show-contract";
public final static String OPT_TRADE_BSQ_SWAP = "trade-bsq-swap";
public final static String OPT_TRADE_ID = "trade-id";
public final static String OPT_TRADE_INSTANT = "trade-instant";
public final static String OPT_TIMEOUT = "timeout";

View File

@ -60,6 +60,7 @@ public class OffersServiceRequest {
this.grpcStubs = grpcStubs;
}
@SuppressWarnings("unused")
public OfferInfo createFixedPricedOffer(String direction,
String currencyCode,
long amount,
@ -81,6 +82,7 @@ public class OffersServiceRequest {
0 /* no trigger price */);
}
@SuppressWarnings("unused")
public OfferInfo createMarketBasedPricedOffer(String direction,
String currencyCode,
long amount,
@ -328,6 +330,13 @@ public class OffersServiceRequest {
return sortOffersByDate(offers);
}
public List<OfferInfo> getMyCryptoCurrencyOffersSortedByDate(String currencyCode) {
ArrayList<OfferInfo> offers = new ArrayList<>();
offers.addAll(getMyCryptoCurrencyOffers(BUY.name(), currencyCode));
offers.addAll(getMyCryptoCurrencyOffers(SELL.name(), currencyCode));
return sortOffersByDate(offers);
}
public List<OfferInfo> getMyBsqOffersSortedByDate() {
ArrayList<OfferInfo> offers = new ArrayList<>();
offers.addAll(getMyCryptoCurrencyOffers(BUY.name(), "BSQ"));

View File

@ -29,6 +29,8 @@ import protobuf.PaymentMethod;
import java.util.List;
import static java.lang.String.format;
import bisq.cli.GrpcStubs;
@ -65,6 +67,22 @@ public class PaymentAccountsServiceRequest {
return grpcStubs.paymentAccountsService.getPaymentAccounts(request).getPaymentAccountsList();
}
/**
* Returns the first PaymentAccount found with the given name, or throws an
* IllegalArgumentException if not found. This method should be used with care;
* it will only return one PaymentAccount, and the account name must be an exact
* match on the name argument.
* @param accountName the name of the stored PaymentAccount to retrieve
* @return PaymentAccount with given name
*/
public PaymentAccount getPaymentAccount(String accountName) {
return getPaymentAccounts().stream()
.filter(a -> a.getAccountName().equals(accountName)).findFirst()
.orElseThrow(() ->
new IllegalArgumentException(format("payment account with name '%s' not found",
accountName)));
}
public PaymentAccount createCryptoCurrencyPaymentAccount(String accountName,
String currencyCode,
String address,

View File

@ -74,6 +74,7 @@ public class TradesServiceRequest {
var request = ConfirmPaymentStartedRequest.newBuilder()
.setTradeId(tradeId)
.build();
//noinspection ResultOfMethodCallIgnored
grpcStubs.tradesService.confirmPaymentStarted(request);
}
@ -81,6 +82,7 @@ public class TradesServiceRequest {
var request = ConfirmPaymentReceivedRequest.newBuilder()
.setTradeId(tradeId)
.build();
//noinspection ResultOfMethodCallIgnored
grpcStubs.tradesService.confirmPaymentReceived(request);
}
@ -88,6 +90,7 @@ public class TradesServiceRequest {
var request = KeepFundsRequest.newBuilder()
.setTradeId(tradeId)
.build();
//noinspection ResultOfMethodCallIgnored
grpcStubs.tradesService.keepFunds(request);
}
@ -97,6 +100,7 @@ public class TradesServiceRequest {
.setAddress(address)
.setMemo(memo)
.build();
//noinspection ResultOfMethodCallIgnored
grpcStubs.tradesService.withdrawFunds(request);
}
}

View File

@ -161,6 +161,7 @@ public class WalletsServiceRequest {
public void lockWallet() {
var request = LockWalletRequest.newBuilder().build();
//noinspection ResultOfMethodCallIgnored
grpcStubs.walletsService.lockWallet(request);
}
@ -168,18 +169,21 @@ public class WalletsServiceRequest {
var request = UnlockWalletRequest.newBuilder()
.setPassword(walletPassword)
.setTimeout(timeout).build();
//noinspection ResultOfMethodCallIgnored
grpcStubs.walletsService.unlockWallet(request);
}
public void removeWalletPassword(String walletPassword) {
var request = RemoveWalletPasswordRequest.newBuilder()
.setPassword(walletPassword).build();
//noinspection ResultOfMethodCallIgnored
grpcStubs.walletsService.removeWalletPassword(request);
}
public void setWalletPassword(String walletPassword) {
var request = SetWalletPasswordRequest.newBuilder()
.setPassword(walletPassword).build();
//noinspection ResultOfMethodCallIgnored
grpcStubs.walletsService.setWalletPassword(request);
}
@ -187,6 +191,7 @@ public class WalletsServiceRequest {
var request = SetWalletPasswordRequest.newBuilder()
.setPassword(oldWalletPassword)
.setNewPassword(newWalletPassword).build();
//noinspection ResultOfMethodCallIgnored
grpcStubs.walletsService.setWalletPassword(request);
}
}

View File

@ -0,0 +1,155 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.cli.table;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.stream.IntStream;
import static bisq.cli.table.column.Column.JUSTIFICATION.RIGHT;
import static com.google.common.base.Strings.padStart;
import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8;
import bisq.cli.table.column.Column;
/**
* A simple table of formatted data for the CLI's output console. A table must be
* created with at least one populated column, and each column passed to the constructor
* must contain the same number of rows. Null checking is omitted because tables are
* populated by protobuf message fields which cannot be null.
*
* All data in a column has the same type: long, string, etc., but a table
* may contain an arbitrary number of columns of any type. For output formatting
* purposes, numeric and date columns should be transformed to a StringColumn type with
* formatted and justified string values before being passed to the constructor.
*
* This is not a relational, rdbms table.
*/
public class Table {
public final Column<?>[] columns;
public final int rowCount;
// Each printed column is delimited by two spaces.
private final int columnDelimiterLength = 2;
/**
* Default constructor. Takes populated Columns.
*
* @param columns containing the same number of rows
*/
public Table(Column<?>... columns) {
this.columns = columns;
this.rowCount = columns.length > 0 ? columns[0].rowCount() : 0;
validateStructure();
}
/**
* Print table data to a PrintStream.
*
* @param printStream the target output stream
*/
public void print(PrintStream printStream) {
printColumnNames(printStream);
for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) {
printRow(printStream, rowIndex);
}
}
/**
* Print table column names to a PrintStream.
*
* @param printStream the target output stream
*/
private void printColumnNames(PrintStream printStream) {
IntStream.range(0, columns.length).forEachOrdered(colIndex -> {
var c = columns[colIndex];
var justifiedName = c.getJustification().equals(RIGHT)
? padStart(c.getName(), c.getWidth(), ' ')
: c.getName();
var paddedWidth = colIndex == columns.length - 1
? c.getName().length()
: c.getWidth() + columnDelimiterLength;
printStream.printf("%-" + paddedWidth + "s", justifiedName);
});
printStream.println();
}
/**
* Print a table row to a PrintStream.
*
* @param printStream the target output stream
*/
private void printRow(PrintStream printStream, int rowIndex) {
IntStream.range(0, columns.length).forEachOrdered(colIndex -> {
var c = columns[colIndex];
var paddedWidth = colIndex == columns.length - 1
? c.getWidth()
: c.getWidth() + columnDelimiterLength;
printStream.printf("%-" + paddedWidth + "s", c.getRow(rowIndex));
if (colIndex == columns.length - 1)
printStream.println();
});
}
/**
* Returns the table's formatted output as a String.
* @return String
*/
@Override
public String toString() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (PrintStream ps = new PrintStream(baos, true, UTF_8)) {
print(ps);
}
return baos.toString();
}
/**
* Verifies the table has columns, and each column has the same number of rows.
*/
private void validateStructure() {
if (columns.length == 0)
throw new IllegalArgumentException("Table has no columns.");
if (columns[0].isEmpty())
throw new IllegalArgumentException(
format("Table's 1st column (%s) has no data.",
columns[0].getName()));
IntStream.range(1, columns.length).forEachOrdered(colIndex -> {
var c = columns[colIndex];
if (c.isEmpty())
throw new IllegalStateException(
format("Table column # %d (%s) does not have any data.",
colIndex + 1,
c.getName()));
if (this.rowCount != c.rowCount())
throw new IllegalStateException(
format("Table column # %d (%s) does not have same number of rows as 1st column.",
colIndex + 1,
c.getName()));
});
}
}

View File

@ -0,0 +1,47 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.cli.table.builder;
import bisq.proto.grpc.OfferInfo;
import java.util.List;
import java.util.function.Predicate;
import bisq.cli.table.Table;
/**
* Abstract superclass for TableBuilder implementations.
*/
abstract class AbstractTableBuilder {
protected final TableType tableType;
protected final List<?> protos;
AbstractTableBuilder(TableType tableType, List<?> protos) {
this.tableType = tableType;
this.protos = protos;
if (protos.isEmpty())
throw new IllegalArgumentException("cannot build a table without rows");
}
public abstract Table build();
protected final Predicate<OfferInfo> isFiatOffer = (o) -> o.getBaseCurrencyCode().equals("BTC");
}

View File

@ -0,0 +1,303 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.cli.table.builder;
import bisq.proto.grpc.ContractInfo;
import bisq.proto.grpc.TradeInfo;
import java.text.DecimalFormat;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import static bisq.cli.table.builder.TableBuilderConstants.COL_HEADER_BUYER_DEPOSIT;
import static bisq.cli.table.builder.TableBuilderConstants.COL_HEADER_SELLER_DEPOSIT;
import static bisq.cli.table.builder.TableType.TRADE_DETAIL_TBL;
import bisq.cli.table.column.Column;
import bisq.cli.table.column.MixedTradeFeeColumn;
import bisq.cli.table.column.MixedVolumeColumn;
abstract class AbstractTradeListBuilder extends AbstractTableBuilder {
protected final List<TradeInfo> trades;
protected final TradeTableColumnSupplier colSupplier;
protected final Column<String> colTradeId;
@Nullable
protected final Column<Long> colCreateDate;
@Nullable
protected final Column<String> colMarket;
protected final Column<Long> colPrice;
@Nullable
protected final Column<String> colPriceDeviation;
@Nullable
protected final Column<String> colCurrency;
@Nullable
protected final Column<Long> colAmountInBtc;
@Nullable
protected final MixedVolumeColumn colMixedAmount;
@Nullable
protected final Column<Long> colMinerTxFee;
@Nullable
protected final MixedTradeFeeColumn colMixedTradeFee;
@Nullable
protected final Column<Long> colBuyerDeposit;
@Nullable
protected final Column<Long> colSellerDeposit;
@Nullable
protected final Column<String> colPaymentMethod;
@Nullable
protected final Column<String> colRole;
@Nullable
protected final Column<String> colOfferType;
@Nullable
protected final Column<String> colStatusDescription;
// Trade detail tbl specific columns
@Nullable
protected final Column<Boolean> colIsDepositPublished;
@Nullable
protected final Column<Boolean> colIsDepositConfirmed;
@Nullable
protected final Column<Boolean> colIsPayoutPublished;
@Nullable
protected final Column<Boolean> colIsFundsWithdrawn;
@Nullable
protected final Column<Long> colBisqTradeFee;
@Nullable
protected final Column<Long> colTradeCost;
@Nullable
protected final Column<Boolean> colIsPaymentSent;
@Nullable
protected final Column<Boolean> colIsPaymentReceived;
@Nullable
protected final Column<String> colAltcoinReceiveAddressColumn;
AbstractTradeListBuilder(TableType tableType, List<?> protos) {
super(tableType, protos);
validate();
this.trades = protos.stream().map(p -> (TradeInfo) p).collect(Collectors.toList());
this.colSupplier = new TradeTableColumnSupplier(tableType, trades);
this.colTradeId = colSupplier.tradeIdColumn.get();
this.colCreateDate = colSupplier.createDateColumn.get();
this.colMarket = colSupplier.marketColumn.get();
this.colPrice = colSupplier.priceColumn.get();
this.colPriceDeviation = colSupplier.priceDeviationColumn.get();
this.colCurrency = colSupplier.currencyColumn.get();
this.colAmountInBtc = colSupplier.amountInBtcColumn.get();
this.colMixedAmount = colSupplier.mixedAmountColumn.get();
this.colMinerTxFee = colSupplier.minerTxFeeColumn.get();
this.colMixedTradeFee = colSupplier.mixedTradeFeeColumn.get();
this.colBuyerDeposit = colSupplier.toSecurityDepositColumn.apply(COL_HEADER_BUYER_DEPOSIT);
this.colSellerDeposit = colSupplier.toSecurityDepositColumn.apply(COL_HEADER_SELLER_DEPOSIT);
this.colPaymentMethod = colSupplier.paymentMethodColumn.get();
this.colRole = colSupplier.roleColumn.get();
this.colOfferType = colSupplier.offerTypeColumn.get();
this.colStatusDescription = colSupplier.statusDescriptionColumn.get();
// Trade detail specific columns
this.colIsDepositPublished = colSupplier.depositPublishedColumn.get();
this.colIsDepositConfirmed = colSupplier.depositConfirmedColumn.get();
this.colIsPayoutPublished = colSupplier.payoutPublishedColumn.get();
this.colIsFundsWithdrawn = colSupplier.fundsWithdrawnColumn.get();
this.colBisqTradeFee = colSupplier.bisqTradeDetailFeeColumn.get();
this.colTradeCost = colSupplier.tradeCostColumn.get();
this.colIsPaymentSent = colSupplier.paymentSentColumn.get();
this.colIsPaymentReceived = colSupplier.paymentReceivedColumn.get();
this.colAltcoinReceiveAddressColumn = colSupplier.altcoinReceiveAddressColumn.get();
}
protected void validate() {
if (isTradeDetailTblBuilder.get()) {
if (protos.size() != 1)
throw new IllegalArgumentException("trade detail tbl can have only one row");
} else if (protos.isEmpty()) {
throw new IllegalArgumentException("trade tbl has no rows");
}
}
// Helper Functions
private final Supplier<Boolean> isTradeDetailTblBuilder = () -> tableType.equals(TRADE_DETAIL_TBL);
protected final Predicate<TradeInfo> isFiatTrade = (t) -> isFiatOffer.test(t.getOffer());
protected final Predicate<TradeInfo> isTaker = (t) -> t.getRole().toLowerCase().contains("taker");
// Column Value Functions
protected final Function<TradeInfo, Long> toAmount = (t) ->
isFiatTrade.test(t)
? t.getTradeAmountAsLong()
: t.getTradeVolume();
protected final Function<TradeInfo, Long> toTradeVolume = (t) ->
isFiatTrade.test(t)
? t.getTradeVolume()
: t.getTradeAmountAsLong();
protected final Function<TradeInfo, String> toMarket = (t) ->
t.getOffer().getBaseCurrencyCode() + "/"
+ t.getOffer().getCounterCurrencyCode();
protected final Function<TradeInfo, String> toPaymentCurrencyCode = (t) ->
isFiatTrade.test(t)
? t.getOffer().getCounterCurrencyCode()
: t.getOffer().getBaseCurrencyCode();
protected final Function<TradeInfo, Integer> toDisplayedVolumePrecision = (t) -> {
if (isFiatTrade.test(t)) {
return 0;
} else {
String currencyCode = toPaymentCurrencyCode.apply(t);
return currencyCode.equalsIgnoreCase("BSQ") ? 2 : 8;
}
};
// TODO Move to TradeUtil ?
protected final Function<TradeInfo, String> toPriceDeviation = (t) ->
t.getOffer().getUseMarketBasedPrice()
? formatToPercent(t.getOffer().getMarketPriceMargin())
: "N/A";
protected final Function<TradeInfo, Long> toMyMinerTxFee = (t) ->
isTaker.test(t)
? t.getTxFeeAsLong()
: t.getOffer().getTxFee();
// TODO Move to TradeUtil ?
protected final BiFunction<TradeInfo, Boolean, Long> toTradeFeeBsq = (t, isMyOffer) -> {
if (isMyOffer) {
return t.getOffer().getIsCurrencyForMakerFeeBtc()
? 0L // Maker paid BTC fee, return 0.
: t.getOffer().getMakerFee();
} else {
return t.getIsCurrencyForTakerFeeBtc()
? 0L // Taker paid BTC fee, return 0.
: t.getTakerFeeAsLong();
}
};
// TODO Move to TradeUtil ?
protected final BiFunction<TradeInfo, Boolean, Long> toTradeFeeBtc = (t, isMyOffer) -> {
if (isMyOffer) {
return t.getOffer().getIsCurrencyForMakerFeeBtc()
? t.getOffer().getMakerFee()
: 0L; // Maker paid BSQ fee, return 0.
} else {
return t.getIsCurrencyForTakerFeeBtc()
? t.getTakerFeeAsLong()
: 0L; // Taker paid BSQ fee, return 0.
}
};
protected final Function<TradeInfo, Long> toMyMakerOrTakerFee = (t) ->
isTaker.test(t)
? t.getTakerFeeAsLong()
: t.getOffer().getMakerFee();
// TODO Move to TradeUtil ? SEE ClosedTradesViewModel # getDirectionLabel
protected final Function<TradeInfo, String> toOfferType = (t) -> {
if (isFiatTrade.test(t)) {
return t.getOffer().getDirection() + " " + t.getOffer().getBaseCurrencyCode();
} else {
if (t.getOffer().getDirection().equals("BUY")) {
return "SELL " + t.getOffer().getBaseCurrencyCode();
} else {
return "BUY " + t.getOffer().getBaseCurrencyCode();
}
}
};
protected final Predicate<TradeInfo> showAltCoinBuyerAddress = (t) -> {
if (isFiatTrade.test(t)) {
return false;
} else {
ContractInfo contract = t.getContract();
boolean isBuyerMakerAndSellerTaker = contract.getIsBuyerMakerAndSellerTaker();
if (isTaker.test(t)) {
return !isBuyerMakerAndSellerTaker;
} else {
return isBuyerMakerAndSellerTaker;
}
}
};
protected final Function<TradeInfo, String> toAltcoinReceiveAddress = (t) -> {
if (showAltCoinBuyerAddress.test(t)) {
ContractInfo contract = t.getContract();
boolean isBuyerMakerAndSellerTaker = contract.getIsBuyerMakerAndSellerTaker();
return isBuyerMakerAndSellerTaker // (is BTC buyer / maker)
? contract.getTakerPaymentAccountPayload().getAddress()
: contract.getMakerPaymentAccountPayload().getAddress();
} else {
return "";
}
};
// TODO Stuff to move into bisq/cli/CurrencyFormat.java ?
public static String formatToPercent(double value) {
DecimalFormat decimalFormat = new DecimalFormat("#.##");
decimalFormat.setMinimumFractionDigits(2);
decimalFormat.setMaximumFractionDigits(2);
return formatToPercent(value, decimalFormat);
}
public static String formatToPercent(double value, DecimalFormat decimalFormat) {
return decimalFormat.format(roundDouble(value * 100.0, 2)).replace(",", ".") + "%";
}
public static double roundDouble(double value, int precision) {
return roundDouble(value, precision, RoundingMode.HALF_UP);
}
@SuppressWarnings("SameParameterValue")
public static double roundDouble(double value, int precision, RoundingMode roundingMode) {
if (precision < 0)
throw new IllegalArgumentException();
if (!Double.isFinite(value))
throw new IllegalArgumentException("Expected a finite double, but found " + value);
try {
BigDecimal bd = BigDecimal.valueOf(value);
bd = bd.setScale(precision, roundingMode);
return bd.doubleValue();
} catch (Throwable t) {
t.printStackTrace(); // TODO throw pretty exception for CLI console
return 0;
}
}
}

View File

@ -0,0 +1,106 @@
package bisq.cli.table.builder;
import bisq.proto.grpc.TradeInfo;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import javax.annotation.Nullable;
import static bisq.cli.table.builder.TableBuilderConstants.*;
import static bisq.cli.table.builder.TableType.TRADE_DETAIL_TBL;
import static bisq.cli.table.column.Column.JUSTIFICATION.RIGHT;
import bisq.cli.table.column.BtcColumn;
import bisq.cli.table.column.Column;
import bisq.cli.table.column.Iso8601DateTimeColumn;
import bisq.cli.table.column.MixedPriceColumn;
import bisq.cli.table.column.MixedTradeFeeColumn;
import bisq.cli.table.column.MixedVolumeColumn;
import bisq.cli.table.column.SatoshiColumn;
import bisq.cli.table.column.StringColumn;
/**
* Builds a {@code bisq.cli.table.Table} from one or more {@code bisq.proto.grpc.TradeInfo} objects.
*/
abstract class AbstractTradeTableBuilder extends AbstractTableBuilder {
@Nullable
protected final Column<String> colTradeId;
@Nullable
protected final Column<Long> colCreateDate;
@Nullable
protected final Column<String> colMarket;
@Nullable
protected final MixedPriceColumn colMixedPrice;
@Nullable
protected final Column<String> colPriceDeviation;
@Nullable
protected final Column<Long> colAmountInBtc;
@Nullable
protected final MixedVolumeColumn colMixedAmount;
@Nullable
protected final Column<String> colCurrency;
@Nullable
protected final MixedTradeFeeColumn colMixedTradeFee;
@Nullable
protected final Column<Long> colBuyerDeposit;
@Nullable
protected final Column<Long> colSellerDeposit;
@Nullable
protected final Column<String> colOfferType;
@Nullable
protected final Column<String> colStatus;
protected final Column<Long> colMinerTxFee;
AbstractTradeTableBuilder(TableType tableType, List<?> protos) {
super(tableType, protos);
boolean isTradeDetail = tableType.equals(TRADE_DETAIL_TBL);
this.colTradeId = isTradeDetail ? null : new StringColumn(COL_HEADER_TRADE_ID);
this.colCreateDate = isTradeDetail ? null : new Iso8601DateTimeColumn(COL_HEADER_DATE_TIME);
this.colMarket = isTradeDetail ? null : new StringColumn(COL_HEADER_MARKET);
this.colMixedPrice = isTradeDetail ? null : new MixedPriceColumn(COL_HEADER_PRICE);
this.colPriceDeviation = isTradeDetail ? null : new StringColumn(COL_HEADER_DEVIATION, RIGHT);
this.colAmountInBtc = isTradeDetail ? null : new BtcColumn(COL_HEADER_AMOUNT_IN_BTC);
this.colMixedAmount = isTradeDetail ? null : new MixedVolumeColumn(COL_HEADER_AMOUNT);
this.colCurrency = isTradeDetail ? null : new StringColumn(COL_HEADER_CURRENCY);
this.colMixedTradeFee = isTradeDetail ? null : new MixedTradeFeeColumn(COL_HEADER_TRADE_FEE);
this.colBuyerDeposit = isTradeDetail ? null : new SatoshiColumn(COL_HEADER_BUYER_DEPOSIT);
this.colSellerDeposit = isTradeDetail ? null : new SatoshiColumn(COL_HEADER_SELLER_DEPOSIT);
this.colOfferType = isTradeDetail ? null : new StringColumn(COL_HEADER_OFFER_TYPE);
this.colStatus = isTradeDetail ? null : new StringColumn(COL_HEADER_STATUS);
this.colMinerTxFee = new SatoshiColumn(COL_HEADER_TX_FEE);
}
protected final Predicate<TradeInfo> isFiatTrade = (t) -> isFiatOffer.test(t.getOffer());
protected final Predicate<TradeInfo> isTaker = (t) -> t.getRole().toLowerCase().contains("taker");
protected final Function<TradeInfo, String> toPaymentCurrencyCode = (t) ->
isFiatTrade.test(t)
? t.getOffer().getCounterCurrencyCode()
: t.getOffer().getBaseCurrencyCode();
protected final Function<TradeInfo, Long> toAmount = (t) ->
isFiatTrade.test(t)
? t.getTradeAmountAsLong()
: t.getTradeVolume();
protected final Function<TradeInfo, Long> toMinerTxFee = (t) ->
isTaker.test(t)
? t.getTxFeeAsLong()
: t.getOffer().getTxFee();
protected final Function<TradeInfo, Long> toMakerTakerFee = (t) ->
isTaker.test(t)
? t.getTakerFeeAsLong()
: t.getOffer().getMakerFee();
protected final Function<TradeInfo, Long> toTradeCost = (t) ->
isFiatTrade.test(t)
? t.getTradeVolume()
: t.getTradeAmountAsLong();
}

View File

@ -0,0 +1,81 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.cli.table.builder;
import bisq.proto.grpc.AddressBalanceInfo;
import java.util.List;
import java.util.stream.Collectors;
import static bisq.cli.table.builder.TableBuilderConstants.COL_HEADER_ADDRESS;
import static bisq.cli.table.builder.TableBuilderConstants.COL_HEADER_AVAILABLE_BALANCE;
import static bisq.cli.table.builder.TableBuilderConstants.COL_HEADER_CONFIRMATIONS;
import static bisq.cli.table.builder.TableBuilderConstants.COL_HEADER_IS_USED_ADDRESS;
import static bisq.cli.table.builder.TableType.ADDRESS_BALANCE_TBL;
import static java.lang.String.format;
import bisq.cli.table.Table;
import bisq.cli.table.column.BooleanColumn;
import bisq.cli.table.column.Column;
import bisq.cli.table.column.LongColumn;
import bisq.cli.table.column.SatoshiColumn;
import bisq.cli.table.column.StringColumn;
/**
* Builds a {@code bisq.cli.table.Table} from a List of
* {@code bisq.proto.grpc.AddressBalanceInfo} objects.
*/
class AddressBalanceTableBuilder extends AbstractTableBuilder {
// Default columns not dynamically generated with address info.
private final Column<String> colAddress;
private final Column<Long> colAvailableBalance;
private final Column<Long> colConfirmations;
private final Column<Boolean> colIsUsed;
AddressBalanceTableBuilder(List<?> protos) {
super(ADDRESS_BALANCE_TBL, protos);
colAddress = new StringColumn(format(COL_HEADER_ADDRESS, "BTC"));
this.colAvailableBalance = new SatoshiColumn(COL_HEADER_AVAILABLE_BALANCE);
this.colConfirmations = new LongColumn(COL_HEADER_CONFIRMATIONS);
this.colIsUsed = new BooleanColumn(COL_HEADER_IS_USED_ADDRESS);
}
public Table build() {
List<AddressBalanceInfo> addresses = protos.stream()
.map(a -> (AddressBalanceInfo) a)
.collect(Collectors.toList());
// Populate columns with address info.
//noinspection SimplifyStreamApiCallChains
addresses.stream().forEachOrdered(a -> {
colAddress.addRow(a.getAddress());
colAvailableBalance.addRow(a.getBalance());
colConfirmations.addRow(a.getNumConfirmations());
colIsUsed.addRow(!a.getIsAddressUnused());
});
// Define and return the table instance with populated columns.
return new Table(colAddress,
colAvailableBalance.asStringColumn(),
colConfirmations.asStringColumn(),
colIsUsed.asStringColumn());
}
}

View File

@ -0,0 +1,78 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.cli.table.builder;
import bisq.proto.grpc.BsqBalanceInfo;
import java.util.List;
import static bisq.cli.table.builder.TableBuilderConstants.*;
import static bisq.cli.table.builder.TableType.BSQ_BALANCE_TBL;
import bisq.cli.table.Table;
import bisq.cli.table.column.Column;
import bisq.cli.table.column.SatoshiColumn;
/**
* Builds a {@code bisq.cli.table.Table} from a
* {@code bisq.proto.grpc.BsqBalanceInfo} object.
*/
class BsqBalanceTableBuilder extends AbstractTableBuilder {
// Default columns not dynamically generated with bsq balance info.
private final Column<Long> colAvailableConfirmedBalance;
private final Column<Long> colUnverifiedBalance;
private final Column<Long> colUnconfirmedChangeBalance;
private final Column<Long> colLockedForVotingBalance;
private final Column<Long> colLockupBondsBalance;
private final Column<Long> colUnlockingBondsBalance;
BsqBalanceTableBuilder(List<?> protos) {
super(BSQ_BALANCE_TBL, protos);
this.colAvailableConfirmedBalance = new SatoshiColumn(COL_HEADER_AVAILABLE_CONFIRMED_BALANCE, true);
this.colUnverifiedBalance = new SatoshiColumn(COL_HEADER_UNVERIFIED_BALANCE, true);
this.colUnconfirmedChangeBalance = new SatoshiColumn(COL_HEADER_UNCONFIRMED_CHANGE_BALANCE, true);
this.colLockedForVotingBalance = new SatoshiColumn(COL_HEADER_LOCKED_FOR_VOTING_BALANCE, true);
this.colLockupBondsBalance = new SatoshiColumn(COL_HEADER_LOCKUP_BONDS_BALANCE, true);
this.colUnlockingBondsBalance = new SatoshiColumn(COL_HEADER_UNLOCKING_BONDS_BALANCE, true);
}
public Table build() {
BsqBalanceInfo balance = (BsqBalanceInfo) protos.get(0);
// Populate columns with bsq balance info.
colAvailableConfirmedBalance.addRow(balance.getAvailableConfirmedBalance());
colUnverifiedBalance.addRow(balance.getUnverifiedBalance());
colUnconfirmedChangeBalance.addRow(balance.getUnconfirmedChangeBalance());
colLockedForVotingBalance.addRow(balance.getLockedForVotingBalance());
colLockupBondsBalance.addRow(balance.getLockupBondsBalance());
colUnlockingBondsBalance.addRow(balance.getUnlockingBondsBalance());
// Define and return the table instance with populated columns.
return new Table(colAvailableConfirmedBalance.asStringColumn(),
colUnverifiedBalance.asStringColumn(),
colUnconfirmedChangeBalance.asStringColumn(),
colLockedForVotingBalance.asStringColumn(),
colLockupBondsBalance.asStringColumn(),
colUnlockingBondsBalance.asStringColumn());
}
}

View File

@ -0,0 +1,73 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.cli.table.builder;
import bisq.proto.grpc.BtcBalanceInfo;
import java.util.List;
import static bisq.cli.table.builder.TableBuilderConstants.COL_HEADER_AVAILABLE_BALANCE;
import static bisq.cli.table.builder.TableBuilderConstants.COL_HEADER_LOCKED_BALANCE;
import static bisq.cli.table.builder.TableBuilderConstants.COL_HEADER_RESERVED_BALANCE;
import static bisq.cli.table.builder.TableBuilderConstants.COL_HEADER_TOTAL_AVAILABLE_BALANCE;
import static bisq.cli.table.builder.TableType.BTC_BALANCE_TBL;
import bisq.cli.table.Table;
import bisq.cli.table.column.Column;
import bisq.cli.table.column.SatoshiColumn;
/**
* Builds a {@code bisq.cli.table.Table} from a
* {@code bisq.proto.grpc.BtcBalanceInfo} object.
*/
class BtcBalanceTableBuilder extends AbstractTableBuilder {
// Default columns not dynamically generated with btc balance info.
private final Column<Long> colAvailableBalance;
private final Column<Long> colReservedBalance;
private final Column<Long> colTotalAvailableBalance;
private final Column<Long> colLockedBalance;
BtcBalanceTableBuilder(List<?> protos) {
super(BTC_BALANCE_TBL, protos);
this.colAvailableBalance = new SatoshiColumn(COL_HEADER_AVAILABLE_BALANCE);
this.colReservedBalance = new SatoshiColumn(COL_HEADER_RESERVED_BALANCE);
this.colTotalAvailableBalance = new SatoshiColumn(COL_HEADER_TOTAL_AVAILABLE_BALANCE);
this.colLockedBalance = new SatoshiColumn(COL_HEADER_LOCKED_BALANCE);
}
public Table build() {
BtcBalanceInfo balance = (BtcBalanceInfo) protos.get(0);
// Populate columns with btc balance info.
colAvailableBalance.addRow(balance.getAvailableBalance());
colReservedBalance.addRow(balance.getReservedBalance());
colTotalAvailableBalance.addRow(balance.getTotalAvailableBalance());
colLockedBalance.addRow(balance.getLockedBalance());
// Define and return the table instance with populated columns.
return new Table(colAvailableBalance.asStringColumn(),
colReservedBalance.asStringColumn(),
colTotalAvailableBalance.asStringColumn(),
colLockedBalance.asStringColumn());
}
}

View File

@ -0,0 +1,49 @@
package bisq.cli.table.builder;
import java.util.List;
import static bisq.cli.table.builder.TableType.FAILED_TRADE_TBL;
import bisq.cli.table.Table;
import bisq.cli.table.column.MixedPriceColumn;
/**
* Builds a {@code bisq.cli.table.Table} from a list of {@code bisq.proto.grpc.TradeInfo} objects.
*/
class FailedTradeTableBuilder extends AbstractTradeListBuilder {
FailedTradeTableBuilder(List<?> protos) {
super(FAILED_TRADE_TBL, protos);
}
public Table build() {
populateColumns();
return new Table(colTradeId,
colCreateDate.asStringColumn(),
colMarket,
colPrice.asStringColumn(),
colAmountInBtc.asStringColumn(),
colMixedAmount.asStringColumn(),
colCurrency,
colOfferType,
colRole,
colStatusDescription);
}
private void populateColumns() {
trades.stream().forEachOrdered(t -> {
colTradeId.addRow(t.getTradeId());
colCreateDate.addRow(t.getDate());
colMarket.addRow(toMarket.apply(t));
((MixedPriceColumn) colPrice).addRow(t.getTradePrice(), isFiatTrade.test(t));
colAmountInBtc.addRow(t.getTradeAmountAsLong());
colMixedAmount.addRow(t.getTradeVolume(), toDisplayedVolumePrecision.apply(t));
colCurrency.addRow(toPaymentCurrencyCode.apply(t));
colOfferType.addRow(toOfferType.apply(t));
colRole.addRow(t.getRole());
colStatusDescription.addRow("Failed");
});
}
}

View File

@ -0,0 +1,280 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.cli.table.builder;
import bisq.proto.grpc.OfferInfo;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import static bisq.cli.table.builder.TableBuilderConstants.*;
import static bisq.cli.table.builder.TableType.OFFER_TBL;
import static bisq.cli.table.column.AltcoinColumn.DISPLAY_MODE.ALTCOIN_OFFER_VOLUME;
import static bisq.cli.table.column.AltcoinColumn.DISPLAY_MODE.ALTCOIN_TRIGGER_PRICE;
import static bisq.cli.table.column.Column.JUSTIFICATION.LEFT;
import static bisq.cli.table.column.Column.JUSTIFICATION.NONE;
import static bisq.cli.table.column.Column.JUSTIFICATION.RIGHT;
import static bisq.cli.table.column.FiatColumn.DISPLAY_MODE.TRIGGER_PRICE;
import static bisq.cli.table.column.FiatColumn.DISPLAY_MODE.VOLUME;
import static bisq.cli.table.column.ZippedStringColumns.DUPLICATION_MODE.EXCLUDE_DUPLICATES;
import static java.lang.String.format;
import static protobuf.OfferDirection.BUY;
import static protobuf.OfferDirection.SELL;
import bisq.cli.table.Table;
import bisq.cli.table.column.AltcoinColumn;
import bisq.cli.table.column.Column;
import bisq.cli.table.column.FiatColumn;
import bisq.cli.table.column.Iso8601DateTimeColumn;
import bisq.cli.table.column.SatoshiColumn;
import bisq.cli.table.column.StringColumn;
import bisq.cli.table.column.ZippedStringColumns;
/**
* Builds a {@code bisq.cli.table.Table} from a List of
* {@code bisq.proto.grpc.OfferInfo} objects.
*/
class OfferTableBuilder extends AbstractTableBuilder {
// Columns common to both fiat and cryptocurrency offers.
private final Column<String> colOfferId = new StringColumn(COL_HEADER_UUID, LEFT);
private final Column<String> colDirection = new StringColumn(COL_HEADER_DIRECTION, LEFT);
private final Column<Long> colAmount = new SatoshiColumn("Temp Amount", NONE);
private final Column<Long> colMinAmount = new SatoshiColumn("Temp Min Amount", NONE);
private final Column<String> colPaymentMethod = new StringColumn(COL_HEADER_PAYMENT_METHOD, LEFT);
private final Column<Long> colCreateDate = new Iso8601DateTimeColumn(COL_HEADER_CREATION_DATE);
OfferTableBuilder(List<?> protos) {
super(OFFER_TBL, protos);
}
public Table build() {
List<OfferInfo> offers = protos.stream().map(p -> (OfferInfo) p).collect(Collectors.toList());
return isShowingFiatOffers.get()
? buildFiatOfferTable(offers)
: buildCryptoCurrencyOfferTable(offers);
}
@SuppressWarnings("ConstantConditions")
public Table buildFiatOfferTable(List<OfferInfo> offers) {
@Nullable
Column<String> colEnabled = enabledColumn.get(); // Not boolean: YES, NO, or PENDING
Column<Long> colFiatPrice = new FiatColumn(format(COL_HEADER_DETAILED_PRICE, fiatTradeCurrency.get()));
Column<Long> colFiatVolume = new FiatColumn(format("Temp Volume (%s)", fiatTradeCurrency.get()), NONE, VOLUME);
Column<Long> colMinFiatVolume = new FiatColumn(format("Temp Min Volume (%s)", fiatTradeCurrency.get()), NONE, VOLUME);
@Nullable
Column<Long> colTriggerPrice = fiatTriggerPriceColumn.get();
// Populate columns with offer info.
//noinspection SimplifyStreamApiCallChains
offers.stream().forEachOrdered(o -> {
if (colEnabled != null)
colEnabled.addRow(toEnabled.apply(o));
colDirection.addRow(o.getDirection());
colFiatPrice.addRow(o.getPrice());
colMinAmount.addRow(o.getMinAmount());
colAmount.addRow(o.getAmount());
colMinFiatVolume.addRow(o.getMinVolume());
colFiatVolume.addRow(o.getVolume());
if (colTriggerPrice != null)
colTriggerPrice.addRow(o.getTriggerPrice());
colPaymentMethod.addRow(o.getPaymentMethodShortName());
colCreateDate.addRow(o.getDate());
colOfferId.addRow(o.getId());
});
ZippedStringColumns amountRange = zippedAmountRangeColumns.get();
ZippedStringColumns volumeRange =
new ZippedStringColumns(format(COL_HEADER_VOLUME_RANGE, fiatTradeCurrency.get()),
RIGHT,
" - ",
colMinFiatVolume.asStringColumn(),
colFiatVolume.asStringColumn());
// Define and return the table instance with populated columns.
if (isShowingMyOffers.get()) {
return new Table(colEnabled.asStringColumn(),
colDirection,
colFiatPrice.asStringColumn(),
amountRange.asStringColumn(EXCLUDE_DUPLICATES),
volumeRange.asStringColumn(EXCLUDE_DUPLICATES),
colTriggerPrice.asStringColumn(),
colPaymentMethod,
colCreateDate.asStringColumn(),
colOfferId);
} else {
return new Table(colDirection,
colFiatPrice.asStringColumn(),
amountRange.asStringColumn(EXCLUDE_DUPLICATES),
volumeRange.asStringColumn(EXCLUDE_DUPLICATES),
colPaymentMethod,
colCreateDate.asStringColumn(),
colOfferId);
}
}
@SuppressWarnings("ConstantConditions")
public Table buildCryptoCurrencyOfferTable(List<OfferInfo> offers) {
@Nullable
Column<String> colEnabled = enabledColumn.get(); // Not boolean: YES, NO, or PENDING
Column<Long> colBtcPrice = new SatoshiColumn(format(COL_HEADER_DETAILED_PRICE_OF_ALTCOIN, altcoinTradeCurrency.get()));
Column<Long> colBtcVolume = new AltcoinColumn(format("Temp Volume (%s)", altcoinTradeCurrency.get()),
NONE,
ALTCOIN_OFFER_VOLUME);
Column<Long> colMinBtcVolume = new AltcoinColumn(format("Temp Min Volume (%s)", altcoinTradeCurrency.get()),
NONE,
ALTCOIN_OFFER_VOLUME);
@Nullable
Column<Long> colTriggerPrice = altcoinTriggerPriceColumn.get();
// Populate columns with offer info.
//noinspection SimplifyStreamApiCallChains
offers.stream().forEachOrdered(o -> {
if (colEnabled != null)
colEnabled.addRow(toEnabled.apply(o));
colDirection.addRow(directionFormat.apply(o));
colBtcPrice.addRow(o.getPrice());
colAmount.addRow(o.getAmount());
colMinAmount.addRow(o.getMinAmount());
colBtcVolume.addRow(o.getMinVolume());
colMinBtcVolume.addRow(o.getVolume());
if (colTriggerPrice != null)
colTriggerPrice.addRow(o.getTriggerPrice());
colPaymentMethod.addRow(o.getPaymentMethodShortName());
colCreateDate.addRow(o.getDate());
colOfferId.addRow(o.getId());
});
ZippedStringColumns amountRange = zippedAmountRangeColumns.get();
ZippedStringColumns volumeRange =
new ZippedStringColumns(format(COL_HEADER_VOLUME_RANGE, altcoinTradeCurrency.get()),
RIGHT,
" - ",
colBtcVolume.asStringColumn(),
colMinBtcVolume.asStringColumn());
// Define and return the table instance with populated columns.
if (isShowingMyOffers.get()) {
if (isShowingBsqOffers.get()) {
return new Table(colEnabled.asStringColumn(),
colDirection,
colBtcPrice.asStringColumn(),
amountRange.asStringColumn(EXCLUDE_DUPLICATES),
volumeRange.asStringColumn(EXCLUDE_DUPLICATES),
colPaymentMethod,
colCreateDate.asStringColumn(),
colOfferId);
} else {
return new Table(colEnabled.asStringColumn(),
colDirection,
colBtcPrice.asStringColumn(),
amountRange.asStringColumn(EXCLUDE_DUPLICATES),
volumeRange.asStringColumn(EXCLUDE_DUPLICATES),
colTriggerPrice.asStringColumn(),
colPaymentMethod,
colCreateDate.asStringColumn(),
colOfferId);
}
} else {
return new Table(colDirection,
colBtcPrice.asStringColumn(),
amountRange.asStringColumn(EXCLUDE_DUPLICATES),
volumeRange.asStringColumn(EXCLUDE_DUPLICATES),
colPaymentMethod,
colCreateDate.asStringColumn(),
colOfferId);
}
}
private final Supplier<OfferInfo> firstOfferInList = () -> (OfferInfo) protos.get(0);
private final Supplier<Boolean> isShowingMyOffers = () -> firstOfferInList.get().getIsMyOffer();
private final Supplier<Boolean> isShowingFiatOffers = () -> isFiatOffer.test(firstOfferInList.get());
private final Supplier<String> fiatTradeCurrency = () -> firstOfferInList.get().getCounterCurrencyCode();
private final Supplier<String> altcoinTradeCurrency = () -> firstOfferInList.get().getBaseCurrencyCode();
private final Supplier<Boolean> isShowingBsqOffers = () ->
!isFiatOffer.test(firstOfferInList.get()) && altcoinTradeCurrency.get().equals("BSQ");
@Nullable // Not a boolean column: YES, NO, or PENDING.
private final Supplier<StringColumn> enabledColumn = () ->
isShowingMyOffers.get()
? new StringColumn(COL_HEADER_ENABLED, LEFT)
: null;
@Nullable
private final Supplier<FiatColumn> fiatTriggerPriceColumn = () ->
isShowingMyOffers.get()
? new FiatColumn(format(COL_HEADER_TRIGGER_PRICE, fiatTradeCurrency.get()), RIGHT, TRIGGER_PRICE)
: null;
@Nullable
private final Supplier<AltcoinColumn> altcoinTriggerPriceColumn = () ->
isShowingMyOffers.get() && !isShowingBsqOffers.get()
? new AltcoinColumn(format(COL_HEADER_TRIGGER_PRICE, altcoinTradeCurrency.get()), RIGHT, ALTCOIN_TRIGGER_PRICE)
: null;
private final Function<OfferInfo, String> toEnabled = (o) -> {
if (o.getIsMyOffer() && o.getIsMyPendingOffer())
return "PENDING";
else
return o.getIsActivated() ? "YES" : "NO";
};
private final Function<String, String> toMirroredDirection = (d) ->
d.equalsIgnoreCase(BUY.name()) ? SELL.name() : BUY.name();
private final Function<OfferInfo, String> directionFormat = (o) -> {
if (isFiatOffer.test(o)) {
return o.getBaseCurrencyCode();
} else {
// Return "Sell BSQ (Buy BTC)", or "Buy BSQ (Sell BTC)".
String direction = o.getDirection();
String mirroredDirection = toMirroredDirection.apply(direction);
Function<String, String> mixedCase = (word) -> word.charAt(0) + word.substring(1).toLowerCase();
return format("%s %s (%s %s)",
mixedCase.apply(mirroredDirection),
o.getBaseCurrencyCode(),
mixedCase.apply(direction),
o.getCounterCurrencyCode());
}
};
private final Supplier<ZippedStringColumns> zippedAmountRangeColumns = () -> {
if (colMinAmount.isEmpty() || colAmount.isEmpty())
throw new IllegalStateException("amount columns must have data");
return new ZippedStringColumns(COL_HEADER_AMOUNT_RANGE,
RIGHT,
" - ",
colMinAmount.asStringColumn(),
colAmount.asStringColumn());
};
}

View File

@ -0,0 +1,47 @@
package bisq.cli.table.builder;
import java.util.List;
import static bisq.cli.table.builder.TableType.OPEN_TRADE_TBL;
import bisq.cli.table.Table;
import bisq.cli.table.column.MixedPriceColumn;
/**
* Builds a {@code bisq.cli.table.Table} from a list of {@code bisq.proto.grpc.TradeInfo} objects.
*/
class OpenTradeTableBuilder extends AbstractTradeListBuilder {
OpenTradeTableBuilder(List<?> protos) {
super(OPEN_TRADE_TBL, protos);
}
public Table build() {
populateColumns();
return new Table(colTradeId,
colCreateDate.asStringColumn(),
colMarket,
colPrice.asStringColumn(),
colAmountInBtc.asStringColumn(),
colMixedAmount.asStringColumn(),
colCurrency,
colPaymentMethod,
colRole);
}
private void populateColumns() {
trades.stream().forEachOrdered(t -> {
colTradeId.addRow(t.getTradeId());
colCreateDate.addRow(t.getDate());
colMarket.addRow(toMarket.apply(t));
((MixedPriceColumn) colPrice).addRow(t.getTradePrice(), isFiatTrade.test(t));
colAmountInBtc.addRow(t.getTradeAmountAsLong());
colMixedAmount.addRow(t.getTradeVolume(), toDisplayedVolumePrecision.apply(t));
colCurrency.addRow(toPaymentCurrencyCode.apply(t));
colPaymentMethod.addRow(t.getOffer().getPaymentMethodShortName());
colRole.addRow(t.getRole());
});
}
}

View File

@ -0,0 +1,74 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.cli.table.builder;
import protobuf.PaymentAccount;
import java.util.List;
import java.util.stream.Collectors;
import static bisq.cli.table.builder.TableBuilderConstants.COL_HEADER_CURRENCY;
import static bisq.cli.table.builder.TableBuilderConstants.COL_HEADER_NAME;
import static bisq.cli.table.builder.TableBuilderConstants.COL_HEADER_PAYMENT_METHOD;
import static bisq.cli.table.builder.TableBuilderConstants.COL_HEADER_UUID;
import static bisq.cli.table.builder.TableType.PAYMENT_ACCOUNT_TBL;
import bisq.cli.table.Table;
import bisq.cli.table.column.Column;
import bisq.cli.table.column.StringColumn;
/**
* Builds a {@code bisq.cli.table.Table} from a List of
* {@code protobuf.PaymentAccount} objects.
*/
class PaymentAccountTableBuilder extends AbstractTableBuilder {
// Default columns not dynamically generated with payment account info.
private final Column<String> colName;
private final Column<String> colCurrency;
private final Column<String> colPaymentMethod;
private final Column<String> colId;
PaymentAccountTableBuilder(List<?> protos) {
super(PAYMENT_ACCOUNT_TBL, protos);
this.colName = new StringColumn(COL_HEADER_NAME);
this.colCurrency = new StringColumn(COL_HEADER_CURRENCY);
this.colPaymentMethod = new StringColumn(COL_HEADER_PAYMENT_METHOD);
this.colId = new StringColumn(COL_HEADER_UUID);
}
public Table build() {
List<PaymentAccount> paymentAccounts = protos.stream()
.map(a -> (PaymentAccount) a)
.collect(Collectors.toList());
// Populate columns with payment account info.
//noinspection SimplifyStreamApiCallChains
paymentAccounts.stream().forEachOrdered(a -> {
colName.addRow(a.getAccountName());
colCurrency.addRow(a.getSelectedTradeCurrency().getCode());
colPaymentMethod.addRow(a.getPaymentMethod().getId());
colId.addRow(a.getId());
});
// Define and return the table instance with populated columns.
return new Table(colName, colCurrency, colPaymentMethod, colId);
}
}

View File

@ -0,0 +1,70 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.cli.table.builder;
import java.util.List;
import static java.util.Collections.singletonList;
import bisq.cli.table.Table;
/**
* Table builder factory. It is not conventionally named TableBuilderFactory because
* it has no static factory methods. The number of static fields and methods in the
* {@code bisq.cli.table} are kept to a minimum in an effort o reduce class load time
* in the session-less CLI.
*/
public class TableBuilder extends AbstractTableBuilder {
public TableBuilder(TableType tableType, Object proto) {
this(tableType, singletonList(proto));
}
public TableBuilder(TableType tableType, List<?> protos) {
super(tableType, protos);
}
public Table build() {
switch (tableType) {
case ADDRESS_BALANCE_TBL:
return new AddressBalanceTableBuilder(protos).build();
case BSQ_BALANCE_TBL:
return new BsqBalanceTableBuilder(protos).build();
case BTC_BALANCE_TBL:
return new BtcBalanceTableBuilder(protos).build();
case CLOSED_TRADE_TBL:
throw new UnsupportedOperationException("TODO return new ClosedTradeTableBuilder(protos).build()");
case FAILED_TRADE_TBL:
throw new UnsupportedOperationException("TODO return new FailedTradeTableBuilder(protos).build()");
case OFFER_TBL:
return new OfferTableBuilder(protos).build();
case OPEN_TRADE_TBL:
throw new UnsupportedOperationException("TODO return new OpenTradeTableBuilder(protos).build()");
case PAYMENT_ACCOUNT_TBL:
return new PaymentAccountTableBuilder(protos).build();
case TRADE_DETAIL_TBL:
return new TradeDetailTableBuilder(protos).build();
case TRANSACTION_TBL:
return new TransactionTableBuilder(protos).build();
default:
throw new IllegalArgumentException("invalid cli table type " + tableType.name());
}
}
}

View File

@ -0,0 +1,83 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.cli.table.builder;
/**
* Table column name constants.
*/
class TableBuilderConstants {
static final String COL_HEADER_ADDRESS = "%-3s Address";
static final String COL_HEADER_AMOUNT = "Amount";
static final String COL_HEADER_AMOUNT_IN_BTC = "Amount in BTC";
static final String COL_HEADER_AMOUNT_RANGE = "BTC(min - max)";
static final String COL_HEADER_AVAILABLE_BALANCE = "Available Balance";
static final String COL_HEADER_AVAILABLE_CONFIRMED_BALANCE = "Available Confirmed Balance";
static final String COL_HEADER_UNCONFIRMED_CHANGE_BALANCE = "Unconfirmed Change Balance";
static final String COL_HEADER_RESERVED_BALANCE = "Reserved Balance";
static final String COL_HEADER_TOTAL_AVAILABLE_BALANCE = "Total Available Balance";
static final String COL_HEADER_LOCKED_BALANCE = "Locked Balance";
static final String COL_HEADER_LOCKED_FOR_VOTING_BALANCE = "Locked For Voting Balance";
static final String COL_HEADER_LOCKUP_BONDS_BALANCE = "Lockup Bonds Balance";
static final String COL_HEADER_UNLOCKING_BONDS_BALANCE = "Unlocking Bonds Balance";
static final String COL_HEADER_UNVERIFIED_BALANCE = "Unverified Balance";
static final String COL_HEADER_BUYER_DEPOSIT = "Buyer Deposit";
static final String COL_HEADER_SELLER_DEPOSIT = "Seller Deposit";
static final String COL_HEADER_CONFIRMATIONS = "Confirmations";
static final String COL_HEADER_DEVIATION = "Deviation";
static final String COL_HEADER_IS_USED_ADDRESS = "Is Used";
static final String COL_HEADER_CREATION_DATE = "Creation Date (UTC)";
static final String COL_HEADER_CURRENCY = "Currency";
static final String COL_HEADER_DATE_TIME = "Date/Time (UTC)";
static final String COL_HEADER_DETAILED_AMOUNT = "Amount(%-3s)";
static final String COL_HEADER_DETAILED_PRICE = "Price in %-3s for 1 BTC";
static final String COL_HEADER_DETAILED_PRICE_OF_ALTCOIN = "Price in BTC for 1 %-3s";
static final String COL_HEADER_DIRECTION = "Buy/Sell";
static final String COL_HEADER_ENABLED = "Enabled";
static final String COL_HEADER_MARKET = "Market";
static final String COL_HEADER_NAME = "Name";
static final String COL_HEADER_OFFER_TYPE = "Offer Type";
static final String COL_HEADER_PAYMENT_METHOD = "Payment Method";
static final String COL_HEADER_PRICE = "Price";
static final String COL_HEADER_STATUS = "Status";
static final String COL_HEADER_TRADE_ALTCOIN_BUYER_ADDRESS = "%-3s Buyer Address";
static final String COL_HEADER_TRADE_BUYER_COST = "Buyer Cost(%-3s)";
static final String COL_HEADER_TRADE_DEPOSIT_CONFIRMED = "Deposit Confirmed";
static final String COL_HEADER_TRADE_DEPOSIT_PUBLISHED = "Deposit Published";
static final String COL_HEADER_TRADE_PAYMENT_SENT = "%-3s Sent";
static final String COL_HEADER_TRADE_PAYMENT_RECEIVED = "%-3s Received";
static final String COL_HEADER_TRADE_PAYOUT_PUBLISHED = "Payout Published";
static final String COL_HEADER_TRADE_WITHDRAWN = "Withdrawn";
static final String COL_HEADER_TRADE_ID = "Trade ID";
static final String COL_HEADER_TRADE_ROLE = "My Role";
static final String COL_HEADER_TRADE_SHORT_ID = "ID";
@Deprecated
static final String COL_HEADER_TRADE_TX_FEE = "Tx Fee(BTC)";
static final String COL_HEADER_TRADE_MAKER_FEE = "Maker Fee(%-3s)";
static final String COL_HEADER_TRADE_TAKER_FEE = "Taker Fee(%-3s)";
static final String COL_HEADER_TRADE_FEE = "Trade Fee";
static final String COL_HEADER_TRIGGER_PRICE = "Trigger Price(%-3s)";
static final String COL_HEADER_TX_ID = "Tx ID";
static final String COL_HEADER_TX_INPUT_SUM = "Tx Inputs (BTC)";
static final String COL_HEADER_TX_OUTPUT_SUM = "Tx Outputs (BTC)";
static final String COL_HEADER_TX_FEE = "Tx Fee (BTC)";
static final String COL_HEADER_TX_SIZE = "Tx Size (Bytes)";
static final String COL_HEADER_TX_IS_CONFIRMED = "Is Confirmed";
static final String COL_HEADER_TX_MEMO = "Memo";
static final String COL_HEADER_VOLUME_RANGE = "%-3s(min - max)";
static final String COL_HEADER_UUID = "ID";
}

View File

@ -0,0 +1,35 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.cli.table.builder;
/**
* Used as param in TableBuilder constructor instead of inspecting
* protos to find out what kind of CLI output table should be built.
*/
public enum TableType {
ADDRESS_BALANCE_TBL,
BSQ_BALANCE_TBL,
BTC_BALANCE_TBL,
CLOSED_TRADE_TBL,
FAILED_TRADE_TBL,
OFFER_TBL,
OPEN_TRADE_TBL,
PAYMENT_ACCOUNT_TBL,
TRADE_DETAIL_TBL,
TRANSACTION_TBL
}

View File

@ -0,0 +1,92 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.cli.table.builder;
import java.util.ArrayList;
import java.util.List;
import static bisq.cli.table.builder.TableType.TRADE_DETAIL_TBL;
import bisq.cli.table.Table;
import bisq.cli.table.column.Column;
/**
* Builds a {@code bisq.cli.table.Table} from a {@code bisq.proto.grpc.TradeInfo} object.
*/
class TradeDetailTableBuilder extends AbstractTradeListBuilder {
TradeDetailTableBuilder(List<?> protos) {
super(TRADE_DETAIL_TBL, protos);
}
/**
* Build a single row trade detail table.
* @return Table containing one row
*/
public Table build() {
populateColumns();
List<Column<?>> columns = defineColumnList();
return new Table(columns.toArray(new Column<?>[0]));
}
private void populateColumns() {
trades.stream().forEachOrdered(t -> {
colTradeId.addRow(t.getShortId());
colRole.addRow(t.getRole());
colPrice.addRow(t.getTradePrice());
colAmountInBtc.addRow(toAmount.apply(t));
colMinerTxFee.addRow(toMyMinerTxFee.apply(t));
colBisqTradeFee.addRow(toMyMakerOrTakerFee.apply(t));
colIsDepositPublished.addRow(t.getIsDepositPublished());
colIsDepositConfirmed.addRow(t.getIsDepositConfirmed());
colTradeCost.addRow(toTradeVolume.apply(t));
colIsPaymentSent.addRow(t.getIsFiatSent());
colIsPaymentReceived.addRow(t.getIsFiatReceived());
colIsPayoutPublished.addRow(t.getIsPayoutPublished());
colIsFundsWithdrawn.addRow(t.getIsWithdrawn());
if (colAltcoinReceiveAddressColumn != null)
colAltcoinReceiveAddressColumn.addRow(toAltcoinReceiveAddress.apply(t));
});
}
private List<Column<?>> defineColumnList() {
List<Column<?>> columns = new ArrayList<>() {{
add(colTradeId);
add(colRole);
add(colPrice.asStringColumn());
add(colAmountInBtc.asStringColumn());
add(colMinerTxFee.asStringColumn());
add(colBisqTradeFee.asStringColumn());
add(colIsDepositPublished.asStringColumn());
add(colIsDepositConfirmed.asStringColumn());
add(colTradeCost.asStringColumn());
add(colIsPaymentSent.asStringColumn());
add(colIsPaymentReceived.asStringColumn());
add(colIsPayoutPublished.asStringColumn());
add(colIsFundsWithdrawn.asStringColumn());
}};
if (colAltcoinReceiveAddressColumn != null)
columns.add(colAltcoinReceiveAddressColumn);
return columns;
}
}

View File

@ -0,0 +1,263 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.cli.table.builder;
import bisq.proto.grpc.ContractInfo;
import bisq.proto.grpc.OfferInfo;
import bisq.proto.grpc.TradeInfo;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import static bisq.cli.table.builder.TableBuilderConstants.*;
import static bisq.cli.table.builder.TableType.CLOSED_TRADE_TBL;
import static bisq.cli.table.builder.TableType.FAILED_TRADE_TBL;
import static bisq.cli.table.builder.TableType.OPEN_TRADE_TBL;
import static bisq.cli.table.builder.TableType.TRADE_DETAIL_TBL;
import static bisq.cli.table.column.AltcoinColumn.DISPLAY_MODE.ALTCOIN_OFFER_VOLUME;
import static bisq.cli.table.column.Column.JUSTIFICATION.LEFT;
import static bisq.cli.table.column.Column.JUSTIFICATION.RIGHT;
import static bisq.cli.table.column.FiatColumn.DISPLAY_MODE.VOLUME;
import bisq.cli.table.column.AltcoinColumn;
import bisq.cli.table.column.BooleanColumn;
import bisq.cli.table.column.BtcColumn;
import bisq.cli.table.column.Column;
import bisq.cli.table.column.FiatColumn;
import bisq.cli.table.column.Iso8601DateTimeColumn;
import bisq.cli.table.column.MixedPriceColumn;
import bisq.cli.table.column.MixedTradeFeeColumn;
import bisq.cli.table.column.MixedVolumeColumn;
import bisq.cli.table.column.SatoshiColumn;
import bisq.cli.table.column.StringColumn;
/**
* Convenience for supplying column definitions to
* open/closed/failed/detail trade table builders.
*/
@Slf4j
class TradeTableColumnSupplier {
@Getter
private final TableType tableType;
@Getter
private final List<TradeInfo> trades;
public TradeTableColumnSupplier(TableType tableType, List<TradeInfo> trades) {
this.tableType = tableType;
this.trades = trades;
}
private final Supplier<Boolean> isTradeDetailTblBuilder = () -> getTableType().equals(TRADE_DETAIL_TBL);
private final Supplier<Boolean> isOpenTradeTblBuilder = () -> getTableType().equals(OPEN_TRADE_TBL);
private final Supplier<Boolean> isClosedTradeTblBuilder = () -> getTableType().equals(CLOSED_TRADE_TBL);
private final Supplier<Boolean> isFailedTradeTblBuilder = () -> getTableType().equals(FAILED_TRADE_TBL);
private final Supplier<TradeInfo> firstRow = () -> getTrades().get(0);
private final Predicate<OfferInfo> isFiatOffer = (o) -> o.getBaseCurrencyCode().equals("BTC");
private final Predicate<TradeInfo> isFiatTrade = (t) -> isFiatOffer.test(t.getOffer());
private final Predicate<TradeInfo> isTaker = (t) -> t.getRole().toLowerCase().contains("taker");
final Supplier<StringColumn> tradeIdColumn = () -> isTradeDetailTblBuilder.get()
? new StringColumn(COL_HEADER_TRADE_SHORT_ID)
: new StringColumn(COL_HEADER_TRADE_ID);
final Supplier<Iso8601DateTimeColumn> createDateColumn = () -> isTradeDetailTblBuilder.get()
? null
: new Iso8601DateTimeColumn(COL_HEADER_DATE_TIME);
final Supplier<StringColumn> marketColumn = () -> isTradeDetailTblBuilder.get()
? null
: new StringColumn(COL_HEADER_MARKET);
private final Function<TradeInfo, Column<Long>> toDetailedPriceColumn = (t) -> {
String colHeader = isFiatTrade.test(t)
? String.format(COL_HEADER_DETAILED_PRICE, t.getOffer().getCounterCurrencyCode())
: String.format(COL_HEADER_DETAILED_PRICE_OF_ALTCOIN, t.getOffer().getBaseCurrencyCode());
return isFiatTrade.test(t)
? new FiatColumn(colHeader)
: new AltcoinColumn(colHeader);
};
final Supplier<Column<Long>> priceColumn = () -> isTradeDetailTblBuilder.get()
? toDetailedPriceColumn.apply(firstRow.get())
: new MixedPriceColumn(COL_HEADER_PRICE);
final Supplier<Column<String>> priceDeviationColumn = () -> isTradeDetailTblBuilder.get()
? null
: new StringColumn(COL_HEADER_DEVIATION, RIGHT);
final Supplier<StringColumn> currencyColumn = () -> isTradeDetailTblBuilder.get()
? null
: new StringColumn(COL_HEADER_CURRENCY);
private final Function<TradeInfo, Column<Long>> toDetailedAmountColumn = (t) -> {
String headerCurrencyCode = t.getOffer().getBaseCurrencyCode();
String colHeader = String.format(COL_HEADER_DETAILED_AMOUNT, headerCurrencyCode);
return isFiatTrade.test(t)
? new SatoshiColumn(colHeader)
: new AltcoinColumn(colHeader, ALTCOIN_OFFER_VOLUME);
};
final Supplier<Column<Long>> amountInBtcColumn = () -> isTradeDetailTblBuilder.get()
? toDetailedAmountColumn.apply(firstRow.get())
: new BtcColumn(COL_HEADER_AMOUNT_IN_BTC);
final Supplier<MixedVolumeColumn> mixedAmountColumn = () -> isTradeDetailTblBuilder.get()
? null
: new MixedVolumeColumn(COL_HEADER_AMOUNT);
final Supplier<Column<Long>> minerTxFeeColumn = () -> isTradeDetailTblBuilder.get() || isClosedTradeTblBuilder.get()
? new SatoshiColumn(COL_HEADER_TX_FEE)
: null;
final Supplier<MixedTradeFeeColumn> mixedTradeFeeColumn = () -> isTradeDetailTblBuilder.get()
? null
: new MixedTradeFeeColumn(COL_HEADER_TRADE_FEE);
final Supplier<StringColumn> paymentMethodColumn = () -> isTradeDetailTblBuilder.get() || isClosedTradeTblBuilder.get()
? null
: new StringColumn(COL_HEADER_PAYMENT_METHOD, LEFT);
final Supplier<StringColumn> roleColumn = () ->
isTradeDetailTblBuilder.get() || isOpenTradeTblBuilder.get() || isFailedTradeTblBuilder.get()
? new StringColumn(COL_HEADER_TRADE_ROLE)
: null;
final Function<String, Column<Long>> toSecurityDepositColumn = (name) -> isClosedTradeTblBuilder.get()
? new SatoshiColumn(name)
: null;
final Supplier<StringColumn> offerTypeColumn = () -> isTradeDetailTblBuilder.get()
? null
: new StringColumn(COL_HEADER_OFFER_TYPE);
final Supplier<StringColumn> statusDescriptionColumn = () -> isTradeDetailTblBuilder.get()
? null
: new StringColumn(COL_HEADER_STATUS);
private final Function<String, Column<Boolean>> toBooleanColumn = BooleanColumn::new;
final Supplier<Column<Boolean>> depositPublishedColumn = () -> isTradeDetailTblBuilder.get()
? toBooleanColumn.apply(COL_HEADER_TRADE_DEPOSIT_PUBLISHED)
: null;
final Supplier<Column<Boolean>> depositConfirmedColumn = () -> isTradeDetailTblBuilder.get()
? toBooleanColumn.apply(COL_HEADER_TRADE_DEPOSIT_CONFIRMED)
: null;
final Supplier<Column<Boolean>> payoutPublishedColumn = () -> isTradeDetailTblBuilder.get()
? toBooleanColumn.apply(COL_HEADER_TRADE_PAYOUT_PUBLISHED)
: null;
final Supplier<Column<Boolean>> fundsWithdrawnColumn = () -> isTradeDetailTblBuilder.get()
? toBooleanColumn.apply(COL_HEADER_TRADE_WITHDRAWN)
: null;
final Supplier<Column<Long>> bisqTradeDetailFeeColumn = () -> {
if (isTradeDetailTblBuilder.get()) {
TradeInfo t = firstRow.get();
String headerCurrencyCode = isTaker.test(t)
? t.getIsCurrencyForTakerFeeBtc() ? "BTC" : "BSQ"
: t.getOffer().getIsCurrencyForMakerFeeBtc() ? "BTC" : "BSQ";
String colHeader = isTaker.test(t)
? String.format(COL_HEADER_TRADE_TAKER_FEE, headerCurrencyCode)
: String.format(COL_HEADER_TRADE_MAKER_FEE, headerCurrencyCode);
boolean isBsqSatoshis = headerCurrencyCode.equals("BSQ");
return new SatoshiColumn(colHeader, isBsqSatoshis);
} else {
return null;
}
};
final Function<TradeInfo, String> toPaymentCurrencyCode = (t) ->
isFiatTrade.test(t)
? t.getOffer().getCounterCurrencyCode()
: t.getOffer().getBaseCurrencyCode();
final Supplier<Column<Boolean>> paymentSentColumn = () -> {
if (isTradeDetailTblBuilder.get()) {
String headerCurrencyCode = toPaymentCurrencyCode.apply(firstRow.get());
String colHeader = String.format(COL_HEADER_TRADE_PAYMENT_SENT, headerCurrencyCode);
return new BooleanColumn(colHeader);
} else {
return null;
}
};
final Supplier<Column<Boolean>> paymentReceivedColumn = () -> {
if (isTradeDetailTblBuilder.get()) {
String headerCurrencyCode = toPaymentCurrencyCode.apply(firstRow.get());
String colHeader = String.format(COL_HEADER_TRADE_PAYMENT_RECEIVED, headerCurrencyCode);
return new BooleanColumn(colHeader);
} else {
return null;
}
};
final Supplier<Column<Long>> tradeCostColumn = () -> {
if (isTradeDetailTblBuilder.get()) {
TradeInfo t = firstRow.get();
String headerCurrencyCode = t.getOffer().getCounterCurrencyCode();
String colHeader = String.format(COL_HEADER_TRADE_BUYER_COST, headerCurrencyCode);
return isFiatTrade.test(t)
? new FiatColumn(colHeader, VOLUME)
: new SatoshiColumn(colHeader);
} else {
return null;
}
};
final Predicate<TradeInfo> showAltCoinBuyerAddress = (t) -> {
if (isFiatTrade.test(t)) {
return false;
} else {
ContractInfo contract = t.getContract();
boolean isBuyerMakerAndSellerTaker = contract.getIsBuyerMakerAndSellerTaker();
if (isTaker.test(t)) {
return !isBuyerMakerAndSellerTaker;
} else {
return isBuyerMakerAndSellerTaker;
}
}
};
@Nullable
final Supplier<Column<String>> altcoinReceiveAddressColumn = () -> {
if (isTradeDetailTblBuilder.get()) {
TradeInfo t = firstRow.get();
if (showAltCoinBuyerAddress.test(t)) {
String headerCurrencyCode = toPaymentCurrencyCode.apply(t);
String colHeader = String.format(COL_HEADER_TRADE_ALTCOIN_BUYER_ADDRESS, headerCurrencyCode);
return new StringColumn(colHeader);
} else {
return null;
}
} else {
return null;
}
};
}

View File

@ -0,0 +1,103 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.cli.table.builder;
import bisq.proto.grpc.TxInfo;
import java.util.List;
import javax.annotation.Nullable;
import static bisq.cli.table.builder.TableBuilderConstants.*;
import static bisq.cli.table.builder.TableType.TRANSACTION_TBL;
import bisq.cli.table.Table;
import bisq.cli.table.column.BooleanColumn;
import bisq.cli.table.column.Column;
import bisq.cli.table.column.LongColumn;
import bisq.cli.table.column.SatoshiColumn;
import bisq.cli.table.column.StringColumn;
/**
* Builds a {@code bisq.cli.table.Table} from a {@code bisq.proto.grpc.TxInfo} object.
*/
class TransactionTableBuilder extends AbstractTableBuilder {
// Default columns not dynamically generated with tx info.
private final Column<String> colTxId;
private final Column<Boolean> colIsConfirmed;
private final Column<Long> colInputSum;
private final Column<Long> colOutputSum;
private final Column<Long> colTxFee;
private final Column<Long> colTxSize;
TransactionTableBuilder(List<?> protos) {
super(TRANSACTION_TBL, protos);
this.colTxId = new StringColumn(COL_HEADER_TX_ID);
this.colIsConfirmed = new BooleanColumn(COL_HEADER_TX_IS_CONFIRMED);
this.colInputSum = new SatoshiColumn(COL_HEADER_TX_INPUT_SUM);
this.colOutputSum = new SatoshiColumn(COL_HEADER_TX_OUTPUT_SUM);
this.colTxFee = new SatoshiColumn(COL_HEADER_TX_FEE);
this.colTxSize = new LongColumn(COL_HEADER_TX_SIZE);
}
public Table build() {
// TODO Add 'gettransactions' api method & show multiple tx in the console.
// For now, a tx tbl is only one row.
TxInfo tx = (TxInfo) protos.get(0);
// Declare the columns derived from tx info.
@Nullable
Column<String> colMemo = tx.getMemo().isEmpty()
? null
: new StringColumn(COL_HEADER_TX_MEMO);
// Populate columns with tx info.
colTxId.addRow(tx.getTxId());
colIsConfirmed.addRow(!tx.getIsPending());
colInputSum.addRow(tx.getInputSum());
colOutputSum.addRow(tx.getOutputSum());
colTxFee.addRow(tx.getFee());
colTxSize.addRow((long) tx.getSize());
if (colMemo != null)
colMemo.addRow(tx.getMemo());
// Define and return the table instance with populated columns.
if (colMemo != null) {
return new Table(colTxId,
colIsConfirmed.asStringColumn(),
colInputSum.asStringColumn(),
colOutputSum.asStringColumn(),
colTxFee.asStringColumn(),
colTxSize.asStringColumn(),
colMemo);
} else {
return new Table(colTxId,
colIsConfirmed.asStringColumn(),
colInputSum.asStringColumn(),
colOutputSum.asStringColumn(),
colTxFee.asStringColumn(),
colTxSize.asStringColumn());
}
}
}

View File

@ -0,0 +1,86 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.cli.table.column;
import static bisq.cli.table.column.Column.JUSTIFICATION.RIGHT;
import static com.google.common.base.Strings.padEnd;
import static com.google.common.base.Strings.padStart;
/**
* Partial implementation of the {@link Column} interface.
*/
abstract class AbstractColumn<C extends Column<T>, T> implements Column<T> {
// We create an encapsulated StringColumn up front to populate with formatted
// strings in each this.addRow(Long value) call. But we will not know how
// to justify the cached, formatted string until the column is fully populated.
protected final StringColumn stringColumn;
// The name field is not final, so it can be re-set for column alignment.
protected String name;
protected final JUSTIFICATION justification;
// The max width is not known until after column is fully populated.
protected int maxWidth;
public AbstractColumn(String name, JUSTIFICATION justification) {
this.name = name;
this.justification = justification;
this.stringColumn = this instanceof StringColumn ? null : new StringColumn(name, justification);
}
@Override
public String getName() {
return this.name;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public int getWidth() {
return maxWidth;
}
@Override
public JUSTIFICATION getJustification() {
return this.justification;
}
@Override
public Column<T> justify() {
if (this instanceof StringColumn && this.justification.equals(RIGHT))
return this.justify();
else
return this; // no-op
}
protected final String toJustifiedString(String s) {
switch (justification) {
case LEFT:
return padEnd(s, maxWidth, ' ');
case RIGHT:
return padStart(s, maxWidth, ' ');
case NONE:
default:
return s;
}
}
}

View File

@ -0,0 +1,98 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.cli.table.column;
import java.util.function.BiFunction;
import java.util.stream.IntStream;
import static bisq.cli.CurrencyFormat.formatCryptoCurrencyPrice;
import static bisq.cli.CurrencyFormat.formatCryptoCurrencyVolume;
import static bisq.cli.table.column.AltcoinColumn.DISPLAY_MODE.ALTCOIN_PRICE;
import static bisq.cli.table.column.Column.JUSTIFICATION.RIGHT;
/**
* For displaying altcoin values as volume, price, or optional trigger price
* with appropriate precision.
*/
public class AltcoinColumn extends LongColumn {
public enum DISPLAY_MODE {
ALTCOIN_OFFER_VOLUME,
ALTCOIN_PRICE,
ALTCOIN_TRIGGER_PRICE
}
private final DISPLAY_MODE displayMode;
// The default AltcoinColumn JUSTIFICATION is RIGHT.
// The default AltcoinColumn DISPLAY_MODE is ALTCOIN_PRICE.
public AltcoinColumn(String name) {
this(name, RIGHT, ALTCOIN_PRICE);
}
public AltcoinColumn(String name, DISPLAY_MODE displayMode) {
this(name, RIGHT, displayMode);
}
public AltcoinColumn(String name,
JUSTIFICATION justification,
DISPLAY_MODE displayMode) {
super(name, justification);
this.displayMode = displayMode;
}
@Override
public void addRow(Long value) {
rows.add(value);
String s = toFormattedString.apply(value, displayMode);
stringColumn.addRow(s);
if (isNewMaxWidth.test(s))
maxWidth = s.length();
}
@Override
public String getRowAsFormattedString(int rowIndex) {
return toFormattedString.apply(getRow(rowIndex), displayMode);
}
@Override
public StringColumn asStringColumn() {
// We cached the formatted altcoin value strings, but we did
// not know how much padding each string needed until now.
IntStream.range(0, stringColumn.getRows().size()).forEach(rowIndex -> {
String unjustified = stringColumn.getRow(rowIndex);
String justified = stringColumn.toJustifiedString(unjustified);
stringColumn.updateRow(rowIndex, justified);
});
return this.stringColumn;
}
private final BiFunction<Long, DISPLAY_MODE, String> toFormattedString = (value, displayMode) -> {
switch (displayMode) {
case ALTCOIN_OFFER_VOLUME:
return value > 0 ? formatCryptoCurrencyVolume(value) : "";
case ALTCOIN_PRICE:
case ALTCOIN_TRIGGER_PRICE:
return value > 0 ? formatCryptoCurrencyPrice(value) : "";
default:
throw new IllegalStateException("invalid display mode: " + displayMode);
}
};
}

View File

@ -0,0 +1,131 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.cli.table.column;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.IntStream;
import static bisq.cli.table.column.Column.JUSTIFICATION.LEFT;
/**
* For displaying boolean values as YES, NO, or user's choice for 'true' and 'false'.
*/
public class BooleanColumn extends AbstractColumn<BooleanColumn, Boolean> {
private static final String DEFAULT_TRUE_AS_STRING = "YES";
private static final String DEFAULT_FALSE_AS_STRING = "NO";
private final List<Boolean> rows = new ArrayList<>();
private final Predicate<String> isNewMaxWidth = (s) -> s != null && !s.isEmpty() && s.length() > maxWidth;
private final String trueAsString;
private final String falseAsString;
// The default BooleanColumn JUSTIFICATION is LEFT.
// The default BooleanColumn True AsString value is YES.
// The default BooleanColumn False AsString value is NO.
public BooleanColumn(String name) {
this(name, LEFT, DEFAULT_TRUE_AS_STRING, DEFAULT_FALSE_AS_STRING);
}
// Use this constructor to override default LEFT justification.
@SuppressWarnings("unused")
public BooleanColumn(String name, JUSTIFICATION justification) {
this(name, justification, DEFAULT_TRUE_AS_STRING, DEFAULT_FALSE_AS_STRING);
}
// Use this constructor to override default true/false as string defaults.
public BooleanColumn(String name, String trueAsString, String falseAsString) {
this(name, LEFT, trueAsString, falseAsString);
}
// Use this constructor to override default LEFT justification.
public BooleanColumn(String name,
JUSTIFICATION justification,
String trueAsString,
String falseAsString) {
super(name, justification);
this.trueAsString = trueAsString;
this.falseAsString = falseAsString;
this.maxWidth = name.length();
}
@Override
public void addRow(Boolean value) {
rows.add(value);
// We do not know how much padding each StringColumn value needs until it has all the values.
String s = asString(value);
stringColumn.addRow(s);
if (isNewMaxWidth.test(s))
maxWidth = s.length();
}
@Override
public List<Boolean> getRows() {
return rows;
}
@Override
public int rowCount() {
return rows.size();
}
@Override
public boolean isEmpty() {
return rows.isEmpty();
}
@Override
public Boolean getRow(int rowIndex) {
return rows.get(rowIndex);
}
@Override
public void updateRow(int rowIndex, Boolean newValue) {
rows.set(rowIndex, newValue);
}
@Override
public String getRowAsFormattedString(int rowIndex) {
return getRow(rowIndex)
? trueAsString
: falseAsString;
}
@Override
public StringColumn asStringColumn() {
// We cached the formatted satoshi strings, but we did
// not know how much padding each string needed until now.
IntStream.range(0, stringColumn.getRows().size()).forEach(rowIndex -> {
String unjustified = stringColumn.getRow(rowIndex);
String justified = stringColumn.toJustifiedString(unjustified);
stringColumn.updateRow(rowIndex, justified);
});
return stringColumn;
}
private String asString(boolean value) {
return value ? trueAsString : falseAsString;
}
}

View File

@ -0,0 +1,48 @@
package bisq.cli.table.column;
import java.util.stream.IntStream;
import static bisq.cli.CurrencyFormat.formatBtc;
import static com.google.common.base.Strings.padEnd;
import static java.util.Comparator.comparingInt;
public class BtcColumn extends SatoshiColumn {
public BtcColumn(String name) {
super(name);
}
@Override
public void addRow(Long value) {
rows.add(value);
String s = formatBtc(value);
stringColumn.addRow(s);
if (isNewMaxWidth.test(s))
maxWidth = s.length();
}
@Override
public String getRowAsFormattedString(int rowIndex) {
return formatBtc(getRow(rowIndex));
}
@Override
public StringColumn asStringColumn() {
// We cached the formatted satoshi strings, but we did
// not know how much zero padding each string needed until now.
int maxColumnValueWidth = stringColumn.getRows().stream()
.max(comparingInt(String::length))
.get()
.length();
IntStream.range(0, stringColumn.getRows().size()).forEach(rowIndex -> {
String btcString = stringColumn.getRow(rowIndex);
if (btcString.length() < maxColumnValueWidth) {
String paddedBtcString = padEnd(btcString, maxColumnValueWidth, '0');
stringColumn.updateRow(rowIndex, paddedBtcString);
}
});
return stringColumn.justify();
}
}

View File

@ -0,0 +1,122 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.cli.table.column;
import java.util.List;
public interface Column<T> {
enum JUSTIFICATION {
LEFT,
RIGHT,
NONE
}
/**
* Returns the column's name.
*
* @return name as String
*/
String getName();
/**
* Sets the column name.
*
* @param name of the column
*/
void setName(String name);
/**
* Add column value.
*
* @param value added to column's data (row)
*/
void addRow(T value);
/**
* Returns the column data.
*
* @return rows as List<T>
*/
List<T> getRows();
/**
* Returns the maximum width of the column name, or longest,
* formatted string value -- whichever is greater.
*
* @return width of the populated column as int
*/
int getWidth();
/**
* Returns the number of rows in the column.
*
* @return number of rows in the column as int.
*/
int rowCount();
/**
* Returns true if the column has no data.
*
* @return true if empty, false if not
*/
boolean isEmpty();
/**
* Returns the column value (data) at given row index.
*
* @return value object
*/
T getRow(int rowIndex);
/**
* Update an existing value at the given row index to a new value.
*
* @param rowIndex row index of value to be updated
* @param newValue new value
*/
void updateRow(int rowIndex, T newValue);
/**
* Returns the row value as a formatted String.
*
* @return a row value as formatted String
*/
String getRowAsFormattedString(int rowIndex);
/**
* Return the column with all of its data as a StringColumn with all of its
* formatted string data.
*
* @return StringColumn
*/
StringColumn asStringColumn();
/**
* Convenience for justifying populated StringColumns before being displayed.
* Is only useful for StringColumn instances.
*/
Column<T> justify();
/**
* Returns JUSTIFICATION value (RIGHT|LEFT|NONE) for the column.
*
* @return column JUSTIFICATION
*/
JUSTIFICATION getJustification();
}

View File

@ -0,0 +1,93 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.cli.table.column;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.IntStream;
import static bisq.cli.table.column.Column.JUSTIFICATION.RIGHT;
/**
* For displaying Double values.
*/
public class DoubleColumn extends NumberColumn<DoubleColumn, Double> {
protected final List<Double> rows = new ArrayList<>();
protected final Predicate<String> isNewMaxWidth = (s) -> s != null && !s.isEmpty() && s.length() > maxWidth;
// The default DoubleColumn JUSTIFICATION is RIGHT.
public DoubleColumn(String name) {
this(name, RIGHT);
}
public DoubleColumn(String name, JUSTIFICATION justification) {
super(name, justification);
this.maxWidth = name.length();
}
@Override
public void addRow(Double value) {
rows.add(value);
String s = String.valueOf(value);
if (isNewMaxWidth.test(s))
maxWidth = s.length();
}
@Override
public List<Double> getRows() {
return rows;
}
@Override
public int rowCount() {
return rows.size();
}
@Override
public boolean isEmpty() {
return rows.isEmpty();
}
@Override
public Double getRow(int rowIndex) {
return rows.get(rowIndex);
}
@Override
public void updateRow(int rowIndex, Double newValue) {
rows.set(rowIndex, newValue);
}
@Override
public String getRowAsFormattedString(int rowIndex) {
String s = String.valueOf(getRow(rowIndex));
return toJustifiedString(s);
}
@Override
public StringColumn asStringColumn() {
IntStream.range(0, rows.size()).forEachOrdered(rowIndex ->
stringColumn.addRow(getRowAsFormattedString(rowIndex)));
return stringColumn;
}
}

View File

@ -0,0 +1,91 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.cli.table.column;
import java.util.stream.IntStream;
import static bisq.cli.CurrencyFormat.formatFiatVolume;
import static bisq.cli.CurrencyFormat.formatPrice;
import static bisq.cli.table.column.Column.JUSTIFICATION.RIGHT;
import static bisq.cli.table.column.FiatColumn.DISPLAY_MODE.PRICE;
import static bisq.cli.table.column.FiatColumn.DISPLAY_MODE.TRIGGER_PRICE;
/**
* For displaying fiat values as volume, price, or optional trigger price
* with appropriate precision.
*/
public class FiatColumn extends LongColumn {
public enum DISPLAY_MODE {
PRICE,
TRIGGER_PRICE,
VOLUME
}
private final DISPLAY_MODE displayMode;
// The default FiatColumn JUSTIFICATION is RIGHT.
// The default FiatColumn DISPLAY_MODE is PRICE.
public FiatColumn(String name) {
this(name, RIGHT, PRICE);
}
public FiatColumn(String name, DISPLAY_MODE displayMode) {
this(name, RIGHT, displayMode);
}
public FiatColumn(String name,
JUSTIFICATION justification,
DISPLAY_MODE displayMode) {
super(name, justification);
this.displayMode = displayMode;
}
@Override
public void addRow(Long value) {
rows.add(value);
String s;
if (displayMode.equals(TRIGGER_PRICE))
s = value > 0 ? formatPrice(value) : "";
else
s = displayMode.equals(PRICE) ? formatPrice(value) : formatFiatVolume(value);
stringColumn.addRow(s);
if (isNewMaxWidth.test(s))
maxWidth = s.length();
}
@Override
public String getRowAsFormattedString(int rowIndex) {
return getRow(rowIndex).toString();
}
@Override
public StringColumn asStringColumn() {
// We cached the formatted fiat price strings, but we did
// not know how much padding each string needed until now.
IntStream.range(0, stringColumn.getRows().size()).forEach(rowIndex -> {
String unjustified = stringColumn.getRow(rowIndex);
String justified = stringColumn.toJustifiedString(unjustified);
stringColumn.updateRow(rowIndex, justified);
});
return this.stringColumn;
}
}

View File

@ -0,0 +1,64 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.cli.table.column;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.stream.IntStream;
import static bisq.cli.table.column.Column.JUSTIFICATION.LEFT;
import static com.google.common.base.Strings.padEnd;
import static com.google.common.base.Strings.padStart;
import static java.lang.System.currentTimeMillis;
import static java.util.TimeZone.getTimeZone;
/**
* For displaying (long) timestamp values as ISO-8601 dates in UTC time zone.
*/
public class Iso8601DateTimeColumn extends LongColumn {
protected final SimpleDateFormat iso8601DateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
// The default Iso8601DateTimeColumn JUSTIFICATION is LEFT.
public Iso8601DateTimeColumn(String name) {
this(name, LEFT);
}
public Iso8601DateTimeColumn(String name, JUSTIFICATION justification) {
super(name, justification);
iso8601DateFormat.setTimeZone(getTimeZone("UTC"));
this.maxWidth = Math.max(name.length(), String.valueOf(currentTimeMillis()).length());
}
@Override
public String getRowAsFormattedString(int rowIndex) {
long time = getRow(rowIndex);
return justification.equals(LEFT)
? padEnd(iso8601DateFormat.format(new Date(time)), maxWidth, ' ')
: padStart(iso8601DateFormat.format(new Date(time)), maxWidth, ' ');
}
@Override
public StringColumn asStringColumn() {
IntStream.range(0, rows.size()).forEachOrdered(rowIndex ->
stringColumn.addRow(getRowAsFormattedString(rowIndex)));
return stringColumn;
}
}

View File

@ -0,0 +1,93 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.cli.table.column;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.IntStream;
import static bisq.cli.table.column.Column.JUSTIFICATION.RIGHT;
/**
* For displaying Long values.
*/
public class LongColumn extends NumberColumn<LongColumn, Long> {
protected final List<Long> rows = new ArrayList<>();
protected final Predicate<String> isNewMaxWidth = (s) -> s != null && !s.isEmpty() && s.length() > maxWidth;
// The default LongColumn JUSTIFICATION is RIGHT.
public LongColumn(String name) {
this(name, RIGHT);
}
public LongColumn(String name, JUSTIFICATION justification) {
super(name, justification);
this.maxWidth = name.length();
}
@Override
public void addRow(Long value) {
rows.add(value);
String s = String.valueOf(value);
if (isNewMaxWidth.test(s))
maxWidth = s.length();
}
@Override
public List<Long> getRows() {
return rows;
}
@Override
public int rowCount() {
return rows.size();
}
@Override
public boolean isEmpty() {
return rows.isEmpty();
}
@Override
public Long getRow(int rowIndex) {
return rows.get(rowIndex);
}
@Override
public void updateRow(int rowIndex, Long newValue) {
rows.set(rowIndex, newValue);
}
@Override
public String getRowAsFormattedString(int rowIndex) {
String s = String.valueOf(getRow(rowIndex));
return toJustifiedString(s);
}
@Override
public StringColumn asStringColumn() {
IntStream.range(0, rows.size()).forEachOrdered(rowIndex ->
stringColumn.addRow(getRowAsFormattedString(rowIndex)));
return stringColumn;
}
}

View File

@ -0,0 +1,57 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.cli.table.column;
import static bisq.cli.CurrencyFormat.formatPrice;
import static bisq.cli.CurrencyFormat.formatSatoshis;
import static bisq.cli.table.column.Column.JUSTIFICATION.RIGHT;
/**
* For displaying a mix of fiat and altcoin prices with appropriate precision.
*/
public class MixedPriceColumn extends LongColumn {
public MixedPriceColumn(String name) {
super(name, RIGHT);
}
@Override
public void addRow(Long value) {
throw new UnsupportedOperationException("use public void addRow(Long value, boolean isFiat) instead");
}
public void addRow(Long value, boolean isFiat) {
rows.add(value);
String s = isFiat ? formatPrice(value) : formatSatoshis(value);
stringColumn.addRow(s);
if (isNewMaxWidth.test(s))
maxWidth = s.length();
}
@Override
public String getRowAsFormattedString(int rowIndex) {
return getRow(rowIndex).toString();
}
@Override
public StringColumn asStringColumn() {
return stringColumn.justify();
}
}

View File

@ -0,0 +1,59 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.cli.table.column;
import static bisq.cli.CurrencyFormat.formatBsq;
import static bisq.cli.CurrencyFormat.formatSatoshis;
import static bisq.cli.table.column.Column.JUSTIFICATION.RIGHT;
/**
* For displaying a mix of BSQ and BTC trade fees with appropriate precision.
*/
public class MixedTradeFeeColumn extends LongColumn {
public MixedTradeFeeColumn(String name) {
super(name, RIGHT);
}
@Override
public void addRow(Long value) {
throw new UnsupportedOperationException("use public void addRow(Long value, boolean isBsq) instead");
}
public void addRow(Long value, boolean isBsq) {
rows.add(value);
String s = isBsq
? formatBsq(value) + " BSQ"
: formatSatoshis(value) + " BTC";
stringColumn.addRow(s);
if (isNewMaxWidth.test(s))
maxWidth = s.length();
}
@Override
public String getRowAsFormattedString(int rowIndex) {
return getRow(rowIndex).toString();
}
@Override
public StringColumn asStringColumn() {
return stringColumn.justify();
}
}

View File

@ -0,0 +1,73 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.cli.table.column;
import static bisq.cli.CurrencyFormat.formatCryptoCurrencyVolume;
import static bisq.cli.CurrencyFormat.formatFiatVolume;
import static bisq.cli.table.column.Column.JUSTIFICATION.RIGHT;
/**
* For displaying a mix of fiat and altcoin volumes with appropriate precision.
*/
public class MixedVolumeColumn extends LongColumn {
public MixedVolumeColumn(String name) {
super(name, RIGHT);
}
@Override
public void addRow(Long value) {
throw new UnsupportedOperationException("use public void addRow(Long value, boolean isAltcoinVolume) instead");
}
@Deprecated
public void addRow(Long value, boolean isAltcoinVolume) {
rows.add(value);
String s = isAltcoinVolume
? formatCryptoCurrencyVolume(value)
: formatFiatVolume(value);
stringColumn.addRow(s);
if (isNewMaxWidth.test(s))
maxWidth = s.length();
}
public void addRow(Long value, int displayPrecision) {
rows.add(value);
boolean isAltcoinVolume = displayPrecision > 0;
String s = isAltcoinVolume
? formatCryptoCurrencyVolume(value, displayPrecision)
: formatFiatVolume(value);
stringColumn.addRow(s);
if (isNewMaxWidth.test(s))
maxWidth = s.length();
}
@Override
public String getRowAsFormattedString(int rowIndex) {
return getRow(rowIndex).toString();
}
@Override
public StringColumn asStringColumn() {
return stringColumn.justify();
}
}

View File

@ -0,0 +1,32 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.cli.table.column;
/**
* Abstract superclass for numeric Columns.
*
* @param <C> the subclass column's type (LongColumn, IntegerColumn, ...)
* @param <T> the subclass column's numeric Java type (Long, Integer, ...)
*/
abstract class NumberColumn<C extends NumberColumn<C, T>,
T extends Number> extends AbstractColumn<C, T> implements Column<T> {
public NumberColumn(String name, JUSTIFICATION justification) {
super(name, justification);
}
}

View File

@ -0,0 +1,72 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.cli.table.column;
import static bisq.cli.CurrencyFormat.formatBsq;
import static bisq.cli.CurrencyFormat.formatSatoshis;
import static bisq.cli.table.column.Column.JUSTIFICATION.RIGHT;
/**
* For displaying BTC or BSQ satoshi values with appropriate precision.
*/
public class SatoshiColumn extends LongColumn {
protected final boolean isBsqSatoshis;
// The default SatoshiColumn JUSTIFICATION is RIGHT.
public SatoshiColumn(String name) {
this(name, RIGHT, false);
}
public SatoshiColumn(String name, boolean isBsqSatoshis) {
this(name, RIGHT, isBsqSatoshis);
}
public SatoshiColumn(String name, JUSTIFICATION justification) {
this(name, justification, false);
}
public SatoshiColumn(String name, JUSTIFICATION justification, boolean isBsqSatoshis) {
super(name, justification);
this.isBsqSatoshis = isBsqSatoshis;
}
@Override
public void addRow(Long value) {
rows.add(value);
// We do not know how much padding each StringColumn value needs until it has all the values.
String s = isBsqSatoshis ? formatBsq(value) : formatSatoshis(value);
stringColumn.addRow(s);
if (isNewMaxWidth.test(s))
maxWidth = s.length();
}
@Override
public String getRowAsFormattedString(int rowIndex) {
return isBsqSatoshis
? formatBsq(getRow(rowIndex))
: formatSatoshis(getRow(rowIndex));
}
@Override
public StringColumn asStringColumn() {
return stringColumn.justify();
}
}

View File

@ -0,0 +1,102 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.cli.table.column;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.IntStream;
import static bisq.cli.table.column.Column.JUSTIFICATION.LEFT;
import static bisq.cli.table.column.Column.JUSTIFICATION.RIGHT;
/**
* For displaying justified string values.
*/
public class StringColumn extends AbstractColumn<StringColumn, String> {
private final List<String> rows = new ArrayList<>();
private final Predicate<String> isNewMaxWidth = (s) -> s != null && !s.isEmpty() && s.length() > maxWidth;
// The default StringColumn JUSTIFICATION is LEFT.
public StringColumn(String name) {
this(name, LEFT);
}
// Use this constructor to override default LEFT justification.
public StringColumn(String name, JUSTIFICATION justification) {
super(name, justification);
this.maxWidth = name.length();
}
@Override
public void addRow(String value) {
rows.add(value);
if (isNewMaxWidth.test(value))
maxWidth = value.length();
}
@Override
public List<String> getRows() {
return rows;
}
@Override
public int rowCount() {
return rows.size();
}
@Override
public boolean isEmpty() {
return rows.isEmpty();
}
@Override
public String getRow(int rowIndex) {
return rows.get(rowIndex);
}
@Override
public void updateRow(int rowIndex, String newValue) {
rows.set(rowIndex, newValue);
}
@Override
public String getRowAsFormattedString(int rowIndex) {
return getRow(rowIndex);
}
@Override
public StringColumn asStringColumn() {
return this;
}
@Override
public StringColumn justify() {
if (justification.equals(RIGHT)) {
IntStream.range(0, getRows().size()).forEach(rowIndex -> {
String unjustified = getRow(rowIndex);
String justified = toJustifiedString(unjustified);
updateRow(rowIndex, justified);
});
}
return this;
}
}

View File

@ -0,0 +1,130 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.cli.table.column;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
import javax.annotation.Nullable;
import static bisq.cli.table.column.ZippedStringColumns.DUPLICATION_MODE.EXCLUDE_DUPLICATES;
import static bisq.cli.table.column.ZippedStringColumns.DUPLICATION_MODE.INCLUDE_DUPLICATES;
import bisq.cli.table.column.Column.JUSTIFICATION;
/**
* For zipping multiple StringColumns into a single StringColumn.
* Useful for displaying amount and volume range values.
*/
public class ZippedStringColumns {
public enum DUPLICATION_MODE {
EXCLUDE_DUPLICATES,
INCLUDE_DUPLICATES
}
private final String name;
private final JUSTIFICATION justification;
private final String delimiter;
private final StringColumn[] columns;
public ZippedStringColumns(String name,
JUSTIFICATION justification,
String delimiter,
StringColumn... columns) {
this.name = name;
this.justification = justification;
this.delimiter = delimiter;
this.columns = columns;
validateColumnData();
}
public StringColumn asStringColumn(DUPLICATION_MODE duplicationMode) {
StringColumn stringColumn = new StringColumn(name, justification);
buildRows(stringColumn, duplicationMode);
// Re-set the column name field to its justified value, in case any of the column
// values are longer than the name passed to this constructor.
stringColumn.setName(stringColumn.toJustifiedString(name));
return stringColumn;
}
private void buildRows(StringColumn stringColumn, DUPLICATION_MODE duplicationMode) {
// Populate the StringColumn with unjustified zipped values; we cannot justify
// the zipped values until stringColumn knows its final maxWidth.
IntStream.range(0, columns[0].getRows().size()).forEach(rowIndex -> {
String row = buildRow(rowIndex, duplicationMode);
stringColumn.addRow(row);
});
formatRows(stringColumn);
}
private String buildRow(int rowIndex, DUPLICATION_MODE duplicationMode) {
StringBuilder rowBuilder = new StringBuilder();
@Nullable
List<String> processedValues = duplicationMode.equals(EXCLUDE_DUPLICATES)
? new ArrayList<>()
: null;
IntStream.range(0, columns.length).forEachOrdered(colIndex -> {
// For each column @ rowIndex ...
var value = columns[colIndex].getRows().get(rowIndex);
if (duplicationMode.equals(INCLUDE_DUPLICATES)) {
if (rowBuilder.length() > 0)
rowBuilder.append(delimiter);
rowBuilder.append(value);
} else if (!processedValues.contains(value)) {
if (rowBuilder.length() > 0)
rowBuilder.append(delimiter);
rowBuilder.append(value);
processedValues.add(value);
}
});
return rowBuilder.toString();
}
private void formatRows(StringColumn stringColumn) {
// Now we can justify the zipped string values in the new StringColumn.
IntStream.range(0, stringColumn.getRows().size()).forEach(rowIndex -> {
String unjustified = stringColumn.getRow(rowIndex);
String justified = stringColumn.toJustifiedString(unjustified);
stringColumn.updateRow(rowIndex, justified);
});
}
private void validateColumnData() {
if (columns.length == 0)
throw new IllegalStateException("cannot zip columns because they do not have any data");
StringColumn firstColumn = columns[0];
if (firstColumn.getRows().isEmpty())
throw new IllegalStateException("1st column has no data");
IntStream.range(1, columns.length).forEach(colIndex -> {
if (columns[colIndex].getRows().size() != firstColumn.getRows().size())
throw new IllegalStateException("columns do not have same number of rows");
});
}
}

View File

@ -0,0 +1,96 @@
package bisq.cli;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import java.util.LinkedList;
import java.util.function.Predicate;
import lombok.extern.slf4j.Slf4j;
import static bisq.cli.opts.OptLabel.OPT_HOST;
import static bisq.cli.opts.OptLabel.OPT_PASSWORD;
import static bisq.cli.opts.OptLabel.OPT_PORT;
import static org.bitbucket.cowwoc.diffmatchpatch.DiffMatchPatch.Operation.DELETE;
import static org.bitbucket.cowwoc.diffmatchpatch.DiffMatchPatch.Operation.INSERT;
import bisq.cli.opts.ArgumentList;
import org.bitbucket.cowwoc.diffmatchpatch.DiffMatchPatch;
/**
* Parent class for CLI smoke tests. Useful for examining the format of the console
* output, and checking for diffs while making changes to console output formatters.
*
* Tests that create offers or trades should not be run on mainnet.
*/
@Slf4j
public abstract class AbstractCliTest {
static final String PASSWORD_OPT = "--password=xyz"; // Both daemons' password.
static final String ALICE_PORT_OPT = "--port=" + 9998; // Alice's daemon port.
static final String BOB_PORT_OPT = "--port=" + 9999; // Bob's daemon port.
static final String[] BASE_ALICE_CLIENT_OPTS = new String[]{PASSWORD_OPT, ALICE_PORT_OPT};
static final String[] BASE_BOB_CLIENT_OPTS = new String[]{PASSWORD_OPT, BOB_PORT_OPT};
protected final GrpcClient aliceClient;
protected final GrpcClient bobClient;
public AbstractCliTest() {
this.aliceClient = getGrpcClient(BASE_ALICE_CLIENT_OPTS);
this.bobClient = getGrpcClient(BASE_BOB_CLIENT_OPTS);
}
protected GrpcClient getGrpcClient(String[] args) {
var parser = new OptionParser();
var hostOpt = parser.accepts(OPT_HOST, "rpc server hostname or ip")
.withRequiredArg()
.defaultsTo("localhost");
var portOpt = parser.accepts(OPT_PORT, "rpc server port")
.withRequiredArg()
.ofType(Integer.class)
.defaultsTo(9998);
var passwordOpt = parser.accepts(OPT_PASSWORD, "rpc server password")
.withRequiredArg();
OptionSet options = parser.parse(new ArgumentList(args).getCLIArguments());
var host = options.valueOf(hostOpt);
var port = options.valueOf(portOpt);
var password = options.valueOf(passwordOpt);
if (password == null)
throw new IllegalArgumentException("missing required 'password' option");
return new GrpcClient(host, port, password);
}
protected void checkDiffsIgnoreWhitespace(String oldOutput, String newOutput) {
Predicate<DiffMatchPatch.Operation> isInsertOrDelete = (operation) ->
operation.equals(INSERT) || operation.equals(DELETE);
Predicate<String> isWhitespace = (text) -> text.trim().isEmpty();
boolean hasNonWhitespaceDiffs = false;
if (!oldOutput.equals(newOutput)) {
DiffMatchPatch dmp = new DiffMatchPatch();
LinkedList<DiffMatchPatch.Diff> diff = dmp.diffMain(oldOutput, newOutput, true);
for (DiffMatchPatch.Diff d : diff) {
if (isInsertOrDelete.test(d.operation) && !isWhitespace.test(d.text)) {
hasNonWhitespaceDiffs = true;
log.error(">>> DIFF {}", d);
}
}
}
if (hasNonWhitespaceDiffs)
log.error("FAIL: There were diffs");
else
log.info("PASS: No diffs");
}
protected void printOldTbl(String tbl) {
log.info("OLD Console OUT:\n{}", tbl);
}
protected void printNewTbl(String tbl) {
log.info("NEW Console OUT:\n{}", tbl);
}
}

View File

@ -0,0 +1,61 @@
package bisq.cli.table;
import bisq.proto.grpc.AddressBalanceInfo;
import java.util.List;
import static bisq.cli.table.builder.TableType.ADDRESS_BALANCE_TBL;
import static java.lang.System.err;
import static java.util.Collections.singletonList;
import bisq.cli.AbstractCliTest;
import bisq.cli.TableFormat;
import bisq.cli.table.builder.TableBuilder;
@SuppressWarnings("unused")
public class AddressCliOutputDiffTest extends AbstractCliTest {
public static void main(String[] args) {
AddressCliOutputDiffTest test = new AddressCliOutputDiffTest();
test.getFundingAddresses();
test.getAddressBalance();
}
public AddressCliOutputDiffTest() {
super();
}
private void getFundingAddresses() {
var fundingAddresses = aliceClient.getFundingAddresses();
if (fundingAddresses.size() > 0) {
var oldTbl = TableFormat.formatAddressBalanceTbl(fundingAddresses);
var newTbl = new TableBuilder(ADDRESS_BALANCE_TBL, fundingAddresses).build().toString();
printOldTbl(oldTbl);
printNewTbl(newTbl);
checkDiffsIgnoreWhitespace(oldTbl, newTbl);
} else {
err.println("no funding addresses found");
}
}
private void getAddressBalance() {
List<AddressBalanceInfo> addresses = aliceClient.getFundingAddresses();
int numAddresses = addresses.size();
// Check output for last 2 addresses.
for (int i = numAddresses - 2; i < addresses.size(); i++) {
var addressBalanceInfo = addresses.get(i);
getAddressBalance(addressBalanceInfo.getAddress());
}
}
private void getAddressBalance(String address) {
var addressBalance = singletonList(aliceClient.getAddressBalance(address));
var oldTbl = TableFormat.formatAddressBalanceTbl(addressBalance);
var newTbl = new TableBuilder(ADDRESS_BALANCE_TBL, addressBalance).build().toString();
printOldTbl(oldTbl);
printNewTbl(newTbl);
checkDiffsIgnoreWhitespace(oldTbl, newTbl);
}
}

View File

@ -0,0 +1,42 @@
package bisq.cli.table;
import static bisq.cli.table.builder.TableType.BSQ_BALANCE_TBL;
import static bisq.cli.table.builder.TableType.BTC_BALANCE_TBL;
import bisq.cli.AbstractCliTest;
import bisq.cli.TableFormat;
import bisq.cli.table.builder.TableBuilder;
@SuppressWarnings("unused")
public class GetBalanceCliOutputDiffTest extends AbstractCliTest {
public static void main(String[] args) {
GetBalanceCliOutputDiffTest test = new GetBalanceCliOutputDiffTest();
test.getBtcBalance();
test.getBsqBalance();
}
public GetBalanceCliOutputDiffTest() {
super();
}
private void getBtcBalance() {
var balance = aliceClient.getBtcBalances();
var oldTbl = TableFormat.formatBtcBalanceInfoTbl(balance);
var newTbl = new TableBuilder(BTC_BALANCE_TBL, balance).build().toString();
printOldTbl(oldTbl);
printNewTbl(newTbl);
checkDiffsIgnoreWhitespace(oldTbl, newTbl);
}
private void getBsqBalance() {
var balance = aliceClient.getBsqBalances();
var oldTbl = TableFormat.formatBsqBalanceInfoTbl(balance);
var newTbl = new TableBuilder(BSQ_BALANCE_TBL, balance).build().toString();
printOldTbl(oldTbl);
printNewTbl(newTbl);
checkDiffsIgnoreWhitespace(oldTbl, newTbl);
}
}

View File

@ -0,0 +1,126 @@
package bisq.cli.table;
import bisq.proto.grpc.OfferInfo;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import static bisq.cli.table.builder.TableType.OFFER_TBL;
import static protobuf.OfferDirection.BUY;
import static protobuf.OfferDirection.SELL;
import bisq.cli.AbstractCliTest;
import bisq.cli.OfferFormat;
import bisq.cli.table.builder.TableBuilder;
@SuppressWarnings("unused")
@Slf4j
public class GetOffersCliOutputDiffTest extends AbstractCliTest {
// "My" offers are always Alice's offers.
// "Available" offers are always Alice's offers available to Bob.
public static void main(String[] args) {
GetOffersCliOutputDiffTest test = new GetOffersCliOutputDiffTest();
test.getMyBuyUsdOffers();
test.getMySellUsdOffers();
test.getAvailableBuyUsdOffers();
test.getAvailableSellUsdOffers();
/*
// TODO Uncomment when XMR support is added.
test.getMyBuyXmrOffers();
test.getMySellXmrOffers();
test.getAvailableBuyXmrOffers();
test.getAvailableSellXmrOffers();
*/
test.getMyBuyBsqOffers();
test.getMySellBsqOffers();
test.getAvailableBuyBsqOffers();
test.getAvailableSellBsqOffers();
}
public GetOffersCliOutputDiffTest() {
super();
}
private void getMyBuyUsdOffers() {
var myOffers = aliceClient.getMyOffers(BUY.name(), "USD");
printAndCheckDiffs(myOffers, BUY.name(), "USD");
}
private void getMySellUsdOffers() {
var myOffers = aliceClient.getMyOffers(SELL.name(), "USD");
printAndCheckDiffs(myOffers, SELL.name(), "USD");
}
private void getAvailableBuyUsdOffers() {
var offers = bobClient.getOffers(BUY.name(), "USD");
printAndCheckDiffs(offers, BUY.name(), "USD");
}
private void getAvailableSellUsdOffers() {
var offers = bobClient.getOffers(SELL.name(), "USD");
printAndCheckDiffs(offers, SELL.name(), "USD");
}
private void getMyBuyXmrOffers() {
var myOffers = aliceClient.getMyOffers(BUY.name(), "XMR");
printAndCheckDiffs(myOffers, BUY.name(), "XMR");
}
private void getMySellXmrOffers() {
var myOffers = aliceClient.getMyOffers(SELL.name(), "XMR");
printAndCheckDiffs(myOffers, SELL.name(), "XMR");
}
private void getAvailableBuyXmrOffers() {
var offers = bobClient.getOffers(BUY.name(), "XMR");
printAndCheckDiffs(offers, BUY.name(), "XMR");
}
private void getAvailableSellXmrOffers() {
var offers = bobClient.getOffers(SELL.name(), "XMR");
printAndCheckDiffs(offers, SELL.name(), "XMR");
}
private void getMyBuyBsqOffers() {
var myOffers = aliceClient.getMyOffers(BUY.name(), "BSQ");
printAndCheckDiffs(myOffers, BUY.name(), "BSQ");
}
private void getMySellBsqOffers() {
var myOffers = aliceClient.getMyOffers(SELL.name(), "BSQ");
printAndCheckDiffs(myOffers, SELL.name(), "BSQ");
}
private void getAvailableBuyBsqOffers() {
var offers = bobClient.getOffers(BUY.name(), "BSQ");
printAndCheckDiffs(offers, BUY.name(), "BSQ");
}
private void getAvailableSellBsqOffers() {
var offers = bobClient.getOffers(SELL.name(), "BSQ");
printAndCheckDiffs(offers, SELL.name(), "BSQ");
}
private void printAndCheckDiffs(List<OfferInfo> offers,
String direction,
String currencyCode) {
if (offers.isEmpty()) {
log.warn("No {} {} offers to print.", direction, currencyCode);
} else {
log.info("Checking for diffs in {} {} offers.", direction, currencyCode);
var oldTbl = OfferFormat.formatOfferTable(offers, currencyCode);
var newTbl = new TableBuilder(OFFER_TBL, offers).build().toString();
printOldTbl(oldTbl);
printNewTbl(newTbl);
checkDiffsIgnoreWhitespace(oldTbl, newTbl);
}
}
}

View File

@ -0,0 +1,52 @@
package bisq.cli.table;
import lombok.extern.slf4j.Slf4j;
import static bisq.cli.table.builder.TableType.TRADE_DETAIL_TBL;
import static java.lang.System.out;
import bisq.cli.AbstractCliTest;
import bisq.cli.GrpcClient;
import bisq.cli.TradeFormat;
import bisq.cli.table.builder.TableBuilder;
@SuppressWarnings("unused")
@Slf4j
public class GetTradeCliOutputDiffTest extends AbstractCliTest {
public static void main(String[] args) {
if (args.length == 0)
throw new IllegalStateException("Need a single trade-id program argument.");
GetTradeCliOutputDiffTest test = new GetTradeCliOutputDiffTest(args[0]);
test.getAlicesTrade();
out.println();
test.getBobsTrade();
}
private final String tradeId;
public GetTradeCliOutputDiffTest(String tradeId) {
super();
this.tradeId = tradeId;
}
private void getAlicesTrade() {
getTrade(aliceClient);
}
private void getBobsTrade() {
getTrade(bobClient);
}
private void getTrade(GrpcClient client) {
var trade = client.getTrade(tradeId);
var oldTbl = TradeFormat.format(trade);
var newTbl = new TableBuilder(TRADE_DETAIL_TBL, trade).build().toString();
printOldTbl(oldTbl);
printNewTbl(newTbl);
checkDiffsIgnoreWhitespace(oldTbl, newTbl);
}
}

View File

@ -0,0 +1,41 @@
package bisq.cli.table;
import lombok.extern.slf4j.Slf4j;
import static bisq.cli.table.builder.TableType.TRANSACTION_TBL;
import bisq.cli.AbstractCliTest;
import bisq.cli.TransactionFormat;
import bisq.cli.table.builder.TableBuilder;
@SuppressWarnings("unused")
@Slf4j
public class GetTransactionCliOutputDiffTest extends AbstractCliTest {
public static void main(String[] args) {
if (args.length == 0)
throw new IllegalStateException("Need a single transaction-id program argument.");
GetTransactionCliOutputDiffTest test = new GetTransactionCliOutputDiffTest(args[0]);
test.getTransaction();
}
private final String transactionId;
public GetTransactionCliOutputDiffTest(String transactionId) {
super();
this.transactionId = transactionId;
}
private void getTransaction() {
var tx = aliceClient.getTransaction(transactionId);
var oldTbl = TransactionFormat.format(tx);
var newTbl = new TableBuilder(TRANSACTION_TBL, tx).build().toString();
printOldTbl(oldTbl);
printNewTbl(newTbl);
// Should show 1 (OK) diff due to new 'Is Confirmed' column being left justified (fixed).
checkDiffsIgnoreWhitespace(oldTbl, newTbl);
}
}

View File

@ -0,0 +1,39 @@
package bisq.cli.table;
import lombok.extern.slf4j.Slf4j;
import static bisq.cli.TableFormat.formatPaymentAcctTbl;
import static bisq.cli.table.builder.TableType.PAYMENT_ACCOUNT_TBL;
import bisq.cli.AbstractCliTest;
import bisq.cli.table.builder.TableBuilder;
@SuppressWarnings("unused")
@Slf4j
public class PaymentAccountsCliOutputDiffTest extends AbstractCliTest {
public static void main(String[] args) {
PaymentAccountsCliOutputDiffTest test = new PaymentAccountsCliOutputDiffTest();
test.getPaymentAccounts();
}
public PaymentAccountsCliOutputDiffTest() {
super();
}
private void getPaymentAccounts() {
var paymentAccounts = aliceClient.getPaymentAccounts();
if (paymentAccounts.size() > 0) {
var oldTbl = formatPaymentAcctTbl(paymentAccounts);
var newTbl = new TableBuilder(PAYMENT_ACCOUNT_TBL, paymentAccounts).build().toString();
printOldTbl(oldTbl);
printNewTbl(newTbl);
checkDiffsIgnoreWhitespace(oldTbl, newTbl);
} else {
log.warn("no payment accounts found");
}
}
}

View File

@ -240,13 +240,11 @@ public class CoreApi {
public PaymentAccount createCryptoCurrencyPaymentAccount(String accountName,
String currencyCode,
String address,
boolean tradeInstant,
boolean isBsqSwap) {
boolean tradeInstant) {
return paymentAccountsService.createCryptoCurrencyPaymentAccount(accountName,
currencyCode,
address,
tradeInstant,
isBsqSwap);
tradeInstant);
}
public List<PaymentMethod> getCryptoCurrencyPaymentMethods() {

View File

@ -103,8 +103,7 @@ class CorePaymentAccountsService {
PaymentAccount createCryptoCurrencyPaymentAccount(String accountName,
String currencyCode,
String address,
boolean tradeInstant,
boolean isBsqSwap) {
boolean tradeInstant) {
String bsqCode = currencyCode.toUpperCase();
if (!bsqCode.equals("BSQ"))
throw new IllegalArgumentException("api does not currently support " + currencyCode + " accounts");
@ -112,25 +111,15 @@ class CorePaymentAccountsService {
// Validate the BSQ address string but ignore the return value.
coreWalletsService.getValidBsqAddress(address);
// TODO Split into 2 methods: createAtomicPaymentAccount(), createCryptoCurrencyPaymentAccount().
PaymentAccount cryptoCurrencyAccount;
if (isBsqSwap) {
cryptoCurrencyAccount = PaymentAccountFactory.getPaymentAccount(PaymentMethod.BSQ_SWAP);
} else {
cryptoCurrencyAccount = tradeInstant
? (InstantCryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS_INSTANT)
: (CryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS);
}
AssetAccount cryptoCurrencyAccount = tradeInstant
? (InstantCryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS_INSTANT)
: (CryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS);
cryptoCurrencyAccount.init();
cryptoCurrencyAccount.setAccountName(accountName);
if (!isBsqSwap) {
((AssetAccount) cryptoCurrencyAccount).setAddress(address);
}
cryptoCurrencyAccount.setAddress(address);
Optional<CryptoCurrency> cryptoCurrency = getCryptoCurrency(bsqCode);
cryptoCurrency.ifPresent(cryptoCurrencyAccount::setSingleTradeCurrency);
user.addPaymentAccount(cryptoCurrencyAccount);
accountAgeWitnessService.publishMyAccountAgeWitness(cryptoCurrencyAccount.getPaymentAccountPayload());
log.info("Saved crypto payment account with id {} and payment method {}.",
cryptoCurrencyAccount.getId(),
cryptoCurrencyAccount.getPaymentAccountPayload().getPaymentMethodId());

View File

@ -136,8 +136,7 @@ class GrpcPaymentAccountsService extends PaymentAccountsImplBase {
PaymentAccount paymentAccount = coreApi.createCryptoCurrencyPaymentAccount(req.getAccountName(),
req.getCurrencyCode(),
req.getAddress(),
req.getTradeInstant(),
req.getIsBsqSwap());
req.getTradeInstant());
var reply = CreateCryptoCurrencyPaymentAccountReply.newBuilder()
.setPaymentAccount(paymentAccount.toProtoMessage())
.build();

View File

@ -684,8 +684,7 @@ class OfferBookViewModel extends ActivatableViewModel {
return coreApi.createCryptoCurrencyPaymentAccount(DisplayUtils.createAssetsAccountName("BSQ", unusedBsqAddressAsString),
"BSQ",
unusedBsqAddressAsString,
isInstantPaymentMethod(offer),
false);
isInstantPaymentMethod(offer));
}
public void setOfferActionHandler(OfferView.OfferActionHandler offerActionHandler) {

View File

@ -311,7 +311,6 @@ message CreateCryptoCurrencyPaymentAccountRequest {
string currencyCode = 2;
string address = 3;
bool tradeInstant = 4;
bool isBsqSwap = 5;
}
message CreateCryptoCurrencyPaymentAccountReply {