diff --git a/apitest/src/main/java/bisq/apitest/config/ApiTestConfig.java b/apitest/src/main/java/bisq/apitest/config/ApiTestConfig.java index b17b154171..ec2076e885 100644 --- a/apitest/src/main/java/bisq/apitest/config/ApiTestConfig.java +++ b/apitest/src/main/java/bisq/apitest/config/ApiTestConfig.java @@ -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"; diff --git a/apitest/src/test/java/bisq/apitest/method/MethodTest.java b/apitest/src/test/java/bisq/apitest/method/MethodTest.java index 2a9ad4f329..f01c4ecbf5 100644 --- a/apitest/src/test/java/bisq/apitest/method/MethodTest.java +++ b/apitest/src/test/java/bisq/apitest/method/MethodTest.java @@ -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)); } diff --git a/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java b/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java index 0f01407412..f5b8904815 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java @@ -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 toOfferTable = (offer) -> + new TableBuilder(OFFER_TBL, offer).build().toString(); + + protected final Function, String> toOffersTable = (offers) -> + new TableBuilder(OFFER_TBL, offers).build().toString(); + + // TODO + protected final Function 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); diff --git a/apitest/src/test/java/bisq/apitest/method/offer/BsqSwapOfferTest.java b/apitest/src/test/java/bisq/apitest/method/offer/BsqSwapOfferTest.java index 1fbcd77cd3..bdb516c515 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/BsqSwapOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/BsqSwapOfferTest.java @@ -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); diff --git a/apitest/src/test/java/bisq/apitest/method/offer/CreateBSQOffersTest.java b/apitest/src/test/java/bisq/apitest/method/offer/CreateBSQOffersTest.java index b76a8b7aed..72a5de9e56 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/CreateBSQOffersTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/CreateBSQOffersTest.java @@ -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 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 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() { diff --git a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingFixedPriceTest.java b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingFixedPriceTest.java index 096c457e77..d24840f669 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingFixedPriceTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingFixedPriceTest.java @@ -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()); } } diff --git a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java index efa983caba..f40565ed61 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java @@ -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()); diff --git a/apitest/src/test/java/bisq/apitest/method/offer/EditOfferTest.java b/apitest/src/test/java/bisq/apitest/method/offer/EditOfferTest.java index 018b7277e3..59c231d07b 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/EditOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/EditOfferTest.java @@ -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 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()); diff --git a/apitest/src/test/java/bisq/apitest/method/payment/CreatePaymentAccountTest.java b/apitest/src/test/java/bisq/apitest/method/payment/CreatePaymentAccountTest.java index 26e2030c84..ec54bf3327 100644 --- a/apitest/src/test/java/bisq/apitest/method/payment/CreatePaymentAccountTest.java +++ b/apitest/src/test/java/bisq/apitest/method/payment/CreatePaymentAccountTest.java @@ -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()); } } } diff --git a/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java b/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java index 4c4a6b3453..ba59c1a98f 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java @@ -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 maxTradeStateAndPhaseChecks = () -> isLongRunningTest ? 10 : 2; + private final Function 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 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 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())); } } } diff --git a/apitest/src/test/java/bisq/apitest/method/trade/BsqSwapTradeTest.java b/apitest/src/test/java/bisq/apitest/method/trade/BsqSwapTradeTest.java index dda9a66b7d..73261907db 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/BsqSwapTradeTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/BsqSwapTradeTest.java @@ -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() { diff --git a/apitest/src/test/java/bisq/apitest/method/trade/BsqSwapTradeTestLoop.java b/apitest/src/test/java/bisq/apitest/method/trade/BsqSwapTradeTestLoop.java deleted file mode 100644 index d5cf9b7dd7..0000000000 --- a/apitest/src/test/java/bisq/apitest/method/trade/BsqSwapTradeTestLoop.java +++ /dev/null @@ -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 . - */ - -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(); - } - } -} diff --git a/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBSQOfferTest.java b/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBSQOfferTest.java index 9acdb4b32a..876a053b69 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBSQOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBSQOfferTest.java @@ -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 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 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); } diff --git a/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java b/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java index 7a7f0dd9c8..b9c761d356 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java @@ -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 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 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); } diff --git a/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferWithNationalBankAcctTest.java b/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferWithNationalBankAcctTest.java index a625d86fae..210d58192e 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferWithNationalBankAcctTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferWithNationalBankAcctTest.java @@ -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 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 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); } diff --git a/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBSQOfferTest.java b/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBSQOfferTest.java index d33e0e360e..d830a78e13 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBSQOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBSQOfferTest.java @@ -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 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 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); } diff --git a/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBTCOfferTest.java b/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBTCOfferTest.java index 078d299859..8bd13fff09 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBTCOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBTCOfferTest.java @@ -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 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 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); } diff --git a/apitest/src/test/java/bisq/apitest/method/wallet/BsqWalletTest.java b/apitest/src/test/java/bisq/apitest/method/wallet/BsqWalletTest.java index f96d422f6a..010f0fe0d2 100644 --- a/apitest/src/test/java/bisq/apitest/method/wallet/BsqWalletTest.java +++ b/apitest/src/test/java/bisq/apitest/method/wallet/BsqWalletTest.java @@ -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()); } } diff --git a/apitest/src/test/java/bisq/apitest/method/wallet/BtcWalletTest.java b/apitest/src/test/java/bisq/apitest/method/wallet/BtcWalletTest.java index 19d065c6cc..550695e353 100644 --- a/apitest/src/test/java/bisq/apitest/method/wallet/BtcWalletTest.java +++ b/apitest/src/test/java/bisq/apitest/method/wallet/BtcWalletTest.java @@ -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]. diff --git a/apitest/src/test/java/bisq/apitest/scenario/LongRunningBsqSwapTest.java b/apitest/src/test/java/bisq/apitest/scenario/LongRunningBsqSwapTest.java index bec7044f12..d56c98e5b3 100644 --- a/apitest/src/test/java/bisq/apitest/scenario/LongRunningBsqSwapTest.java +++ b/apitest/src/test/java/bisq/apitest/scenario/LongRunningBsqSwapTest.java @@ -43,7 +43,6 @@ public class LongRunningBsqSwapTest extends AbstractOfferTest { @BeforeAll public static void setUp() { AbstractOfferTest.setUp(); - createBsqSwapBsqPaymentAccounts(); } @Test diff --git a/apitest/src/test/java/bisq/apitest/scenario/LongRunningTradesTest.java b/apitest/src/test/java/bisq/apitest/scenario/LongRunningTradesTest.java index 2ab8d7fdae..ae9c3ae3de 100644 --- a/apitest/src/test/java/bisq/apitest/scenario/LongRunningTradesTest.java +++ b/apitest/src/test/java/bisq/apitest/scenario/LongRunningTradesTest.java @@ -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) { diff --git a/apitest/src/test/java/bisq/apitest/scenario/OfferTest.java b/apitest/src/test/java/bisq/apitest/scenario/OfferTest.java index e52916bad8..0996d21eef 100644 --- a/apitest/src/test/java/bisq/apitest/scenario/OfferTest.java +++ b/apitest/src/test/java/bisq/apitest/scenario/OfferTest.java @@ -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(); diff --git a/apitest/src/test/java/bisq/apitest/scenario/TradeTest.java b/apitest/src/test/java/bisq/apitest/scenario/TradeTest.java index becb62f918..6361271251 100644 --- a/apitest/src/test/java/bisq/apitest/scenario/TradeTest.java +++ b/apitest/src/test/java/bisq/apitest/scenario/TradeTest.java @@ -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(); } diff --git a/apitest/src/test/java/bisq/apitest/scenario/bot/protocol/MakerBotProtocol.java b/apitest/src/test/java/bisq/apitest/scenario/bot/protocol/MakerBotProtocol.java index 0ce26002ec..e251150cf1 100644 --- a/apitest/src/test/java/bisq/apitest/scenario/bot/protocol/MakerBotProtocol.java +++ b/apitest/src/test/java/bisq/apitest/scenario/bot/protocol/MakerBotProtocol.java @@ -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; diff --git a/apitest/src/test/java/bisq/apitest/scenario/bot/protocol/TakerBotProtocol.java b/apitest/src/test/java/bisq/apitest/scenario/bot/protocol/TakerBotProtocol.java index 63b700824f..22964d0f16 100644 --- a/apitest/src/test/java/bisq/apitest/scenario/bot/protocol/TakerBotProtocol.java +++ b/apitest/src/test/java/bisq/apitest/scenario/bot/protocol/TakerBotProtocol.java @@ -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; diff --git a/build.gradle b/build.gradle index 7e467e2923..038281abc4 100644 --- a/build.gradle +++ b/build.gradle @@ -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 { diff --git a/cli/src/main/java/bisq/cli/CliMain.java b/cli/src/main/java/bisq/cli/CliMain.java index 7a52f9a4cc..bc84aed69f 100644 --- a/cli/src/main/java/bisq/cli/CliMain.java +++ b/cli/src/main/java/bisq/cli/CliMain.java @@ -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) { diff --git a/cli/src/main/java/bisq/cli/ColumnHeaderConstants.java b/cli/src/main/java/bisq/cli/ColumnHeaderConstants.java index 32a4564d16..e71dde5eb1 100644 --- a/cli/src/main/java/bisq/cli/ColumnHeaderConstants.java +++ b/cli/src/main/java/bisq/cli/ColumnHeaderConstants.java @@ -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. diff --git a/cli/src/main/java/bisq/cli/CryptoCurrencyUtil.java b/cli/src/main/java/bisq/cli/CryptoCurrencyUtil.java index cb503d7c07..c4dec5f59f 100644 --- a/cli/src/main/java/bisq/cli/CryptoCurrencyUtil.java +++ b/cli/src/main/java/bisq/cli/CryptoCurrencyUtil.java @@ -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 getSupportedCryptoCurrencies() { final List result = new ArrayList<>(); result.add("BSQ"); + // result.add("XMR"); // TODO Uncomment when XMR support is added. result.sort(String::compareTo); return result; } diff --git a/cli/src/main/java/bisq/cli/CurrencyFormat.java b/cli/src/main/java/bisq/cli/CurrencyFormat.java index 8d8a3d11fd..a9d8714619 100644 --- a/cli/src/main/java/bisq/cli/CurrencyFormat.java +++ b/cli/src/main/java/bisq/cli/CurrencyFormat.java @@ -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) { diff --git a/cli/src/main/java/bisq/cli/DirectionFormat.java b/cli/src/main/java/bisq/cli/DirectionFormat.java index b3cb1773f4..c56d89467b 100644 --- a/cli/src/main/java/bisq/cli/DirectionFormat.java +++ b/cli/src/main/java/bisq/cli/DirectionFormat.java @@ -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 offers) { diff --git a/cli/src/main/java/bisq/cli/GrpcClient.java b/cli/src/main/java/bisq/cli/GrpcClient.java index 4372577a1a..dcdaddc550 100644 --- a/cli/src/main/java/bisq/cli/GrpcClient.java +++ b/cli/src/main/java/bisq/cli/GrpcClient.java @@ -327,6 +327,10 @@ public final class GrpcClient { return offersServiceRequest.getMyOffersSortedByDate(currencyCode); } + public List getMyCryptoCurrencyOffersSortedByDate(String currencyCode) { + return offersServiceRequest.getMyCryptoCurrencyOffersSortedByDate(currencyCode); + } + public List 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, diff --git a/cli/src/main/java/bisq/cli/OfferFormat.java b/cli/src/main/java/bisq/cli/OfferFormat.java new file mode 100644 index 0000000000..9aea2d9167 --- /dev/null +++ b/cli/src/main/java/bisq/cli/OfferFormat.java @@ -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 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 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 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 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 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 offers) { + return getLongestColumnSize( + COL_HEADER_PAYMENT_METHOD.length(), + offers.stream() + .map(OfferInfo::getPaymentMethodShortName) + .collect(Collectors.toList())); + } + + private static int getLongestAmountColWidth(List offers) { + return getLongestColumnSize( + COL_HEADER_AMOUNT.length(), + offers.stream() + .map(o -> formatAmountRange(o.getMinAmount(), o.getAmount())) + .collect(Collectors.toList())); + } + + private static int getLongestVolumeColWidth(List 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 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())); + } +} diff --git a/cli/src/main/java/bisq/cli/TableFormat.java b/cli/src/main/java/bisq/cli/TableFormat.java index 1340ac3a76..37c3b36476 100644 --- a/cli/src/main/java/bisq/cli/TableFormat.java +++ b/cli/src/main/java/bisq/cli/TableFormat.java @@ -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 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 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 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 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 offers) { - return getLongestColumnSize( - COL_HEADER_PAYMENT_METHOD.length(), - offers.stream() - .map(OfferInfo::getPaymentMethodShortName) - .collect(Collectors.toList())); - } - - private static int getLongestAmountColWidth(List offers) { - return getLongestColumnSize( - COL_HEADER_AMOUNT.length(), - offers.stream() - .map(o -> formatAmountRange(o.getMinAmount(), o.getAmount())) - .collect(Collectors.toList())); - } - - private static int getLongestVolumeColWidth(List 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 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 strings) { + static int getLongestColumnSize(int headerLength, List 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)); } diff --git a/cli/src/main/java/bisq/cli/TradeFormat.java b/cli/src/main/java/bisq/cli/TradeFormat.java index 57248f4da0..4ac8549728 100644 --- a/cli/src/main/java/bisq/cli/TradeFormat.java +++ b/cli/src/main/java/bisq/cli/TradeFormat.java @@ -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 amountFormat = (t) -> t.getOffer().getBaseCurrencyCode().equals("BTC") ? formatSatoshis(t.getTradeAmountAsLong()) - : formatCryptoCurrencyOfferVolume(t.getTradeVolume()); + : formatCryptoCurrencyVolume(t.getTradeVolume()); private static final BiFunction makerTakerMinerTxFeeFormat = (t, isTaker) -> { if (isTaker) { @@ -188,7 +189,7 @@ public class TradeFormat { private static final Function tradeCostFormat = (t) -> t.getOffer().getBaseCurrencyCode().equals("BTC") - ? formatOfferVolume(t.getTradeVolume()) + ? formatFiatVolume(t.getTradeVolume()) : formatSatoshis(t.getTradeAmountAsLong()); private static final BiFunction bsqReceiveAddress = (t, showBsqBuyerAddress) -> { diff --git a/cli/src/main/java/bisq/cli/TransactionFormat.java b/cli/src/main/java/bisq/cli/TransactionFormat.java index 608c2fcb71..fd0553c537 100644 --- a/cli/src/main/java/bisq/cli/TransactionFormat.java +++ b/cli/src/main/java/bisq/cli/TransactionFormat.java @@ -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 { diff --git a/cli/src/main/java/bisq/cli/opts/CreateCryptoCurrencyPaymentAcctOptionParser.java b/cli/src/main/java/bisq/cli/opts/CreateCryptoCurrencyPaymentAcctOptionParser.java index 000d327d78..a37a9f109b 100644 --- a/cli/src/main/java/bisq/cli/opts/CreateCryptoCurrencyPaymentAcctOptionParser.java +++ b/cli/src/main/java/bisq/cli/opts/CreateCryptoCurrencyPaymentAcctOptionParser.java @@ -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 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); - } } diff --git a/cli/src/main/java/bisq/cli/opts/OptLabel.java b/cli/src/main/java/bisq/cli/opts/OptLabel.java index e31bf744cd..70dda3e6fc 100644 --- a/cli/src/main/java/bisq/cli/opts/OptLabel.java +++ b/cli/src/main/java/bisq/cli/opts/OptLabel.java @@ -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"; diff --git a/cli/src/main/java/bisq/cli/request/OffersServiceRequest.java b/cli/src/main/java/bisq/cli/request/OffersServiceRequest.java index 2df00bc2ee..259ff8e142 100644 --- a/cli/src/main/java/bisq/cli/request/OffersServiceRequest.java +++ b/cli/src/main/java/bisq/cli/request/OffersServiceRequest.java @@ -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 getMyCryptoCurrencyOffersSortedByDate(String currencyCode) { + ArrayList offers = new ArrayList<>(); + offers.addAll(getMyCryptoCurrencyOffers(BUY.name(), currencyCode)); + offers.addAll(getMyCryptoCurrencyOffers(SELL.name(), currencyCode)); + return sortOffersByDate(offers); + } + public List getMyBsqOffersSortedByDate() { ArrayList offers = new ArrayList<>(); offers.addAll(getMyCryptoCurrencyOffers(BUY.name(), "BSQ")); diff --git a/cli/src/main/java/bisq/cli/request/PaymentAccountsServiceRequest.java b/cli/src/main/java/bisq/cli/request/PaymentAccountsServiceRequest.java index 467aa51462..342be3939b 100644 --- a/cli/src/main/java/bisq/cli/request/PaymentAccountsServiceRequest.java +++ b/cli/src/main/java/bisq/cli/request/PaymentAccountsServiceRequest.java @@ -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, diff --git a/cli/src/main/java/bisq/cli/request/TradesServiceRequest.java b/cli/src/main/java/bisq/cli/request/TradesServiceRequest.java index 33a68c344a..97d7d2a5ef 100644 --- a/cli/src/main/java/bisq/cli/request/TradesServiceRequest.java +++ b/cli/src/main/java/bisq/cli/request/TradesServiceRequest.java @@ -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); } } diff --git a/cli/src/main/java/bisq/cli/request/WalletsServiceRequest.java b/cli/src/main/java/bisq/cli/request/WalletsServiceRequest.java index e4e7f07c5f..d95a68e2f8 100644 --- a/cli/src/main/java/bisq/cli/request/WalletsServiceRequest.java +++ b/cli/src/main/java/bisq/cli/request/WalletsServiceRequest.java @@ -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); } } diff --git a/cli/src/main/java/bisq/cli/table/Table.java b/cli/src/main/java/bisq/cli/table/Table.java new file mode 100644 index 0000000000..60677b3a2a --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/Table.java @@ -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 . + */ + +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())); + }); + } +} diff --git a/cli/src/main/java/bisq/cli/table/builder/AbstractTableBuilder.java b/cli/src/main/java/bisq/cli/table/builder/AbstractTableBuilder.java new file mode 100644 index 0000000000..1484e2c1f9 --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/builder/AbstractTableBuilder.java @@ -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 . + */ + +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 isFiatOffer = (o) -> o.getBaseCurrencyCode().equals("BTC"); +} diff --git a/cli/src/main/java/bisq/cli/table/builder/AbstractTradeListBuilder.java b/cli/src/main/java/bisq/cli/table/builder/AbstractTradeListBuilder.java new file mode 100644 index 0000000000..23a265a45c --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/builder/AbstractTradeListBuilder.java @@ -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 . + */ + +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 trades; + + protected final TradeTableColumnSupplier colSupplier; + + protected final Column colTradeId; + @Nullable + protected final Column colCreateDate; + @Nullable + protected final Column colMarket; + protected final Column colPrice; + @Nullable + protected final Column colPriceDeviation; + @Nullable + protected final Column colCurrency; + @Nullable + protected final Column colAmountInBtc; + @Nullable + protected final MixedVolumeColumn colMixedAmount; + @Nullable + protected final Column colMinerTxFee; + @Nullable + protected final MixedTradeFeeColumn colMixedTradeFee; + @Nullable + protected final Column colBuyerDeposit; + @Nullable + protected final Column colSellerDeposit; + @Nullable + protected final Column colPaymentMethod; + @Nullable + protected final Column colRole; + @Nullable + protected final Column colOfferType; + @Nullable + protected final Column colStatusDescription; + + // Trade detail tbl specific columns + + @Nullable + protected final Column colIsDepositPublished; + @Nullable + protected final Column colIsDepositConfirmed; + @Nullable + protected final Column colIsPayoutPublished; + @Nullable + protected final Column colIsFundsWithdrawn; + @Nullable + protected final Column colBisqTradeFee; + @Nullable + protected final Column colTradeCost; + @Nullable + protected final Column colIsPaymentSent; + @Nullable + protected final Column colIsPaymentReceived; + @Nullable + protected final Column 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 isTradeDetailTblBuilder = () -> tableType.equals(TRADE_DETAIL_TBL); + + protected final Predicate isFiatTrade = (t) -> isFiatOffer.test(t.getOffer()); + + protected final Predicate isTaker = (t) -> t.getRole().toLowerCase().contains("taker"); + + // Column Value Functions + + protected final Function toAmount = (t) -> + isFiatTrade.test(t) + ? t.getTradeAmountAsLong() + : t.getTradeVolume(); + + protected final Function toTradeVolume = (t) -> + isFiatTrade.test(t) + ? t.getTradeVolume() + : t.getTradeAmountAsLong(); + + protected final Function toMarket = (t) -> + t.getOffer().getBaseCurrencyCode() + "/" + + t.getOffer().getCounterCurrencyCode(); + + protected final Function toPaymentCurrencyCode = (t) -> + isFiatTrade.test(t) + ? t.getOffer().getCounterCurrencyCode() + : t.getOffer().getBaseCurrencyCode(); + + + protected final Function 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 toPriceDeviation = (t) -> + t.getOffer().getUseMarketBasedPrice() + ? formatToPercent(t.getOffer().getMarketPriceMargin()) + : "N/A"; + + protected final Function toMyMinerTxFee = (t) -> + isTaker.test(t) + ? t.getTxFeeAsLong() + : t.getOffer().getTxFee(); + + + // TODO Move to TradeUtil ? + protected final BiFunction 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 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 toMyMakerOrTakerFee = (t) -> + isTaker.test(t) + ? t.getTakerFeeAsLong() + : t.getOffer().getMakerFee(); + + // TODO Move to TradeUtil ? SEE ClosedTradesViewModel # getDirectionLabel + protected final Function 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 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 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; + } + } +} diff --git a/cli/src/main/java/bisq/cli/table/builder/AbstractTradeTableBuilder.java b/cli/src/main/java/bisq/cli/table/builder/AbstractTradeTableBuilder.java new file mode 100644 index 0000000000..f94c19f63e --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/builder/AbstractTradeTableBuilder.java @@ -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 colTradeId; + @Nullable + protected final Column colCreateDate; + @Nullable + protected final Column colMarket; + @Nullable + protected final MixedPriceColumn colMixedPrice; + @Nullable + protected final Column colPriceDeviation; + @Nullable + protected final Column colAmountInBtc; + @Nullable + protected final MixedVolumeColumn colMixedAmount; + @Nullable + protected final Column colCurrency; + @Nullable + protected final MixedTradeFeeColumn colMixedTradeFee; + @Nullable + protected final Column colBuyerDeposit; + @Nullable + protected final Column colSellerDeposit; + @Nullable + protected final Column colOfferType; + @Nullable + protected final Column colStatus; + protected final Column 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 isFiatTrade = (t) -> isFiatOffer.test(t.getOffer()); + + protected final Predicate isTaker = (t) -> t.getRole().toLowerCase().contains("taker"); + + protected final Function toPaymentCurrencyCode = (t) -> + isFiatTrade.test(t) + ? t.getOffer().getCounterCurrencyCode() + : t.getOffer().getBaseCurrencyCode(); + + protected final Function toAmount = (t) -> + isFiatTrade.test(t) + ? t.getTradeAmountAsLong() + : t.getTradeVolume(); + + protected final Function toMinerTxFee = (t) -> + isTaker.test(t) + ? t.getTxFeeAsLong() + : t.getOffer().getTxFee(); + + protected final Function toMakerTakerFee = (t) -> + isTaker.test(t) + ? t.getTakerFeeAsLong() + : t.getOffer().getMakerFee(); + + protected final Function toTradeCost = (t) -> + isFiatTrade.test(t) + ? t.getTradeVolume() + : t.getTradeAmountAsLong(); +} diff --git a/cli/src/main/java/bisq/cli/table/builder/AddressBalanceTableBuilder.java b/cli/src/main/java/bisq/cli/table/builder/AddressBalanceTableBuilder.java new file mode 100644 index 0000000000..21b6e2ee99 --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/builder/AddressBalanceTableBuilder.java @@ -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 . + */ + +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 colAddress; + private final Column colAvailableBalance; + private final Column colConfirmations; + private final Column 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 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()); + } +} diff --git a/cli/src/main/java/bisq/cli/table/builder/BsqBalanceTableBuilder.java b/cli/src/main/java/bisq/cli/table/builder/BsqBalanceTableBuilder.java new file mode 100644 index 0000000000..01c3877198 --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/builder/BsqBalanceTableBuilder.java @@ -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 . + */ + +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 colAvailableConfirmedBalance; + private final Column colUnverifiedBalance; + private final Column colUnconfirmedChangeBalance; + private final Column colLockedForVotingBalance; + private final Column colLockupBondsBalance; + private final Column 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()); + } +} diff --git a/cli/src/main/java/bisq/cli/table/builder/BtcBalanceTableBuilder.java b/cli/src/main/java/bisq/cli/table/builder/BtcBalanceTableBuilder.java new file mode 100644 index 0000000000..b399f70370 --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/builder/BtcBalanceTableBuilder.java @@ -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 . + */ + +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 colAvailableBalance; + private final Column colReservedBalance; + private final Column colTotalAvailableBalance; + private final Column 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()); + } +} diff --git a/cli/src/main/java/bisq/cli/table/builder/FailedTradeTableBuilder.java b/cli/src/main/java/bisq/cli/table/builder/FailedTradeTableBuilder.java new file mode 100644 index 0000000000..a4a6084347 --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/builder/FailedTradeTableBuilder.java @@ -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"); + }); + } +} diff --git a/cli/src/main/java/bisq/cli/table/builder/OfferTableBuilder.java b/cli/src/main/java/bisq/cli/table/builder/OfferTableBuilder.java new file mode 100644 index 0000000000..08d19c0910 --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/builder/OfferTableBuilder.java @@ -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 . + */ + +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 colOfferId = new StringColumn(COL_HEADER_UUID, LEFT); + private final Column colDirection = new StringColumn(COL_HEADER_DIRECTION, LEFT); + private final Column colAmount = new SatoshiColumn("Temp Amount", NONE); + private final Column colMinAmount = new SatoshiColumn("Temp Min Amount", NONE); + private final Column colPaymentMethod = new StringColumn(COL_HEADER_PAYMENT_METHOD, LEFT); + private final Column colCreateDate = new Iso8601DateTimeColumn(COL_HEADER_CREATION_DATE); + + OfferTableBuilder(List protos) { + super(OFFER_TBL, protos); + } + + public Table build() { + List offers = protos.stream().map(p -> (OfferInfo) p).collect(Collectors.toList()); + return isShowingFiatOffers.get() + ? buildFiatOfferTable(offers) + : buildCryptoCurrencyOfferTable(offers); + } + + @SuppressWarnings("ConstantConditions") + public Table buildFiatOfferTable(List offers) { + @Nullable + Column colEnabled = enabledColumn.get(); // Not boolean: YES, NO, or PENDING + Column colFiatPrice = new FiatColumn(format(COL_HEADER_DETAILED_PRICE, fiatTradeCurrency.get())); + Column colFiatVolume = new FiatColumn(format("Temp Volume (%s)", fiatTradeCurrency.get()), NONE, VOLUME); + Column colMinFiatVolume = new FiatColumn(format("Temp Min Volume (%s)", fiatTradeCurrency.get()), NONE, VOLUME); + @Nullable + Column 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 offers) { + @Nullable + Column colEnabled = enabledColumn.get(); // Not boolean: YES, NO, or PENDING + Column colBtcPrice = new SatoshiColumn(format(COL_HEADER_DETAILED_PRICE_OF_ALTCOIN, altcoinTradeCurrency.get())); + Column colBtcVolume = new AltcoinColumn(format("Temp Volume (%s)", altcoinTradeCurrency.get()), + NONE, + ALTCOIN_OFFER_VOLUME); + Column colMinBtcVolume = new AltcoinColumn(format("Temp Min Volume (%s)", altcoinTradeCurrency.get()), + NONE, + ALTCOIN_OFFER_VOLUME); + @Nullable + Column 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 firstOfferInList = () -> (OfferInfo) protos.get(0); + private final Supplier isShowingMyOffers = () -> firstOfferInList.get().getIsMyOffer(); + private final Supplier isShowingFiatOffers = () -> isFiatOffer.test(firstOfferInList.get()); + private final Supplier fiatTradeCurrency = () -> firstOfferInList.get().getCounterCurrencyCode(); + private final Supplier altcoinTradeCurrency = () -> firstOfferInList.get().getBaseCurrencyCode(); + private final Supplier isShowingBsqOffers = () -> + !isFiatOffer.test(firstOfferInList.get()) && altcoinTradeCurrency.get().equals("BSQ"); + + @Nullable // Not a boolean column: YES, NO, or PENDING. + private final Supplier enabledColumn = () -> + isShowingMyOffers.get() + ? new StringColumn(COL_HEADER_ENABLED, LEFT) + : null; + @Nullable + private final Supplier fiatTriggerPriceColumn = () -> + isShowingMyOffers.get() + ? new FiatColumn(format(COL_HEADER_TRIGGER_PRICE, fiatTradeCurrency.get()), RIGHT, TRIGGER_PRICE) + : null; + @Nullable + private final Supplier altcoinTriggerPriceColumn = () -> + isShowingMyOffers.get() && !isShowingBsqOffers.get() + ? new AltcoinColumn(format(COL_HEADER_TRIGGER_PRICE, altcoinTradeCurrency.get()), RIGHT, ALTCOIN_TRIGGER_PRICE) + : null; + + private final Function toEnabled = (o) -> { + if (o.getIsMyOffer() && o.getIsMyPendingOffer()) + return "PENDING"; + else + return o.getIsActivated() ? "YES" : "NO"; + }; + + private final Function toMirroredDirection = (d) -> + d.equalsIgnoreCase(BUY.name()) ? SELL.name() : BUY.name(); + + private final Function 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 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 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()); + }; +} diff --git a/cli/src/main/java/bisq/cli/table/builder/OpenTradeTableBuilder.java b/cli/src/main/java/bisq/cli/table/builder/OpenTradeTableBuilder.java new file mode 100644 index 0000000000..a388d03664 --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/builder/OpenTradeTableBuilder.java @@ -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()); + }); + } +} diff --git a/cli/src/main/java/bisq/cli/table/builder/PaymentAccountTableBuilder.java b/cli/src/main/java/bisq/cli/table/builder/PaymentAccountTableBuilder.java new file mode 100644 index 0000000000..f02b8dcd72 --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/builder/PaymentAccountTableBuilder.java @@ -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 . + */ + +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 colName; + private final Column colCurrency; + private final Column colPaymentMethod; + private final Column 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 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); + } +} diff --git a/cli/src/main/java/bisq/cli/table/builder/TableBuilder.java b/cli/src/main/java/bisq/cli/table/builder/TableBuilder.java new file mode 100644 index 0000000000..4084be33a9 --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/builder/TableBuilder.java @@ -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 . + */ + +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()); + } + } +} diff --git a/cli/src/main/java/bisq/cli/table/builder/TableBuilderConstants.java b/cli/src/main/java/bisq/cli/table/builder/TableBuilderConstants.java new file mode 100644 index 0000000000..1ea28b63ec --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/builder/TableBuilderConstants.java @@ -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 . + */ + +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"; +} diff --git a/cli/src/main/java/bisq/cli/table/builder/TableType.java b/cli/src/main/java/bisq/cli/table/builder/TableType.java new file mode 100644 index 0000000000..0f6afef25b --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/builder/TableType.java @@ -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 . + */ + +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 +} diff --git a/cli/src/main/java/bisq/cli/table/builder/TradeDetailTableBuilder.java b/cli/src/main/java/bisq/cli/table/builder/TradeDetailTableBuilder.java new file mode 100644 index 0000000000..693a0c916a --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/builder/TradeDetailTableBuilder.java @@ -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 . + */ + +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> 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> defineColumnList() { + List> 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; + } +} diff --git a/cli/src/main/java/bisq/cli/table/builder/TradeTableColumnSupplier.java b/cli/src/main/java/bisq/cli/table/builder/TradeTableColumnSupplier.java new file mode 100644 index 0000000000..967ee1779f --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/builder/TradeTableColumnSupplier.java @@ -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 . + */ + +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 trades; + + public TradeTableColumnSupplier(TableType tableType, List trades) { + this.tableType = tableType; + this.trades = trades; + } + + private final Supplier isTradeDetailTblBuilder = () -> getTableType().equals(TRADE_DETAIL_TBL); + private final Supplier isOpenTradeTblBuilder = () -> getTableType().equals(OPEN_TRADE_TBL); + private final Supplier isClosedTradeTblBuilder = () -> getTableType().equals(CLOSED_TRADE_TBL); + private final Supplier isFailedTradeTblBuilder = () -> getTableType().equals(FAILED_TRADE_TBL); + private final Supplier firstRow = () -> getTrades().get(0); + private final Predicate isFiatOffer = (o) -> o.getBaseCurrencyCode().equals("BTC"); + private final Predicate isFiatTrade = (t) -> isFiatOffer.test(t.getOffer()); + private final Predicate isTaker = (t) -> t.getRole().toLowerCase().contains("taker"); + + final Supplier tradeIdColumn = () -> isTradeDetailTblBuilder.get() + ? new StringColumn(COL_HEADER_TRADE_SHORT_ID) + : new StringColumn(COL_HEADER_TRADE_ID); + + final Supplier createDateColumn = () -> isTradeDetailTblBuilder.get() + ? null + : new Iso8601DateTimeColumn(COL_HEADER_DATE_TIME); + + final Supplier marketColumn = () -> isTradeDetailTblBuilder.get() + ? null + : new StringColumn(COL_HEADER_MARKET); + + private final Function> 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> priceColumn = () -> isTradeDetailTblBuilder.get() + ? toDetailedPriceColumn.apply(firstRow.get()) + : new MixedPriceColumn(COL_HEADER_PRICE); + + final Supplier> priceDeviationColumn = () -> isTradeDetailTblBuilder.get() + ? null + : new StringColumn(COL_HEADER_DEVIATION, RIGHT); + + final Supplier currencyColumn = () -> isTradeDetailTblBuilder.get() + ? null + : new StringColumn(COL_HEADER_CURRENCY); + + private final Function> 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> amountInBtcColumn = () -> isTradeDetailTblBuilder.get() + ? toDetailedAmountColumn.apply(firstRow.get()) + : new BtcColumn(COL_HEADER_AMOUNT_IN_BTC); + + final Supplier mixedAmountColumn = () -> isTradeDetailTblBuilder.get() + ? null + : new MixedVolumeColumn(COL_HEADER_AMOUNT); + + final Supplier> minerTxFeeColumn = () -> isTradeDetailTblBuilder.get() || isClosedTradeTblBuilder.get() + ? new SatoshiColumn(COL_HEADER_TX_FEE) + : null; + + final Supplier mixedTradeFeeColumn = () -> isTradeDetailTblBuilder.get() + ? null + : new MixedTradeFeeColumn(COL_HEADER_TRADE_FEE); + + final Supplier paymentMethodColumn = () -> isTradeDetailTblBuilder.get() || isClosedTradeTblBuilder.get() + ? null + : new StringColumn(COL_HEADER_PAYMENT_METHOD, LEFT); + + final Supplier roleColumn = () -> + isTradeDetailTblBuilder.get() || isOpenTradeTblBuilder.get() || isFailedTradeTblBuilder.get() + ? new StringColumn(COL_HEADER_TRADE_ROLE) + : null; + + final Function> toSecurityDepositColumn = (name) -> isClosedTradeTblBuilder.get() + ? new SatoshiColumn(name) + : null; + + final Supplier offerTypeColumn = () -> isTradeDetailTblBuilder.get() + ? null + : new StringColumn(COL_HEADER_OFFER_TYPE); + + final Supplier statusDescriptionColumn = () -> isTradeDetailTblBuilder.get() + ? null + : new StringColumn(COL_HEADER_STATUS); + + private final Function> toBooleanColumn = BooleanColumn::new; + + final Supplier> depositPublishedColumn = () -> isTradeDetailTblBuilder.get() + ? toBooleanColumn.apply(COL_HEADER_TRADE_DEPOSIT_PUBLISHED) + : null; + + final Supplier> depositConfirmedColumn = () -> isTradeDetailTblBuilder.get() + ? toBooleanColumn.apply(COL_HEADER_TRADE_DEPOSIT_CONFIRMED) + : null; + + final Supplier> payoutPublishedColumn = () -> isTradeDetailTblBuilder.get() + ? toBooleanColumn.apply(COL_HEADER_TRADE_PAYOUT_PUBLISHED) + : null; + + final Supplier> fundsWithdrawnColumn = () -> isTradeDetailTblBuilder.get() + ? toBooleanColumn.apply(COL_HEADER_TRADE_WITHDRAWN) + : null; + + final Supplier> 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 toPaymentCurrencyCode = (t) -> + isFiatTrade.test(t) + ? t.getOffer().getCounterCurrencyCode() + : t.getOffer().getBaseCurrencyCode(); + + final Supplier> 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> 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> 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 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> 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; + } + }; +} diff --git a/cli/src/main/java/bisq/cli/table/builder/TransactionTableBuilder.java b/cli/src/main/java/bisq/cli/table/builder/TransactionTableBuilder.java new file mode 100644 index 0000000000..7227b144ab --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/builder/TransactionTableBuilder.java @@ -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 . + */ + +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 colTxId; + private final Column colIsConfirmed; + private final Column colInputSum; + private final Column colOutputSum; + private final Column colTxFee; + private final Column 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 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()); + } + } +} diff --git a/cli/src/main/java/bisq/cli/table/column/AbstractColumn.java b/cli/src/main/java/bisq/cli/table/column/AbstractColumn.java new file mode 100644 index 0000000000..85a75837df --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/column/AbstractColumn.java @@ -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 . + */ + +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, T> implements Column { + + // 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 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; + } + } +} + diff --git a/cli/src/main/java/bisq/cli/table/column/AltcoinColumn.java b/cli/src/main/java/bisq/cli/table/column/AltcoinColumn.java new file mode 100644 index 0000000000..4c59eb0332 --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/column/AltcoinColumn.java @@ -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 . + */ + +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 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); + } + }; +} diff --git a/cli/src/main/java/bisq/cli/table/column/BooleanColumn.java b/cli/src/main/java/bisq/cli/table/column/BooleanColumn.java new file mode 100644 index 0000000000..75cc17fc82 --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/column/BooleanColumn.java @@ -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 . + */ + +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 { + + private static final String DEFAULT_TRUE_AS_STRING = "YES"; + private static final String DEFAULT_FALSE_AS_STRING = "NO"; + + private final List rows = new ArrayList<>(); + + private final Predicate 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 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; + } +} diff --git a/cli/src/main/java/bisq/cli/table/column/BtcColumn.java b/cli/src/main/java/bisq/cli/table/column/BtcColumn.java new file mode 100644 index 0000000000..3d2062d8df --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/column/BtcColumn.java @@ -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(); + } +} diff --git a/cli/src/main/java/bisq/cli/table/column/Column.java b/cli/src/main/java/bisq/cli/table/column/Column.java new file mode 100644 index 0000000000..442c309615 --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/column/Column.java @@ -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 . + */ + +package bisq.cli.table.column; + +import java.util.List; + +public interface Column { + + 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 + */ + List 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 justify(); + + /** + * Returns JUSTIFICATION value (RIGHT|LEFT|NONE) for the column. + * + * @return column JUSTIFICATION + */ + JUSTIFICATION getJustification(); +} diff --git a/cli/src/main/java/bisq/cli/table/column/DoubleColumn.java b/cli/src/main/java/bisq/cli/table/column/DoubleColumn.java new file mode 100644 index 0000000000..62ad94e28f --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/column/DoubleColumn.java @@ -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 . + */ + +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 { + + protected final List rows = new ArrayList<>(); + + protected final Predicate 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 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; + } +} diff --git a/cli/src/main/java/bisq/cli/table/column/FiatColumn.java b/cli/src/main/java/bisq/cli/table/column/FiatColumn.java new file mode 100644 index 0000000000..e9b940ee9d --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/column/FiatColumn.java @@ -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 . + */ + +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; + } +} diff --git a/cli/src/main/java/bisq/cli/table/column/Iso8601DateTimeColumn.java b/cli/src/main/java/bisq/cli/table/column/Iso8601DateTimeColumn.java new file mode 100644 index 0000000000..0cd4b98ebe --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/column/Iso8601DateTimeColumn.java @@ -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 . + */ + +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; + } +} diff --git a/cli/src/main/java/bisq/cli/table/column/LongColumn.java b/cli/src/main/java/bisq/cli/table/column/LongColumn.java new file mode 100644 index 0000000000..3875f35a89 --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/column/LongColumn.java @@ -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 . + */ + +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 { + + protected final List rows = new ArrayList<>(); + + protected final Predicate 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 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; + } +} diff --git a/cli/src/main/java/bisq/cli/table/column/MixedPriceColumn.java b/cli/src/main/java/bisq/cli/table/column/MixedPriceColumn.java new file mode 100644 index 0000000000..d8beaf80fe --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/column/MixedPriceColumn.java @@ -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 . + */ + +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(); + } +} diff --git a/cli/src/main/java/bisq/cli/table/column/MixedTradeFeeColumn.java b/cli/src/main/java/bisq/cli/table/column/MixedTradeFeeColumn.java new file mode 100644 index 0000000000..0d3d8fa5ab --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/column/MixedTradeFeeColumn.java @@ -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 . + */ + +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(); + } +} diff --git a/cli/src/main/java/bisq/cli/table/column/MixedVolumeColumn.java b/cli/src/main/java/bisq/cli/table/column/MixedVolumeColumn.java new file mode 100644 index 0000000000..1de2a24ecc --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/column/MixedVolumeColumn.java @@ -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 . + */ + +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(); + } +} diff --git a/cli/src/main/java/bisq/cli/table/column/NumberColumn.java b/cli/src/main/java/bisq/cli/table/column/NumberColumn.java new file mode 100644 index 0000000000..42f832f616 --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/column/NumberColumn.java @@ -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 . + */ + +package bisq.cli.table.column; + +/** + * Abstract superclass for numeric Columns. + * + * @param the subclass column's type (LongColumn, IntegerColumn, ...) + * @param the subclass column's numeric Java type (Long, Integer, ...) + */ +abstract class NumberColumn, + T extends Number> extends AbstractColumn implements Column { + + public NumberColumn(String name, JUSTIFICATION justification) { + super(name, justification); + } +} diff --git a/cli/src/main/java/bisq/cli/table/column/SatoshiColumn.java b/cli/src/main/java/bisq/cli/table/column/SatoshiColumn.java new file mode 100644 index 0000000000..70f6f59e2f --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/column/SatoshiColumn.java @@ -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 . + */ + +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(); + } +} diff --git a/cli/src/main/java/bisq/cli/table/column/StringColumn.java b/cli/src/main/java/bisq/cli/table/column/StringColumn.java new file mode 100644 index 0000000000..fa31aa286f --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/column/StringColumn.java @@ -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 . + */ + +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 { + + private final List rows = new ArrayList<>(); + + private final Predicate 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 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; + } +} diff --git a/cli/src/main/java/bisq/cli/table/column/ZippedStringColumns.java b/cli/src/main/java/bisq/cli/table/column/ZippedStringColumns.java new file mode 100644 index 0000000000..e2e6237608 --- /dev/null +++ b/cli/src/main/java/bisq/cli/table/column/ZippedStringColumns.java @@ -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 . + */ + +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 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"); + }); + } +} diff --git a/cli/src/test/java/bisq/cli/AbstractCliTest.java b/cli/src/test/java/bisq/cli/AbstractCliTest.java new file mode 100644 index 0000000000..6e35142d0a --- /dev/null +++ b/cli/src/test/java/bisq/cli/AbstractCliTest.java @@ -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 isInsertOrDelete = (operation) -> + operation.equals(INSERT) || operation.equals(DELETE); + Predicate isWhitespace = (text) -> text.trim().isEmpty(); + boolean hasNonWhitespaceDiffs = false; + if (!oldOutput.equals(newOutput)) { + DiffMatchPatch dmp = new DiffMatchPatch(); + LinkedList 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); + } +} diff --git a/cli/src/test/java/bisq/cli/table/AddressCliOutputDiffTest.java b/cli/src/test/java/bisq/cli/table/AddressCliOutputDiffTest.java new file mode 100644 index 0000000000..2f8c8542cf --- /dev/null +++ b/cli/src/test/java/bisq/cli/table/AddressCliOutputDiffTest.java @@ -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 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); + } +} diff --git a/cli/src/test/java/bisq/cli/table/GetBalanceCliOutputDiffTest.java b/cli/src/test/java/bisq/cli/table/GetBalanceCliOutputDiffTest.java new file mode 100644 index 0000000000..0fa6d11c3d --- /dev/null +++ b/cli/src/test/java/bisq/cli/table/GetBalanceCliOutputDiffTest.java @@ -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); + } +} diff --git a/cli/src/test/java/bisq/cli/table/GetOffersCliOutputDiffTest.java b/cli/src/test/java/bisq/cli/table/GetOffersCliOutputDiffTest.java new file mode 100644 index 0000000000..d97c9f8ded --- /dev/null +++ b/cli/src/test/java/bisq/cli/table/GetOffersCliOutputDiffTest.java @@ -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 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); + } + } +} diff --git a/cli/src/test/java/bisq/cli/table/GetTradeCliOutputDiffTest.java b/cli/src/test/java/bisq/cli/table/GetTradeCliOutputDiffTest.java new file mode 100644 index 0000000000..8516033ee6 --- /dev/null +++ b/cli/src/test/java/bisq/cli/table/GetTradeCliOutputDiffTest.java @@ -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); + } +} diff --git a/cli/src/test/java/bisq/cli/table/GetTransactionCliOutputDiffTest.java b/cli/src/test/java/bisq/cli/table/GetTransactionCliOutputDiffTest.java new file mode 100644 index 0000000000..13fb639b6e --- /dev/null +++ b/cli/src/test/java/bisq/cli/table/GetTransactionCliOutputDiffTest.java @@ -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); + } +} diff --git a/cli/src/test/java/bisq/cli/table/PaymentAccountsCliOutputDiffTest.java b/cli/src/test/java/bisq/cli/table/PaymentAccountsCliOutputDiffTest.java new file mode 100644 index 0000000000..bf1ca88a1c --- /dev/null +++ b/cli/src/test/java/bisq/cli/table/PaymentAccountsCliOutputDiffTest.java @@ -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"); + } + } + +} diff --git a/core/src/main/java/bisq/core/api/CoreApi.java b/core/src/main/java/bisq/core/api/CoreApi.java index 380fd670ca..510c4cfcbc 100644 --- a/core/src/main/java/bisq/core/api/CoreApi.java +++ b/core/src/main/java/bisq/core/api/CoreApi.java @@ -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 getCryptoCurrencyPaymentMethods() { diff --git a/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java b/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java index dcce34a46b..0638ddbda4 100644 --- a/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java +++ b/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java @@ -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 = 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()); diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcPaymentAccountsService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcPaymentAccountsService.java index 1473f30fed..9ac400d100 100644 --- a/daemon/src/main/java/bisq/daemon/grpc/GrpcPaymentAccountsService.java +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcPaymentAccountsService.java @@ -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(); diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java index fa747c27d1..170f268a37 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java @@ -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) { diff --git a/proto/src/main/proto/grpc.proto b/proto/src/main/proto/grpc.proto index b84ea6ae4a..ec8235b404 100644 --- a/proto/src/main/proto/grpc.proto +++ b/proto/src/main/proto/grpc.proto @@ -311,7 +311,6 @@ message CreateCryptoCurrencyPaymentAccountRequest { string currencyCode = 2; string address = 3; bool tradeInstant = 4; - bool isBsqSwap = 5; } message CreateCryptoCurrencyPaymentAccountReply {