diff --git a/apitest/scripts/trade-simulation-utils.sh b/apitest/scripts/trade-simulation-utils.sh index c43beaf2ec..6b14622261 100755 --- a/apitest/scripts/trade-simulation-utils.sh +++ b/apitest/scripts/trade-simulation-utils.sh @@ -458,7 +458,7 @@ delayconfirmpaymentreceived() { } # This is a large function that should be broken up if it ever makes sense to not treat a trade -# execution simulation as an atomic operation. But we are not testing api methods here, just +# execution simulation as an bsq swap operation. But we are not testing api methods here, just # demonstrating how to use them to get through the trade protocol. It should work for any trade # between Bob & Alice, as long as Alice is maker, Bob is taker, and the offer to be taken is the # first displayed in Bob's getoffers command output. diff --git a/apitest/src/main/java/bisq/apitest/linux/BashCommand.java b/apitest/src/main/java/bisq/apitest/linux/BashCommand.java index f40d9b06c9..90f990d445 100644 --- a/apitest/src/main/java/bisq/apitest/linux/BashCommand.java +++ b/apitest/src/main/java/bisq/apitest/linux/BashCommand.java @@ -24,7 +24,7 @@ import java.util.List; import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; +import javax.annotation.Nullable; import static bisq.apitest.config.ApiTestConfig.BASH_PATH_VALUE; import static java.lang.management.ManagementFactory.getRuntimeMXBean; @@ -33,7 +33,9 @@ import static java.lang.management.ManagementFactory.getRuntimeMXBean; public class BashCommand { private int exitStatus = -1; + @Nullable private String output; + @Nullable private String error; private final String command; @@ -92,6 +94,7 @@ public class BashCommand { } // TODO return Optional + @Nullable public String getOutput() { return this.output; } @@ -101,7 +104,6 @@ public class BashCommand { return this.error; } - @NotNull private List tokenizeSystemCommand() { return new ArrayList<>() {{ add(BASH_PATH_VALUE); diff --git a/apitest/src/main/java/bisq/apitest/linux/SystemCommandExecutor.java b/apitest/src/main/java/bisq/apitest/linux/SystemCommandExecutor.java index 28c0fde235..dbf962ea49 100644 --- a/apitest/src/main/java/bisq/apitest/linux/SystemCommandExecutor.java +++ b/apitest/src/main/java/bisq/apitest/linux/SystemCommandExecutor.java @@ -57,15 +57,13 @@ class SystemCommandExecutor { private ThreadedStreamHandler errorStreamHandler; public SystemCommandExecutor(final List cmdOptions) { - if (log.isDebugEnabled()) - log.debug("cmd options {}", cmdOptions.toString()); - if (cmdOptions.isEmpty()) throw new IllegalStateException("No command params specified."); if (cmdOptions.contains("sudo")) throw new IllegalStateException("'sudo' commands are prohibited."); + log.trace("System cmd options {}", cmdOptions); this.cmdOptions = cmdOptions; } diff --git a/apitest/src/test/java/bisq/apitest/ApiTestCase.java b/apitest/src/test/java/bisq/apitest/ApiTestCase.java index fb4938d479..315561cbb6 100644 --- a/apitest/src/test/java/bisq/apitest/ApiTestCase.java +++ b/apitest/src/test/java/bisq/apitest/ApiTestCase.java @@ -17,6 +17,8 @@ package bisq.apitest; +import java.time.Duration; + import java.io.IOException; import java.util.concurrent.ExecutionException; @@ -32,9 +34,9 @@ import static bisq.apitest.config.ApiTestRateMeterInterceptorConfig.getTestRateM import static bisq.apitest.config.BisqAppConfig.alicedaemon; import static bisq.apitest.config.BisqAppConfig.arbdaemon; import static bisq.apitest.config.BisqAppConfig.bobdaemon; +import static com.google.common.util.concurrent.Uninterruptibles.sleepUninterruptibly; import static java.net.InetAddress.getLoopbackAddress; import static java.util.Arrays.stream; -import static java.util.concurrent.TimeUnit.MILLISECONDS; @@ -131,11 +133,7 @@ public class ApiTestCase { } protected static void sleep(long ms) { - try { - MILLISECONDS.sleep(ms); - } catch (InterruptedException ignored) { - // empty - } + sleepUninterruptibly(Duration.ofMillis(ms)); } protected final String testName(TestInfo testInfo) { diff --git a/apitest/src/test/java/bisq/apitest/method/MethodTest.java b/apitest/src/test/java/bisq/apitest/method/MethodTest.java index c2701ffc44..2a9ad4f329 100644 --- a/apitest/src/test/java/bisq/apitest/method/MethodTest.java +++ b/apitest/src/test/java/bisq/apitest/method/MethodTest.java @@ -31,7 +31,12 @@ import java.io.PrintWriter; import java.util.function.Function; import java.util.stream.Collectors; +import org.slf4j.Logger; + +import javax.annotation.Nullable; + import static bisq.apitest.config.ApiTestRateMeterInterceptorConfig.getTestRateMeterInterceptorConfig; +import static java.lang.String.format; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.stream; import static org.junit.jupiter.api.Assertions.fail; @@ -39,6 +44,7 @@ import static org.junit.jupiter.api.Assertions.fail; import bisq.apitest.ApiTestCase; +import bisq.apitest.linux.BashCommand; import bisq.cli.GrpcClient; public class MethodTest extends ApiTestCase { @@ -144,14 +150,42 @@ public class MethodTest extends ApiTestCase { protected final bisq.core.payment.PaymentAccount createPaymentAccount(GrpcClient grpcClient, String jsonString) { // Normally, we do asserts on the protos from the gRPC service, but in this // case we need a bisq.core.payment.PaymentAccount so it can be cast to its - // sub type. + // sub-type. var paymentAccount = grpcClient.createPaymentAccount(jsonString); return bisq.core.payment.PaymentAccount.fromProto(paymentAccount, CORE_PROTO_RESOLVER); } - // Static conveniences for test methods and test case fixture setups. - protected static String encodeToHex(String s) { return Utilities.bytesAsHexString(s.getBytes(UTF_8)); } + + protected void verifyNoLoggedNodeExceptions() { + var loggedExceptions = getNodeExceptionMessages(); + if (loggedExceptions != null) { + String err = format("Exception(s) found in daemon log(s):%n%s", loggedExceptions); + fail(err); + } + } + + protected void printNodeExceptionMessages(Logger log) { + var loggedExceptions = getNodeExceptionMessages(); + if (loggedExceptions != null) + log.error("Exception(s) found in daemon log(s):\n{}", loggedExceptions); + } + + @Nullable + protected static String getNodeExceptionMessages() { + var nodeLogsSpec = config.rootAppDataDir.getAbsolutePath() + "/bisq-BTC_REGTEST_*_dao/bisq.log"; + var grep = "grep Exception " + nodeLogsSpec; + var bashCommand = new BashCommand(grep); + try { + bashCommand.run(); + } catch (IOException | InterruptedException ex) { + fail("Bash command execution error: " + ex); + } + if (bashCommand.getError() == null) + return bashCommand.getOutput(); + else + throw new IllegalStateException("Bash command execution error: " + bashCommand.getError()); + } } 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 2d2e5fc6d7..0f01407412 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java @@ -66,6 +66,16 @@ public abstract class AbstractOfferTest extends MethodTest { 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); + } // Mkt Price Margin value of offer returned from server is scaled down by 10^-2. protected final Function scaledDownMktPriceMargin = (mktPriceMargin) -> diff --git a/apitest/src/test/java/bisq/apitest/method/offer/BsqSwapOfferTest.java b/apitest/src/test/java/bisq/apitest/method/offer/BsqSwapOfferTest.java new file mode 100644 index 0000000000..1fbcd77cd3 --- /dev/null +++ b/apitest/src/test/java/bisq/apitest/method/offer/BsqSwapOfferTest.java @@ -0,0 +1,173 @@ +/* + * 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.offer; + +import bisq.proto.grpc.BsqSwapOfferInfo; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +import static bisq.apitest.config.ApiTestConfig.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.OfferDirection.BUY; + +@Disabled +@Slf4j +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class BsqSwapOfferTest extends AbstractOfferTest { + + @BeforeAll + public static void setUp() { + AbstractOfferTest.setUp(); + createBsqSwapBsqPaymentAccounts(); + } + + @BeforeEach + public void generateBtcBlock() { + genBtcBlocksThenWait(1, 2000); + } + + @Test + @Order(1) + public void testGetBalancesBeforeCreateOffers() { + var alicesBalances = aliceClient.getBalances(); + log.debug("Alice's Before Trade Balance:\n{}", formatBalancesTbls(alicesBalances)); + var bobsBalances = bobClient.getBalances(); + log.debug("Bob's Before Trade Balance:\n{}", formatBalancesTbls(bobsBalances)); + } + + @Test + @Order(2) + public void testAliceCreateBsqSwapBuyOffer1() { + createBsqSwapOffer(); + } + + @Test + @Order(3) + public void testAliceCreateBsqSwapBuyOffer2() { + createBsqSwapOffer(); + } + + @Test + @Order(4) + public void testAliceCreateBsqSwapBuyOffer3() { + createBsqSwapOffer(); + } + + @Test + @Order(5) + public void testAliceCreateBsqSwapBuyOffer4() { + createBsqSwapOffer(); + } + + @Test + @Order(6) + public void testGetMyBsqSwapOffers() { + var offers = aliceClient.getMyBsqSwapBsqOffersSortedByDate(); + assertEquals(4, offers.size()); + } + + @Test + @Order(7) + public void testGetAvailableBsqSwapOffers() { + var offers = bobClient.getBsqSwapOffersSortedByDate(); + assertEquals(4, offers.size()); + } + + @Test + @Order(8) + public void testGetBalancesAfterCreateOffers() { + var alicesBalances = aliceClient.getBalances(); + log.debug("Alice's After Trade Balance:\n{}", formatBalancesTbls(alicesBalances)); + var bobsBalances = bobClient.getBalances(); + log.debug("Bob's After Trade Balance:\n{}", formatBalancesTbls(bobsBalances)); + } + + private void createBsqSwapOffer() { + var bsqSwapOffer = aliceClient.createBsqSwapOffer(BUY.name(), + 1_000_000L, + 1_000_000L, + "0.00005", + alicesBsqAcct.getId()); + log.debug("BsqSwap Sell BSQ (Buy BTC) OFFER:\n{}", bsqSwapOffer); + var newOfferId = bsqSwapOffer.getId(); + assertNotEquals("", newOfferId); + assertEquals(BUY.name(), bsqSwapOffer.getDirection()); + assertEquals(5_000, bsqSwapOffer.getPrice()); + assertEquals(1_000_000L, bsqSwapOffer.getAmount()); + assertEquals(1_000_000L, bsqSwapOffer.getMinAmount()); + // assertEquals(alicesBsqAcct.getId(), atomicOffer.getMakerPaymentAccountId()); + assertEquals(BSQ, bsqSwapOffer.getBaseCurrencyCode()); + assertEquals(BTC, bsqSwapOffer.getCounterCurrencyCode()); + + testGetMyBsqSwapOffer(bsqSwapOffer); + testGetBsqSwapOffer(bsqSwapOffer); + } + + private void testGetMyBsqSwapOffer(BsqSwapOfferInfo bsqSwapOfferInfo) { + int numFetchAttempts = 0; + while (true) { + try { + numFetchAttempts++; + var fetchedBsqSwapOffer = aliceClient.getMyBsqSwapOffer(bsqSwapOfferInfo.getId()); + assertEquals(bsqSwapOfferInfo.getId(), fetchedBsqSwapOffer.getId()); + log.debug("Alice found her (my) new bsq swap offer on attempt # {}.", numFetchAttempts); + break; + } catch (Exception ex) { + log.warn(ex.getMessage()); + + if (numFetchAttempts >= 9) + fail(format("Alice giving up on fetching her (my) bsq swap offer after %d attempts.", numFetchAttempts), ex); + + sleep(1000); + } + } + } + + private void testGetBsqSwapOffer(BsqSwapOfferInfo bsqSwapOfferInfo) { + int numFetchAttempts = 0; + while (true) { + try { + numFetchAttempts++; + var fetchedBsqSwapOffer = bobClient.getBsqSwapOffer(bsqSwapOfferInfo.getId()); + assertEquals(bsqSwapOfferInfo.getId(), fetchedBsqSwapOffer.getId()); + log.debug("Bob found new available bsq swap offer on attempt # {}.", numFetchAttempts); + break; + } catch (Exception ex) { + log.warn(ex.getMessage()); + + if (numFetchAttempts > 9) + fail(format("Bob gave up on fetching available bsq swap offer after %d attempts.", numFetchAttempts), ex); + + sleep(1000); + } + } + } +} diff --git a/apitest/src/test/java/bisq/apitest/method/offer/CancelOfferTest.java b/apitest/src/test/java/bisq/apitest/method/offer/CancelOfferTest.java index 8db313583c..d0ec6d86f1 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/CancelOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/CancelOfferTest.java @@ -35,7 +35,7 @@ import org.junit.jupiter.api.TestMethodOrder; import static bisq.apitest.config.ApiTestConfig.BSQ; import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent; import static org.junit.jupiter.api.Assertions.assertEquals; -import static protobuf.OfferPayload.Direction.BUY; +import static protobuf.OfferDirection.BUY; @Disabled @Slf4j 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 652d7f50dc..b76a8b7aed 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/CreateBSQOffersTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/CreateBSQOffersTest.java @@ -40,8 +40,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static protobuf.OfferPayload.Direction.BUY; -import static protobuf.OfferPayload.Direction.SELL; +import static protobuf.OfferDirection.BUY; +import static protobuf.OfferDirection.SELL; @Disabled @Slf4j 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 715e05a92e..096c457e77 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingFixedPriceTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingFixedPriceTest.java @@ -36,8 +36,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static protobuf.OfferPayload.Direction.BUY; -import static protobuf.OfferPayload.Direction.SELL; +import static protobuf.OfferDirection.BUY; +import static protobuf.OfferDirection.SELL; @Disabled @Slf4j 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 391bb4c5a3..efa983caba 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java @@ -52,8 +52,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static protobuf.OfferPayload.Direction.BUY; -import static protobuf.OfferPayload.Direction.SELL; +import static protobuf.OfferDirection.BUY; +import static protobuf.OfferDirection.SELL; @SuppressWarnings("ConstantConditions") @Disabled 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 a947044d07..018b7277e3 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/EditOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/EditOfferTest.java @@ -47,8 +47,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static protobuf.OfferPayload.Direction.BUY; -import static protobuf.OfferPayload.Direction.SELL; +import static protobuf.OfferDirection.BUY; +import static protobuf.OfferDirection.SELL; @SuppressWarnings("ALL") @Disabled diff --git a/apitest/src/test/java/bisq/apitest/method/offer/ValidateCreateOfferTest.java b/apitest/src/test/java/bisq/apitest/method/offer/ValidateCreateOfferTest.java index 33626ee6c3..cc8a02f33d 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/ValidateCreateOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/ValidateCreateOfferTest.java @@ -35,7 +35,7 @@ import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAs import static java.lang.String.format; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static protobuf.OfferPayload.Direction.BUY; +import static protobuf.OfferDirection.BUY; @Disabled @Slf4j diff --git a/apitest/src/test/java/bisq/apitest/method/trade/BsqSwapTradeTest.java b/apitest/src/test/java/bisq/apitest/method/trade/BsqSwapTradeTest.java new file mode 100644 index 0000000000..dda9a66b7d --- /dev/null +++ b/apitest/src/test/java/bisq/apitest/method/trade/BsqSwapTradeTest.java @@ -0,0 +1,173 @@ +/* + * 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 bisq.proto.grpc.BsqSwapOfferInfo; +import bisq.proto.grpc.BsqSwapTradeInfo; + +import protobuf.BsqSwapTrade; + +import java.util.ArrayList; +import java.util.List; + +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +import static bisq.apitest.config.ApiTestConfig.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.OfferDirection.BUY; + + + +import bisq.apitest.method.offer.AbstractOfferTest; + +@Disabled +@Slf4j +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class BsqSwapTradeTest extends AbstractOfferTest { + + private static final String BISQ_FEE_CURRENCY_CODE = BSQ; + + // Long-running swap trade tests might want to check node logs for exceptions. + @Setter + private boolean checkForLoggedExceptions; + + @BeforeAll + public static void setUp() { + AbstractOfferTest.setUp(); + createBsqSwapBsqPaymentAccounts(); + } + + @BeforeEach + public void generateBtcBlock() { + genBtcBlocksThenWait(1, 2000); + } + + @Test + @Order(1) + public void testGetBalancesBeforeTrade() { + var alicesBalances = aliceClient.getBalances(); + log.info("Alice's Before Trade Balance:\n{}", formatBalancesTbls(alicesBalances)); + var bobsBalances = bobClient.getBalances(); + log.info("Bob's Before Trade Balance:\n{}", formatBalancesTbls(bobsBalances)); + } + + @Test + @Order(2) + public void testAliceCreateBsqSwapBuyOffer() { + var bsqSwapOffer = aliceClient.createBsqSwapOffer(BUY.name(), + 1_000_000L, + 1_000_000L, + "0.00005", + alicesBsqAcct.getId()); + log.debug("BsqSwap Sell BSQ (Buy BTC) OFFER:\n{}", bsqSwapOffer); + var newOfferId = bsqSwapOffer.getId(); + assertNotEquals("", newOfferId); + assertEquals(BUY.name(), bsqSwapOffer.getDirection()); + assertEquals(5_000, bsqSwapOffer.getPrice()); + assertEquals(1_000_000L, bsqSwapOffer.getAmount()); + assertEquals(1_000_000L, bsqSwapOffer.getMinAmount()); + // assertEquals(alicesBsqAcct.getId(), atomicOffer.getMakerPaymentAccountId()); + assertEquals(BSQ, bsqSwapOffer.getBaseCurrencyCode()); + assertEquals(BTC, bsqSwapOffer.getCounterCurrencyCode()); + } + + @Test + @Order(3) + public void testBobTakesBsqSwapOffer() { + var bsqSwapOffer = getAvailableBsqSwapOffer(); + var bsqSwapTradeInfo = bobClient.takeBsqSwapOffer(bsqSwapOffer.getId(), + bobsBsqAcct.getId(), + BISQ_FEE_CURRENCY_CODE); + log.debug("Trade at t1: {}", bsqSwapTradeInfo); + assertEquals(BsqSwapTrade.State.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()); + } + + @Test + @Order(4) + public void testGetBalancesAfterTrade() { + var alicesBalances = aliceClient.getBalances(); + log.info("Alice's After Trade Balance:\n{}", formatBalancesTbls(alicesBalances)); + var bobsBalances = bobClient.getBalances(); + log.info("Bob's After Trade Balance:\n{}", formatBalancesTbls(bobsBalances)); + } + + private BsqSwapOfferInfo getAvailableBsqSwapOffer() { + List bsqSwapOffers = new ArrayList<>(); + int numFetchAttempts = 0; + while (bsqSwapOffers.size() == 0) { + bsqSwapOffers.addAll(bobClient.getBsqSwapOffers(BUY.name(), BSQ)); + numFetchAttempts++; + if (bsqSwapOffers.size() == 0) { + log.warn("No available bsq swap offers found after {} fetch attempts.", numFetchAttempts); + if (numFetchAttempts > 9) { + if (checkForLoggedExceptions) { + printNodeExceptionMessages(log); + } + fail(format("Bob gave up on fetching available bsq swap offers after %d attempts.", numFetchAttempts)); + } + sleep(1000); + } else { + assertEquals(1, bsqSwapOffers.size()); + log.debug("Bob found new available bsq swap offer on attempt # {}.", numFetchAttempts); + break; + } + } + var bsqSwapOffer = bobClient.getBsqSwapOffer(bsqSwapOffers.get(0).getId()); + assertEquals(bsqSwapOffers.get(0).getId(), bsqSwapOffer.getId()); + return bsqSwapOffer; + } + + private BsqSwapTradeInfo getBsqSwapTrade(String tradeId) { + int numFetchAttempts = 0; + while (true) { + try { + numFetchAttempts++; + return bobClient.getBsqSwapTrade(tradeId); + } catch (Exception ex) { + log.warn(ex.getMessage()); + if (numFetchAttempts > 9) { + if (checkForLoggedExceptions) { + printNodeExceptionMessages(log); + } + fail(format("Could not find new bsq swap trade after %d attempts.", numFetchAttempts)); + } else { + sleep(1000); + } + } + } + } +} diff --git a/apitest/src/test/java/bisq/apitest/method/trade/ExpectedProtocolStatus.java b/apitest/src/test/java/bisq/apitest/method/trade/ExpectedProtocolStatus.java index 6365558594..f054349ff9 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/ExpectedProtocolStatus.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/ExpectedProtocolStatus.java @@ -1,6 +1,6 @@ package bisq.apitest.method.trade; -import bisq.core.trade.Trade; +import bisq.core.trade.model.bisq_v1.Trade; /** * A test fixture encapsulating expected trade protocol status. 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 fc365931d5..9acdb4b32a 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBSQOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBSQOfferTest.java @@ -37,13 +37,13 @@ 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.Trade.Phase.DEPOSIT_CONFIRMED; -import static bisq.core.trade.Trade.Phase.FIAT_SENT; -import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED; -import static bisq.core.trade.Trade.State.BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG; -import static bisq.core.trade.Trade.State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN; -import static bisq.core.trade.Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG; -import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG; +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; @@ -51,7 +51,7 @@ 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.OfferPayload.Direction.SELL; +import static protobuf.OfferDirection.SELL; 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 8f03520b52..7a7f0dd9c8 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java @@ -37,17 +37,17 @@ 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.Trade.Phase.DEPOSIT_CONFIRMED; -import static bisq.core.trade.Trade.Phase.FIAT_SENT; -import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED; -import static bisq.core.trade.Trade.State.*; +import static 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 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.OfferPayload.Direction.BUY; +import static protobuf.OfferDirection.BUY; import static protobuf.OpenOffer.State.AVAILABLE; @Disabled 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 1035875010..a625d86fae 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferWithNationalBankAcctTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferWithNationalBankAcctTest.java @@ -55,14 +55,14 @@ 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.Trade.Phase.DEPOSIT_CONFIRMED; -import static bisq.core.trade.Trade.Phase.FIAT_SENT; -import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED; -import static bisq.core.trade.Trade.State.*; +import static 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 org.junit.jupiter.api.Assertions.*; import static protobuf.Offer.State.OFFER_FEE_PAID; -import static protobuf.OfferPayload.Direction.BUY; +import static protobuf.OfferDirection.BUY; import static protobuf.OpenOffer.State.AVAILABLE; /** 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 786601e6fa..d33e0e360e 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBSQOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBSQOfferTest.java @@ -38,21 +38,21 @@ 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 bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED; -import static bisq.core.trade.Trade.Phase.FIAT_SENT; -import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED; -import static bisq.core.trade.Trade.Phase.WITHDRAWN; -import static bisq.core.trade.Trade.State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN; -import static bisq.core.trade.Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG; -import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG; -import static bisq.core.trade.Trade.State.WITHDRAW_COMPLETED; +import static 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.OfferPayload.Direction.BUY; +import static protobuf.OfferDirection.BUY; 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 c4abd90934..078d299859 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBTCOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBTCOfferTest.java @@ -37,18 +37,18 @@ import org.junit.jupiter.api.TestMethodOrder; import static bisq.apitest.config.ApiTestConfig.BTC; import static bisq.cli.TableFormat.formatBalancesTbls; import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent; -import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED; -import static bisq.core.trade.Trade.Phase.FIAT_SENT; -import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED; -import static bisq.core.trade.Trade.Phase.WITHDRAWN; -import static bisq.core.trade.Trade.State.*; +import static 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 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.OfferPayload.Direction.SELL; +import static protobuf.OfferDirection.SELL; import static protobuf.OpenOffer.State.AVAILABLE; @Disabled 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 e978624c2f..f96d422f6a 100644 --- a/apitest/src/test/java/bisq/apitest/method/wallet/BsqWalletTest.java +++ b/apitest/src/test/java/bisq/apitest/method/wallet/BsqWalletTest.java @@ -124,7 +124,7 @@ public class BsqWalletTest extends MethodTest { genBtcBlocksThenWait(1, 4000); BsqBalanceInfo alicesBsqBalances = aliceClient.getBalances().getBsq(); - BsqBalanceInfo bobsBsqBalances = waitForBsqNewAvailableConfirmedBalance(bobClient, 150000000); + BsqBalanceInfo bobsBsqBalances = waitForBsqNewAvailableBalance(bobClient, 150000000); log.debug("See Available Confirmed BSQ Balances..."); printBobAndAliceBsqBalances(testInfo, @@ -166,8 +166,8 @@ public class BsqWalletTest extends MethodTest { return bsqBalance; } - private BsqBalanceInfo waitForBsqNewAvailableConfirmedBalance(GrpcClient grpcClient, - long staleBalance) { + private BsqBalanceInfo waitForBsqNewAvailableBalance(GrpcClient grpcClient, + long staleBalance) { BsqBalanceInfo bsqBalance = grpcClient.getBsqBalances(); for (int numRequests = 1; numRequests <= 15 && bsqBalance.getAvailableConfirmedBalance() == staleBalance; diff --git a/apitest/src/test/java/bisq/apitest/method/wallet/WalletTestUtil.java b/apitest/src/test/java/bisq/apitest/method/wallet/WalletTestUtil.java index 85b9f04e84..e5c002fba5 100644 --- a/apitest/src/test/java/bisq/apitest/method/wallet/WalletTestUtil.java +++ b/apitest/src/test/java/bisq/apitest/method/wallet/WalletTestUtil.java @@ -38,13 +38,13 @@ public class WalletTestUtil { 0); @SuppressWarnings("SameParameterValue") - public static bisq.core.api.model.BsqBalanceInfo bsqBalanceModel(long availableConfirmedBalance, + public static bisq.core.api.model.BsqBalanceInfo bsqBalanceModel(long availableBalance, long unverifiedBalance, long unconfirmedChangeBalance, long lockedForVotingBalance, long lockupBondsBalance, long unlockingBondsBalance) { - return bisq.core.api.model.BsqBalanceInfo.valueOf(availableConfirmedBalance, + return bisq.core.api.model.BsqBalanceInfo.valueOf(availableBalance, unverifiedBalance, unconfirmedChangeBalance, lockedForVotingBalance, @@ -54,7 +54,7 @@ public class WalletTestUtil { public static void verifyBsqBalances(bisq.core.api.model.BsqBalanceInfo expected, BsqBalanceInfo actual) { - assertEquals(expected.getAvailableConfirmedBalance(), actual.getAvailableConfirmedBalance()); + assertEquals(expected.getAvailableBalance(), actual.getAvailableConfirmedBalance()); assertEquals(expected.getUnverifiedBalance(), actual.getUnverifiedBalance()); assertEquals(expected.getUnconfirmedChangeBalance(), actual.getUnconfirmedChangeBalance()); assertEquals(expected.getLockedForVotingBalance(), actual.getLockedForVotingBalance()); diff --git a/apitest/src/test/java/bisq/apitest/scenario/LongRunningBsqSwapTest.java b/apitest/src/test/java/bisq/apitest/scenario/LongRunningBsqSwapTest.java new file mode 100644 index 0000000000..bec7044f12 --- /dev/null +++ b/apitest/src/test/java/bisq/apitest/scenario/LongRunningBsqSwapTest.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.apitest.scenario; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.condition.EnabledIf; + +import static java.lang.System.getenv; + + + +import bisq.apitest.method.offer.AbstractOfferTest; +import bisq.apitest.method.trade.BsqSwapTradeTest; + +@EnabledIf("envLongRunningTestEnabled") +@Slf4j +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class LongRunningBsqSwapTest extends AbstractOfferTest { + + private static final int MAX_SWAPS = 250; + + @BeforeAll + public static void setUp() { + AbstractOfferTest.setUp(); + createBsqSwapBsqPaymentAccounts(); + } + + @Test + @Order(1) + public void testBsqSwaps() { + // TODO Fix wallet inconsistency bugs after N(?) trades. + BsqSwapTradeTest test = new BsqSwapTradeTest(); + test.setCheckForLoggedExceptions(true); + + for (int swapCount = 1; swapCount <= MAX_SWAPS; swapCount++) { + log.info("Beginning BSQ Swap # {}", swapCount); + + test.testGetBalancesBeforeTrade(); + + test.testAliceCreateBsqSwapBuyOffer(); + genBtcBlocksThenWait(1, 8_000); + + test.testBobTakesBsqSwapOffer(); + genBtcBlocksThenWait(1, 8_000); + + test.testGetBalancesAfterTrade(); + log.info("Finished BSQ Swap # {}", swapCount); + } + } + + protected static boolean envLongRunningTestEnabled() { + String envName = "LONG_RUNNING_BSQ_SWAP_TEST_ENABLED"; + String envX = getenv(envName); + if (envX != null) { + log.info("Enabled, found {}.", envName); + return true; + } else { + log.info("Skipped, no environment variable {} defined.", envName); + log.info("To enable on Mac OS or Linux:" + + "\tIf running in terminal, export LONG_RUNNING_BSQ_SWAP_TEST_ENABLED=true in bash shell." + + "\tIf running in Intellij, set LONG_RUNNING_BSQ_SWAP_TEST_ENABLED=true in launcher's Environment variables field."); + return false; + } + } +} diff --git a/apitest/src/test/java/bisq/apitest/scenario/LongRunningOfferDeactivationTest.java b/apitest/src/test/java/bisq/apitest/scenario/LongRunningOfferDeactivationTest.java index e7f09247a8..46195144dc 100644 --- a/apitest/src/test/java/bisq/apitest/scenario/LongRunningOfferDeactivationTest.java +++ b/apitest/src/test/java/bisq/apitest/scenario/LongRunningOfferDeactivationTest.java @@ -35,8 +35,8 @@ import static bisq.cli.CurrencyFormat.formatPrice; import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent; import static java.lang.System.getenv; import static org.junit.jupiter.api.Assertions.fail; -import static protobuf.OfferPayload.Direction.BUY; -import static protobuf.OfferPayload.Direction.SELL; +import static protobuf.OfferDirection.BUY; +import static protobuf.OfferDirection.SELL; diff --git a/apitest/src/test/java/bisq/apitest/scenario/OfferTest.java b/apitest/src/test/java/bisq/apitest/scenario/OfferTest.java index 41ac197f1b..e52916bad8 100644 --- a/apitest/src/test/java/bisq/apitest/scenario/OfferTest.java +++ b/apitest/src/test/java/bisq/apitest/scenario/OfferTest.java @@ -28,6 +28,7 @@ import org.junit.jupiter.api.TestMethodOrder; import bisq.apitest.method.offer.AbstractOfferTest; +import bisq.apitest.method.offer.BsqSwapOfferTest; import bisq.apitest.method.offer.CancelOfferTest; import bisq.apitest.method.offer.CreateBSQOffersTest; import bisq.apitest.method.offer.CreateOfferUsingFixedPriceTest; @@ -90,6 +91,19 @@ public class OfferTest extends AbstractOfferTest { @Test @Order(6) + public void testCreateBSQSwapOffers() { + BsqSwapOfferTest test = new BsqSwapOfferTest(); + BsqSwapOfferTest.createBsqSwapBsqPaymentAccounts(); + test.testAliceCreateBsqSwapBuyOffer1(); + test.testAliceCreateBsqSwapBuyOffer2(); + test.testAliceCreateBsqSwapBuyOffer3(); + test.testAliceCreateBsqSwapBuyOffer4(); + test.testGetMyBsqSwapOffers(); + test.testGetAvailableBsqSwapOffers(); + } + + @Test + @Order(7) public void testEditOffer() { EditOfferTest test = new EditOfferTest(); // Edit fiat offer tests diff --git a/apitest/src/test/java/bisq/apitest/scenario/TradeTest.java b/apitest/src/test/java/bisq/apitest/scenario/TradeTest.java index 1cd4054d61..becb62f918 100644 --- a/apitest/src/test/java/bisq/apitest/scenario/TradeTest.java +++ b/apitest/src/test/java/bisq/apitest/scenario/TradeTest.java @@ -29,6 +29,7 @@ import org.junit.jupiter.api.TestMethodOrder; import bisq.apitest.method.trade.AbstractTradeTest; +import bisq.apitest.method.trade.BsqSwapTradeTest; import bisq.apitest.method.trade.TakeBuyBSQOfferTest; import bisq.apitest.method.trade.TakeBuyBTCOfferTest; import bisq.apitest.method.trade.TakeBuyBTCOfferWithNationalBankAcctTest; @@ -97,4 +98,13 @@ public class TradeTest extends AbstractTradeTest { test.testBobsConfirmPaymentReceived(testInfo); test.testAlicesBtcWithdrawalToExternalAddress(testInfo); } + + @Test + @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/script/BotScriptGenerator.java b/apitest/src/test/java/bisq/apitest/scenario/bot/script/BotScriptGenerator.java index c81730c4c4..9d62799e9e 100644 --- a/apitest/src/test/java/bisq/apitest/scenario/bot/script/BotScriptGenerator.java +++ b/apitest/src/test/java/bisq/apitest/scenario/bot/script/BotScriptGenerator.java @@ -17,8 +17,9 @@ package bisq.apitest.scenario.bot.script; +import bisq.core.util.JsonUtil; + import bisq.common.file.JsonFileManager; -import bisq.common.util.Utilities; import joptsimple.BuiltinHelpFormatter; import joptsimple.OptionParser; @@ -214,7 +215,7 @@ public class BotScriptGenerator { } private String generateBotScriptTemplate() { - return Utilities.objectToJson(new BotScript( + return JsonUtil.objectToJson(new BotScript( useTestHarness, botPaymentMethodId, countryCode, diff --git a/build.gradle b/build.gradle index 2314a22200..cf41f0124c 100644 --- a/build.gradle +++ b/build.gradle @@ -32,7 +32,7 @@ configure(subprojects) { ext { // in alphabetical order bcVersion = '1.63' - bitcoinjVersion = '3186b20' + bitcoinjVersion = '42bbae9' codecVersion = '1.13' easybindVersion = '1.0.3' easyVersion = '4.0.1' diff --git a/cli/src/main/java/bisq/cli/DirectionFormat.java b/cli/src/main/java/bisq/cli/DirectionFormat.java index ac0e5b6c55..b3cb1773f4 100644 --- a/cli/src/main/java/bisq/cli/DirectionFormat.java +++ b/cli/src/main/java/bisq/cli/DirectionFormat.java @@ -24,8 +24,8 @@ import java.util.function.Function; import static bisq.cli.ColumnHeaderConstants.COL_HEADER_DIRECTION; import static java.lang.String.format; -import static protobuf.OfferPayload.Direction.BUY; -import static protobuf.OfferPayload.Direction.SELL; +import static protobuf.OfferDirection.BUY; +import static protobuf.OfferDirection.SELL; class DirectionFormat { diff --git a/cli/src/main/java/bisq/cli/GrpcClient.java b/cli/src/main/java/bisq/cli/GrpcClient.java index 9b2195bf20..4372577a1a 100644 --- a/cli/src/main/java/bisq/cli/GrpcClient.java +++ b/cli/src/main/java/bisq/cli/GrpcClient.java @@ -20,12 +20,17 @@ package bisq.cli; import bisq.proto.grpc.AddressBalanceInfo; import bisq.proto.grpc.BalancesInfo; import bisq.proto.grpc.BsqBalanceInfo; +import bisq.proto.grpc.BsqSwapOfferInfo; +import bisq.proto.grpc.BsqSwapTradeInfo; import bisq.proto.grpc.BtcBalanceInfo; +import bisq.proto.grpc.CreateBsqSwapOfferRequest; import bisq.proto.grpc.GetMethodHelpRequest; import bisq.proto.grpc.GetVersionRequest; import bisq.proto.grpc.OfferInfo; import bisq.proto.grpc.RegisterDisputeAgentRequest; import bisq.proto.grpc.StopRequest; +import bisq.proto.grpc.TakeBsqSwapOfferReply; +import bisq.proto.grpc.TakeBsqSwapOfferRequest; import bisq.proto.grpc.TakeOfferReply; import bisq.proto.grpc.TradeInfo; import bisq.proto.grpc.TxFeeRateInfo; @@ -137,6 +142,21 @@ public final class GrpcClient { return walletsServiceRequest.getTransaction(txId); } + public BsqSwapOfferInfo createBsqSwapOffer(String direction, + long amount, + long minAmount, + String fixedPrice, + String paymentAcctId) { + var request = CreateBsqSwapOfferRequest.newBuilder() + .setDirection(direction) + .setAmount(amount) + .setMinAmount(minAmount) + .setPrice(fixedPrice) + .setPaymentAccountId(paymentAcctId) + .build(); + return grpcStubs.offersService.createBsqSwapOffer(request).getBsqSwapOffer(); + } + public OfferInfo createFixedPricedOffer(String direction, String currencyCode, long amount, @@ -243,14 +263,26 @@ public final class GrpcClient { offersServiceRequest.cancelOffer(offerId); } + public BsqSwapOfferInfo getBsqSwapOffer(String offerId) { + return offersServiceRequest.getBsqSwapOffer(offerId); + } + public OfferInfo getOffer(String offerId) { return offersServiceRequest.getOffer(offerId); } + public BsqSwapOfferInfo getMyBsqSwapOffer(String offerId) { + return offersServiceRequest.getMyBsqSwapOffer(offerId); + } + public OfferInfo getMyOffer(String offerId) { return offersServiceRequest.getMyOffer(offerId); } + public List getBsqSwapOffers(String direction, String currencyCode) { + return offersServiceRequest.getBsqSwapOffers(direction, currencyCode); + } + public List getOffers(String direction, String currencyCode) { return offersServiceRequest.getOffers(direction, currencyCode); } @@ -271,6 +303,14 @@ public final class GrpcClient { return offersServiceRequest.getBsqOffersSortedByDate(); } + public List getBsqSwapOffersSortedByDate() { + return offersServiceRequest.getBsqSwapOffersSortedByDate(); + } + + public List getMyBsqSwapOffers(String direction, String currencyCode) { + return offersServiceRequest.getMyBsqSwapOffers(direction, currencyCode); + } + public List getMyOffers(String direction, String currencyCode) { return offersServiceRequest.getMyOffers(direction, currencyCode); } @@ -291,22 +331,53 @@ public final class GrpcClient { return offersServiceRequest.getMyBsqOffersSortedByDate(); } + public List getMyBsqSwapBsqOffersSortedByDate() { + return offersServiceRequest.getMyBsqSwapOffersSortedByDate(); + } + public OfferInfo getMostRecentOffer(String direction, String currencyCode) { return offersServiceRequest.getMostRecentOffer(direction, currencyCode); } + public List sortBsqSwapOffersByDate(List offerInfoList) { + return offersServiceRequest.sortBsqSwapOffersByDate(offerInfoList); + } + public List sortOffersByDate(List offerInfoList) { return offersServiceRequest.sortOffersByDate(offerInfoList); } + public TakeBsqSwapOfferReply getTakeBsqSwapOfferReply(String offerId, + String paymentAccountId, + String takerFeeCurrencyCode) { + var request = TakeBsqSwapOfferRequest.newBuilder() + .setOfferId(offerId) + .setPaymentAccountId(paymentAccountId) + .setTakerFeeCurrencyCode(takerFeeCurrencyCode) + .build(); + return grpcStubs.tradesService.takeBsqSwapOffer(request); + } + public TakeOfferReply getTakeOfferReply(String offerId, String paymentAccountId, String takerFeeCurrencyCode) { return tradesServiceRequest.getTakeOfferReply(offerId, paymentAccountId, takerFeeCurrencyCode); } + public BsqSwapTradeInfo takeBsqSwapOffer(String offerId, String paymentAccountId, String takerFeeCurrencyCode) { + var reply = getTakeBsqSwapOfferReply(offerId, paymentAccountId, takerFeeCurrencyCode); + if (reply.hasBsqSwapTrade()) + return reply.getBsqSwapTrade(); + else + throw new IllegalStateException(reply.getFailureReason().getDescription()); + } + public TradeInfo takeOffer(String offerId, String paymentAccountId, String takerFeeCurrencyCode) { return tradesServiceRequest.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode); } + public BsqSwapTradeInfo getBsqSwapTrade(String tradeId) { + return tradesServiceRequest.getBsqSwapTrade(tradeId); + } + public TradeInfo getTrade(String tradeId) { return tradesServiceRequest.getTrade(tradeId); } diff --git a/cli/src/main/java/bisq/cli/opts/AbstractMethodOptionParser.java b/cli/src/main/java/bisq/cli/opts/AbstractMethodOptionParser.java index e0b08ed771..499efba7bd 100644 --- a/cli/src/main/java/bisq/cli/opts/AbstractMethodOptionParser.java +++ b/cli/src/main/java/bisq/cli/opts/AbstractMethodOptionParser.java @@ -30,6 +30,7 @@ import lombok.Getter; import static bisq.cli.opts.OptLabel.OPT_HELP; +@SuppressWarnings("unchecked") abstract class AbstractMethodOptionParser implements MethodOpts { // The full command line args passed to CliMain.main(String[] args). @@ -53,7 +54,6 @@ abstract class AbstractMethodOptionParser implements MethodOpts { public AbstractMethodOptionParser parse() { try { options = parser.parse(new ArgumentList(args).getMethodArguments()); - //noinspection unchecked nonOptionArguments = (List) options.nonOptionArguments(); return this; } catch (OptionException ex) { diff --git a/cli/src/main/java/bisq/cli/opts/CreateCryptoCurrencyPaymentAcctOptionParser.java b/cli/src/main/java/bisq/cli/opts/CreateCryptoCurrencyPaymentAcctOptionParser.java index a37a9f109b..000d327d78 100644 --- a/cli/src/main/java/bisq/cli/opts/CreateCryptoCurrencyPaymentAcctOptionParser.java +++ b/cli/src/main/java/bisq/cli/opts/CreateCryptoCurrencyPaymentAcctOptionParser.java @@ -20,10 +20,7 @@ package bisq.cli.opts; import joptsimple.OptionSpec; -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; +import static bisq.cli.opts.OptLabel.*; public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodOptionParser implements MethodOpts { @@ -41,6 +38,11 @@ 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); } @@ -82,4 +84,8 @@ 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 70dda3e6fc..e31bf744cd 100644 --- a/cli/src/main/java/bisq/cli/opts/OptLabel.java +++ b/cli/src/main/java/bisq/cli/opts/OptLabel.java @@ -44,6 +44,7 @@ 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 215c4f3e80..2df00bc2ee 100644 --- a/cli/src/main/java/bisq/cli/request/OffersServiceRequest.java +++ b/cli/src/main/java/bisq/cli/request/OffersServiceRequest.java @@ -17,6 +17,7 @@ package bisq.cli.request; +import bisq.proto.grpc.BsqSwapOfferInfo; import bisq.proto.grpc.CancelOfferRequest; import bisq.proto.grpc.CreateOfferRequest; import bisq.proto.grpc.EditOfferRequest; @@ -38,8 +39,8 @@ import static bisq.proto.grpc.EditOfferRequest.EditType.MKT_PRICE_MARGIN_ONLY; import static bisq.proto.grpc.EditOfferRequest.EditType.TRIGGER_PRICE_ONLY; import static java.util.Comparator.comparing; import static java.util.stream.Collectors.toList; -import static protobuf.OfferPayload.Direction.BUY; -import static protobuf.OfferPayload.Direction.SELL; +import static protobuf.OfferDirection.BUY; +import static protobuf.OfferDirection.SELL; @@ -207,6 +208,13 @@ public class OffersServiceRequest { grpcStubs.offersService.cancelOffer(request); } + public BsqSwapOfferInfo getBsqSwapOffer(String offerId) { + var request = GetOfferRequest.newBuilder() + .setId(offerId) + .build(); + return grpcStubs.offersService.getBsqSwapOffer(request).getBsqSwapOffer(); + } + public OfferInfo getOffer(String offerId) { var request = GetOfferRequest.newBuilder() .setId(offerId) @@ -214,6 +222,14 @@ public class OffersServiceRequest { return grpcStubs.offersService.getOffer(request).getOffer(); } + public BsqSwapOfferInfo getMyBsqSwapOffer(String offerId) { + var request = GetMyOfferRequest.newBuilder() + .setId(offerId) + .build(); + return grpcStubs.offersService.getMyBsqSwapOffer(request).getBsqSwapOffer(); + + } + public OfferInfo getMyOffer(String offerId) { var request = GetMyOfferRequest.newBuilder() .setId(offerId) @@ -221,6 +237,15 @@ public class OffersServiceRequest { return grpcStubs.offersService.getMyOffer(request).getOffer(); } + public List getBsqSwapOffers(String direction, String currencyCode) { + var request = GetOffersRequest.newBuilder() + .setDirection(direction) + .setCurrencyCode(currencyCode) + .build(); + + return grpcStubs.offersService.getBsqSwapOffers(request).getBsqSwapOffersList(); + } + public List getOffers(String direction, String currencyCode) { if (isSupportedCryptoCurrency(currencyCode)) { return getCryptoCurrencyOffers(direction, currencyCode); @@ -251,6 +276,13 @@ public class OffersServiceRequest { return offers.isEmpty() ? offers : sortOffersByDate(offers); } + public List getBsqSwapOffersSortedByDate() { + ArrayList offers = new ArrayList<>(); + offers.addAll(getBsqSwapOffers(BUY.name(), "BSQ")); + offers.addAll(getBsqSwapOffers(SELL.name(), "BSQ")); + return sortBsqSwapOffersByDate(offers); + } + public List getBsqOffersSortedByDate() { ArrayList offers = new ArrayList<>(); offers.addAll(getCryptoCurrencyOffers(BUY.name(), "BSQ")); @@ -258,6 +290,14 @@ public class OffersServiceRequest { return sortOffersByDate(offers); } + public List getMyBsqSwapOffers(String direction, String currencyCode) { + var request = GetMyOffersRequest.newBuilder() + .setDirection(direction) + .setCurrencyCode(currencyCode) + .build(); + return grpcStubs.offersService.getMyBsqSwapOffers(request).getBsqSwapOffersList(); + } + public List getMyOffers(String direction, String currencyCode) { if (isSupportedCryptoCurrency(currencyCode)) { return getMyCryptoCurrencyOffers(direction, currencyCode); @@ -295,11 +335,25 @@ public class OffersServiceRequest { return sortOffersByDate(offers); } + public List getMyBsqSwapOffersSortedByDate() { + ArrayList offers = new ArrayList<>(); + offers.addAll(getMyBsqSwapOffers(BUY.name(), "BSQ")); + offers.addAll(getMyBsqSwapOffers(SELL.name(), "BSQ")); + return sortBsqSwapOffersByDate(offers); + } + public OfferInfo getMostRecentOffer(String direction, String currencyCode) { List offers = getOffersSortedByDate(direction, currencyCode); return offers.isEmpty() ? null : offers.get(offers.size() - 1); } + public List sortBsqSwapOffersByDate(List offerInfoList) { + return offerInfoList.stream() + .sorted(comparing(BsqSwapOfferInfo::getDate)) + .collect(toList()); + + } + public List sortOffersByDate(List offerInfoList) { return offerInfoList.stream() .sorted(comparing(OfferInfo::getDate)) diff --git a/cli/src/main/java/bisq/cli/request/TradesServiceRequest.java b/cli/src/main/java/bisq/cli/request/TradesServiceRequest.java index 6d57bb0354..33a68c344a 100644 --- a/cli/src/main/java/bisq/cli/request/TradesServiceRequest.java +++ b/cli/src/main/java/bisq/cli/request/TradesServiceRequest.java @@ -17,6 +17,7 @@ package bisq.cli.request; +import bisq.proto.grpc.BsqSwapTradeInfo; import bisq.proto.grpc.ConfirmPaymentReceivedRequest; import bisq.proto.grpc.ConfirmPaymentStartedRequest; import bisq.proto.grpc.GetTradeRequest; @@ -55,6 +56,13 @@ public class TradesServiceRequest { throw new IllegalStateException(reply.getFailureReason().getDescription()); } + public BsqSwapTradeInfo getBsqSwapTrade(String tradeId) { + var request = GetTradeRequest.newBuilder() + .setTradeId(tradeId) + .build(); + return grpcStubs.tradesService.getBsqSwapTrade(request).getBsqSwapTrade(); + } + public TradeInfo getTrade(String tradeId) { var request = GetTradeRequest.newBuilder() .setTradeId(tradeId) diff --git a/common/src/main/java/bisq/common/app/Capability.java b/common/src/main/java/bisq/common/app/Capability.java index 1c9aebd889..9227e1f6fa 100644 --- a/common/src/main/java/bisq/common/app/Capability.java +++ b/common/src/main/java/bisq/common/app/Capability.java @@ -42,5 +42,6 @@ public enum Capability { REFUND_AGENT, // Supports refund agents TRADE_STATISTICS_HASH_UPDATE, // We changed the hash method in 1.2.0 and that requires update to 1.2.2 for handling it correctly, otherwise the seed nodes have to process too much data. NO_ADDRESS_PRE_FIX, // At 1.4.0 we removed the prefix filter for mailbox messages. If a peer has that capability we do not sent the prefix. - TRADE_STATISTICS_3 // We used a new reduced trade statistics model from v1.4.0 on + TRADE_STATISTICS_3, // We used a new reduced trade statistics model from v1.4.0 on + BSQ_SWAP_OFFER // Supports new message type BsqSwapOffer } diff --git a/common/src/main/java/bisq/common/config/ConfigFileEditor.java b/common/src/main/java/bisq/common/config/ConfigFileEditor.java index f067430165..38054cb39e 100644 --- a/common/src/main/java/bisq/common/config/ConfigFileEditor.java +++ b/common/src/main/java/bisq/common/config/ConfigFileEditor.java @@ -65,7 +65,7 @@ public class ConfigFileEditor { if (ConfigFileOption.isOption(line)) { ConfigFileOption option = ConfigFileOption.parse(line); if (option.name.equals(name)) { - log.warn("Cleared existing config file option '{}'", option); + log.debug("Cleared existing config file option '{}'", option); continue; } } diff --git a/common/src/main/java/bisq/common/crypto/HashCashService.java b/common/src/main/java/bisq/common/crypto/HashCashService.java new file mode 100644 index 0000000000..80e6f92959 --- /dev/null +++ b/common/src/main/java/bisq/common/crypto/HashCashService.java @@ -0,0 +1,181 @@ +/* + * 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.common.crypto; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.primitives.Longs; + +import java.nio.charset.StandardCharsets; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiFunction; + +import lombok.extern.slf4j.Slf4j; + +/** + * HashCash implementation for proof of work + * It doubles required work by difficulty increase (adding one leading zero). + * + * See https://www.hashcash.org/papers/hashcash.pdf + */ +@Slf4j +public class HashCashService { + // Default validations. Custom implementations might use tolerance. + private static final BiFunction isChallengeValid = Arrays::equals; + private static final BiFunction isDifficultyValid = Integer::equals; + + public static CompletableFuture mint(byte[] payload, + byte[] challenge, + int difficulty) { + return HashCashService.mint(payload, + challenge, + difficulty, + HashCashService::testDifficulty); + } + + public static boolean verify(ProofOfWork proofOfWork) { + return verify(proofOfWork, + proofOfWork.getChallenge(), + proofOfWork.getNumLeadingZeros()); + } + + public static boolean verify(ProofOfWork proofOfWork, + byte[] controlChallenge, + int controlDifficulty) { + return HashCashService.verify(proofOfWork, + controlChallenge, + controlDifficulty, + HashCashService::testDifficulty); + } + + public static boolean verify(ProofOfWork proofOfWork, + byte[] controlChallenge, + int controlDifficulty, + BiFunction challengeValidation, + BiFunction difficultyValidation) { + return HashCashService.verify(proofOfWork, + controlChallenge, + controlDifficulty, + challengeValidation, + difficultyValidation, + HashCashService::testDifficulty); + } + + private static boolean testDifficulty(byte[] result, long difficulty) { + return HashCashService.numberOfLeadingZeros(result) > difficulty; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Generic + /////////////////////////////////////////////////////////////////////////////////////////// + + static CompletableFuture mint(byte[] payload, + byte[] challenge, + int difficulty, + BiFunction testDifficulty) { + return CompletableFuture.supplyAsync(() -> { + long ts = System.currentTimeMillis(); + byte[] result; + long counter = 0; + do { + result = toSha256Hash(payload, challenge, ++counter); + } + while (!testDifficulty.apply(result, difficulty)); + ProofOfWork proofOfWork = new ProofOfWork(payload, counter, challenge, difficulty, System.currentTimeMillis() - ts); + log.info("Completed minting proofOfWork: {}", proofOfWork); + return proofOfWork; + }); + } + + static boolean verify(ProofOfWork proofOfWork, + byte[] controlChallenge, + int controlDifficulty, + BiFunction testDifficulty) { + return verify(proofOfWork, + controlChallenge, + controlDifficulty, + HashCashService.isChallengeValid, + HashCashService.isDifficultyValid, + testDifficulty); + } + + static boolean verify(ProofOfWork proofOfWork, + byte[] controlChallenge, + int controlDifficulty, + BiFunction challengeValidation, + BiFunction difficultyValidation, + BiFunction testDifficulty) { + return challengeValidation.apply(proofOfWork.getChallenge(), controlChallenge) && + difficultyValidation.apply(proofOfWork.getNumLeadingZeros(), controlDifficulty) && + verify(proofOfWork, testDifficulty); + } + + private static boolean verify(ProofOfWork proofOfWork, BiFunction testDifficulty) { + byte[] hash = HashCashService.toSha256Hash(proofOfWork.getPayload(), + proofOfWork.getChallenge(), + proofOfWork.getCounter()); + return testDifficulty.apply(hash, proofOfWork.getNumLeadingZeros()); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Utils + /////////////////////////////////////////////////////////////////////////////////////////// + + public static byte[] getBytes(String value) { + return value.getBytes(StandardCharsets.UTF_8); + } + + @VisibleForTesting + static int numberOfLeadingZeros(byte[] bytes) { + int numberOfLeadingZeros = 0; + for (int i = 0; i < bytes.length; i++) { + numberOfLeadingZeros += numberOfLeadingZeros(bytes[i]); + if (numberOfLeadingZeros < 8 * (i + 1)) { + break; + } + } + return numberOfLeadingZeros; + } + + private static byte[] toSha256Hash(byte[] payload, byte[] challenge, long counter) { + byte[] preImage = org.bouncycastle.util.Arrays.concatenate(payload, + challenge, + Longs.toByteArray(counter)); + return Hash.getSha256Hash(preImage); + } + + // Borrowed from Integer.numberOfLeadingZeros and adjusted for byte + @VisibleForTesting + static int numberOfLeadingZeros(byte i) { + if (i <= 0) + return i == 0 ? 8 : 0; + int n = 7; + if (i >= 1 << 4) { + n -= 4; + i >>>= 4; + } + if (i >= 1 << 2) { + n -= 2; + i >>>= 2; + } + return n - (i >>> 1); + } +} diff --git a/common/src/main/java/bisq/common/crypto/ProofOfWork.java b/common/src/main/java/bisq/common/crypto/ProofOfWork.java new file mode 100644 index 0000000000..8f88e67a28 --- /dev/null +++ b/common/src/main/java/bisq/common/crypto/ProofOfWork.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.common.crypto; + +import bisq.common.proto.network.NetworkPayload; + +import com.google.protobuf.ByteString; + +import java.math.BigInteger; + +import lombok.EqualsAndHashCode; +import lombok.Getter; + +@EqualsAndHashCode +public final class ProofOfWork implements NetworkPayload { + @Getter + private final byte[] payload; + @Getter + private final long counter; + @Getter + private final byte[] challenge; + // We want to support BigInteger value for difficulty as well so we store it as byte array + private final byte[] difficulty; + @Getter + private final long duration; + + public ProofOfWork(byte[] payload, + long counter, + byte[] challenge, + int difficulty, + long duration) { + this(payload, + counter, + challenge, + BigInteger.valueOf(difficulty).toByteArray(), + duration); + } + + public ProofOfWork(byte[] payload, + long counter, + byte[] challenge, + BigInteger difficulty, + long duration) { + this(payload, + counter, + challenge, + difficulty.toByteArray(), + duration); + } + + public ProofOfWork(byte[] payload, + long counter, + byte[] challenge, + byte[] difficulty, + long duration) { + this.payload = payload; + this.counter = counter; + this.challenge = challenge; + this.difficulty = difficulty; + this.duration = duration; + } + + public int getNumLeadingZeros() { + return new BigInteger(difficulty).intValue(); + } + + public BigInteger getTarget() { + return new BigInteger(difficulty); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public protobuf.ProofOfWork toProtoMessage() { + return protobuf.ProofOfWork.newBuilder() + .setPayload(ByteString.copyFrom(payload)) + .setCounter(counter) + .setChallenge(ByteString.copyFrom(challenge)) + .setDifficulty(ByteString.copyFrom(difficulty)) + .setDuration(duration) + .build(); + } + + public static ProofOfWork fromProto(protobuf.ProofOfWork proto) { + return new ProofOfWork( + proto.getPayload().toByteArray(), + proto.getCounter(), + proto.getChallenge().toByteArray(), + proto.getDifficulty().toByteArray(), + proto.getDuration() + ); + } + + + @Override + public String toString() { + return "ProofOfWork{" + + ",\r\n counter=" + counter + + ",\r\n numLeadingZeros=" + getNumLeadingZeros() + + ",\r\n target=" + getTarget() + + ",\r\n duration=" + duration + + "\r\n}"; + } +} diff --git a/common/src/main/java/bisq/common/crypto/ProofOfWorkService.java b/common/src/main/java/bisq/common/crypto/ProofOfWorkService.java new file mode 100644 index 0000000000..c94e4e5c1f --- /dev/null +++ b/common/src/main/java/bisq/common/crypto/ProofOfWorkService.java @@ -0,0 +1,156 @@ +/* + * 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.common.crypto; + +import com.google.common.primitives.Longs; + +import java.math.BigInteger; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiFunction; + +import lombok.extern.slf4j.Slf4j; + +/** + * Bitcoin-like proof of work implementation. Differs from original hashcash by using BigInteger for comparing + * the hash result with the target difficulty to gain more fine grained control for difficulty adjustment. + * This class provides a convenience method getDifficultyAsBigInteger(numLeadingZeros) to get values which are + * equivalent to the hashcash difficulty values. + * + * See https://en.wikipedia.org/wiki/Hashcash" + * "Unlike hashcash, Bitcoin's difficulty target does not specify a minimum number of leading zeros in the hash. + * Instead, the hash is interpreted as a (very large) integer, and this integer must be less than the target integer." + */ +@Slf4j +public class ProofOfWorkService { + // Default validations. Custom implementations might use tolerance. + private static final BiFunction isChallengeValid = Arrays::equals; + private static final BiFunction isTargetValid = BigInteger::equals; + + public static CompletableFuture mint(byte[] payload, + byte[] challenge, + BigInteger target) { + return mint(payload, + challenge, + target, + ProofOfWorkService::testTarget); + } + + public static boolean verify(ProofOfWork proofOfWork) { + return verify(proofOfWork, + proofOfWork.getChallenge(), + proofOfWork.getTarget()); + } + + public static boolean verify(ProofOfWork proofOfWork, + byte[] controlChallenge, + BigInteger controlTarget) { + return verify(proofOfWork, + controlChallenge, + controlTarget, + ProofOfWorkService::testTarget); + } + + public static boolean verify(ProofOfWork proofOfWork, + byte[] controlChallenge, + BigInteger controlTarget, + BiFunction challengeValidation, + BiFunction targetValidation) { + return verify(proofOfWork, + controlChallenge, + controlTarget, + challengeValidation, + targetValidation, + ProofOfWorkService::testTarget); + + } + + public static BigInteger getTarget(int numLeadingZeros) { + return BigInteger.TWO.pow(255 - numLeadingZeros).subtract(BigInteger.ONE); + } + + private static boolean testTarget(byte[] result, BigInteger target) { + return getUnsignedBigInteger(result).compareTo(target) < 0; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Generic + /////////////////////////////////////////////////////////////////////////////////////////// + + static CompletableFuture mint(byte[] payload, + byte[] challenge, + BigInteger target, + BiFunction testTarget) { + return CompletableFuture.supplyAsync(() -> { + long ts = System.currentTimeMillis(); + byte[] result; + long counter = 0; + do { + result = toSha256Hash(payload, challenge, ++counter); + } + while (!testTarget.apply(result, target)); + return new ProofOfWork(payload, counter, challenge, target, System.currentTimeMillis() - ts); + }); + } + + static boolean verify(ProofOfWork proofOfWork, + byte[] controlChallenge, + BigInteger controlTarget, + BiFunction testTarget) { + return verify(proofOfWork, + controlChallenge, + controlTarget, + ProofOfWorkService.isChallengeValid, + ProofOfWorkService.isTargetValid, + testTarget); + } + + static boolean verify(ProofOfWork proofOfWork, + byte[] controlChallenge, + BigInteger controlTarget, + BiFunction challengeValidation, + BiFunction targetValidation, + BiFunction testTarget) { + return challengeValidation.apply(proofOfWork.getChallenge(), controlChallenge) && + targetValidation.apply(proofOfWork.getTarget(), controlTarget) && + verify(proofOfWork, testTarget); + } + + private static boolean verify(ProofOfWork proofOfWork, BiFunction testTarget) { + byte[] hash = toSha256Hash(proofOfWork.getPayload(), proofOfWork.getChallenge(), proofOfWork.getCounter()); + return testTarget.apply(hash, proofOfWork.getTarget()); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Utils + /////////////////////////////////////////////////////////////////////////////////////////// + + private static BigInteger getUnsignedBigInteger(byte[] result) { + return new BigInteger(1, result); + } + + private static byte[] toSha256Hash(byte[] payload, byte[] challenge, long counter) { + byte[] preImage = org.bouncycastle.util.Arrays.concatenate(payload, + challenge, + Longs.toByteArray(counter)); + return Hash.getSha256Hash(preImage); + } +} diff --git a/common/src/main/java/bisq/common/util/Utilities.java b/common/src/main/java/bisq/common/util/Utilities.java index eb34b10bc1..03b29aa35e 100644 --- a/common/src/main/java/bisq/common/util/Utilities.java +++ b/common/src/main/java/bisq/common/util/Utilities.java @@ -19,11 +19,6 @@ package bisq.common.util; import org.bitcoinj.core.Utils; -import com.google.gson.ExclusionStrategy; -import com.google.gson.FieldAttributes; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - import com.google.common.base.Splitter; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.ListeningExecutorService; @@ -87,15 +82,6 @@ import static com.google.common.base.Preconditions.checkNotNull; @Slf4j public class Utilities { - public static String objectToJson(Object object) { - Gson gson = new GsonBuilder() - .setExclusionStrategies(new AnnotationExclusionStrategy()) - /*.excludeFieldsWithModifiers(Modifier.TRANSIENT)*/ - /* .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)*/ - .setPrettyPrinting() - .create(); - return gson.toJson(object); - } public static ExecutorService getSingleThreadExecutor(String name) { final ThreadFactory threadFactory = new ThreadFactoryBuilder() @@ -449,18 +435,6 @@ public class Utilities { return new File(Utilities.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getPath(); } - private static class AnnotationExclusionStrategy implements ExclusionStrategy { - @Override - public boolean shouldSkipField(FieldAttributes f) { - return f.getAnnotation(JsonExclude.class) != null; - } - - @Override - public boolean shouldSkipClass(Class clazz) { - return false; - } - } - public static String toTruncatedString(Object message) { return toTruncatedString(message, 200, true); } diff --git a/common/src/test/java/bisq/common/crypto/HashCashServiceTest.java b/common/src/test/java/bisq/common/crypto/HashCashServiceTest.java new file mode 100644 index 0000000000..115483d25f --- /dev/null +++ b/common/src/test/java/bisq/common/crypto/HashCashServiceTest.java @@ -0,0 +1,84 @@ +package bisq.common.crypto; + +import org.apache.commons.lang3.RandomStringUtils; + +import java.nio.charset.StandardCharsets; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class HashCashServiceTest { + private final static Logger log = LoggerFactory.getLogger(HashCashServiceTest.class); + + @Test + public void testNumberOfLeadingZeros() { + assertEquals(8, HashCashService.numberOfLeadingZeros((byte) 0x0)); + assertEquals(0, HashCashService.numberOfLeadingZeros((byte) 0xFF)); + assertEquals(6, HashCashService.numberOfLeadingZeros((byte) 0x2)); + assertEquals(2, HashCashService.numberOfLeadingZeros(Byte.parseByte("00100000", 2))); + assertEquals(1, HashCashService.numberOfLeadingZeros(new byte[]{Byte.parseByte("01000000", 2), Byte.parseByte("00000000", 2)})); + assertEquals(9, HashCashService.numberOfLeadingZeros(new byte[]{Byte.parseByte("00000000", 2), Byte.parseByte("01000000", 2)})); + assertEquals(17, HashCashService.numberOfLeadingZeros(new byte[]{Byte.parseByte("00000000", 2), Byte.parseByte("00000000", 2), Byte.parseByte("01000000", 2)})); + assertEquals(9, HashCashService.numberOfLeadingZeros(new byte[]{Byte.parseByte("00000000", 2), Byte.parseByte("01010000", 2)})); + } + + // @Ignore + @Test + public void testDiffIncrease() throws ExecutionException, InterruptedException { + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < 12; i++) { + run(i, stringBuilder); + } + log.info(stringBuilder.toString()); + + //Test result on a 4 GHz Intel Core i7: + //Minting 1000 tokens with 0 leading zeros took 0.281 ms per token and 2 iterations in average. Verification took 0.014 ms per token. + //Minting 1000 tokens with 1 leading zeros took 0.058 ms per token and 4 iterations in average. Verification took 0.005 ms per token. + //Minting 1000 tokens with 2 leading zeros took 0.081 ms per token and 8 iterations in average. Verification took 0.004 ms per token. + //Minting 1000 tokens with 3 leading zeros took 0.178 ms per token and 16 iterations in average. Verification took 0.003 ms per token. + //Minting 1000 tokens with 4 leading zeros took 0.133 ms per token and 34 iterations in average. Verification took 0.004 ms per token. + //Minting 1000 tokens with 5 leading zeros took 0.214 ms per token and 64 iterations in average. Verification took 0.003 ms per token. + //Minting 1000 tokens with 6 leading zeros took 0.251 ms per token and 126 iterations in average. Verification took 0.002 ms per token. + //Minting 1000 tokens with 7 leading zeros took 0.396 ms per token and 245 iterations in average. Verification took 0.002 ms per token. + //Minting 1000 tokens with 8 leading zeros took 0.835 ms per token and 529 iterations in average. Verification took 0.002 ms per token. + //Minting 1000 tokens with 9 leading zeros took 1.585 ms per token and 1013 iterations in average. Verification took 0.001 ms per token. + //Minting 1000 tokens with 10 leading zeros took 3.219 ms per token and 2112 iterations in average. Verification took 0.002 ms per token. + //Minting 1000 tokens with 11 leading zeros took 6.213 ms per token and 4123 iterations in average. Verification took 0.002 ms per token. + //Minting 1000 tokens with 12 leading zeros took 13.3 ms per token and 8871 iterations in average. Verification took 0.002 ms per token. + //Minting 1000 tokens with 13 leading zeros took 25.276 ms per token and 16786 iterations in average. Verification took 0.002 ms per token. + } + + private void run(int difficulty, StringBuilder stringBuilder) throws ExecutionException, InterruptedException { + int numTokens = 1000; + byte[] payload = RandomStringUtils.random(50, true, true).getBytes(StandardCharsets.UTF_8); + long ts = System.currentTimeMillis(); + List tokens = new ArrayList<>(); + for (int i = 0; i < numTokens; i++) { + byte[] challenge = UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8); + tokens.add(HashCashService.mint(payload, challenge, difficulty).get()); + } + double size = tokens.size(); + long ts2 = System.currentTimeMillis(); + long averageCounter = Math.round(tokens.stream().mapToLong(ProofOfWork::getCounter).average().orElse(0)); + boolean allValid = tokens.stream().allMatch(HashCashService::verify); + assertTrue(allValid); + double time1 = (System.currentTimeMillis() - ts) / size; + double time2 = (System.currentTimeMillis() - ts2) / size; + stringBuilder.append("\nMinting ").append(numTokens) + .append(" tokens with ").append(difficulty) + .append(" leading zeros took ").append(time1) + .append(" ms per token and ").append(averageCounter) + .append(" iterations in average. Verification took ").append(time2) + .append(" ms per token."); + } +} diff --git a/common/src/test/java/bisq/common/crypto/ProofOfWorkServiceTest.java b/common/src/test/java/bisq/common/crypto/ProofOfWorkServiceTest.java new file mode 100644 index 0000000000..a4bab784b9 --- /dev/null +++ b/common/src/test/java/bisq/common/crypto/ProofOfWorkServiceTest.java @@ -0,0 +1,81 @@ +package bisq.common.crypto; + +import org.apache.commons.lang3.RandomStringUtils; + +import java.nio.charset.StandardCharsets; + +import java.math.BigInteger; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +public class ProofOfWorkServiceTest { + private final static Logger log = LoggerFactory.getLogger(ProofOfWorkServiceTest.class); + + // @Ignore + @Test + public void testDiffIncrease() throws ExecutionException, InterruptedException { + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < 12; i++) { + run(i, stringBuilder); + } + log.info(stringBuilder.toString()); + + //Test result on a 4 GHz Intel Core i7: + //Minting 1000 tokens with 0 leading zeros took 0.279 ms per token and 2 iterations in average. Verification took 0.025 ms per token. + //Minting 1000 tokens with 1 leading zeros took 0.063 ms per token and 4 iterations in average. Verification took 0.007 ms per token. + //Minting 1000 tokens with 2 leading zeros took 0.074 ms per token and 8 iterations in average. Verification took 0.004 ms per token. + //Minting 1000 tokens with 3 leading zeros took 0.117 ms per token and 16 iterations in average. Verification took 0.003 ms per token. + //Minting 1000 tokens with 4 leading zeros took 0.116 ms per token and 33 iterations in average. Verification took 0.003 ms per token. + //Minting 1000 tokens with 5 leading zeros took 0.204 ms per token and 65 iterations in average. Verification took 0.003 ms per token. + //Minting 1000 tokens with 6 leading zeros took 0.23 ms per token and 131 iterations in average. Verification took 0.002 ms per token. + //Minting 1000 tokens with 7 leading zeros took 0.445 ms per token and 270 iterations in average. Verification took 0.002 ms per token. + //Minting 1000 tokens with 8 leading zeros took 0.856 ms per token and 530 iterations in average. Verification took 0.002 ms per token. + //Minting 1000 tokens with 9 leading zeros took 1.629 ms per token and 988 iterations in average. Verification took 0.002 ms per token. + //Minting 1000 tokens with 10 leading zeros took 3.291 ms per token and 2103 iterations in average. Verification took 0.002 ms per token. + //Minting 1000 tokens with 11 leading zeros took 6.259 ms per token and 4009 iterations in average. Verification took 0.001 ms per token. + //Minting 1000 tokens with 12 leading zeros took 13.845 ms per token and 8254 iterations in average. Verification took 0.002 ms per token. + //Minting 1000 tokens with 13 leading zeros took 26.052 ms per token and 16645 iterations in average. Verification took 0.002 ms per token. + + //Minting 100 tokens with 14 leading zeros took 69.14 ms per token and 40917 iterations in average. Verification took 0.06 ms per token. + //Minting 100 tokens with 15 leading zeros took 102.14 ms per token and 65735 iterations in average. Verification took 0.01 ms per token. + //Minting 100 tokens with 16 leading zeros took 209.44 ms per token and 135137 iterations in average. Verification took 0.01 ms per token. + //Minting 100 tokens with 17 leading zeros took 409.46 ms per token and 263751 iterations in average. Verification took 0.01 ms per token. + //Minting 100 tokens with 18 leading zeros took 864.21 ms per token and 555671 iterations in average. Verification took 0.0 ms per token. + //Minting 100 tokens with 19 leading zeros took 1851.33 ms per token and 1097760 iterations in average. Verification took 0.0 ms per token. + } + + private void run(int numLeadingZeros, StringBuilder stringBuilder) throws ExecutionException, InterruptedException { + int numTokens = 1000; + BigInteger target = ProofOfWorkService.getTarget(numLeadingZeros); + byte[] payload = RandomStringUtils.random(50, true, true).getBytes(StandardCharsets.UTF_8); + long ts = System.currentTimeMillis(); + List tokens = new ArrayList<>(); + for (int i = 0; i < numTokens; i++) { + byte[] challenge = UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8); + tokens.add(ProofOfWorkService.mint(payload, challenge, target).get()); + } + double size = tokens.size(); + long ts2 = System.currentTimeMillis(); + long averageCounter = Math.round(tokens.stream().mapToLong(ProofOfWork::getCounter).average().orElse(0)); + boolean allValid = tokens.stream().allMatch(ProofOfWorkService::verify); + assertTrue(allValid); + double time1 = (System.currentTimeMillis() - ts) / size; + double time2 = (System.currentTimeMillis() - ts2) / size; + stringBuilder.append("\nMinting ").append(numTokens) + .append(" tokens with ").append(numLeadingZeros) + .append(" leading zeros took ").append(time1) + .append(" ms per token and ").append(averageCounter) + .append(" iterations in average. Verification took ").append(time2) + .append(" ms per token."); + } +} diff --git a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java index 85d35e19af..685088aa88 100644 --- a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java +++ b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java @@ -23,7 +23,7 @@ import bisq.core.filter.FilterManager; import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferDirection; import bisq.core.offer.OfferRestrictions; import bisq.core.payment.AssetAccount; import bisq.core.payment.ChargeBackRisk; @@ -33,9 +33,9 @@ import bisq.core.payment.payload.PaymentMethod; import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.DisputeResult; import bisq.core.support.dispute.arbitration.TraderDataItem; -import bisq.core.trade.Contract; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.TradingPeer; +import bisq.core.trade.model.bisq_v1.Contract; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.model.TradingPeer; import bisq.core.user.User; import bisq.network.p2p.BootstrapListener; @@ -308,7 +308,7 @@ public class AccountAgeWitnessService { } private Optional findTradePeerWitness(Trade trade) { - TradingPeer tradingPeer = trade.getProcessModel().getTradingPeer(); + TradingPeer tradingPeer = trade.getProcessModel().getTradePeer(); return (tradingPeer == null || tradingPeer.getPaymentAccountPayload() == null || tradingPeer.getPubKeyRing() == null) ? @@ -421,11 +421,11 @@ public class AccountAgeWitnessService { String currencyCode, AccountAgeWitness accountAgeWitness, AccountAge accountAgeCategory, - OfferPayload.Direction direction, + OfferDirection direction, PaymentMethod paymentMethod) { if (CurrencyUtil.isCryptoCurrency(currencyCode) || !PaymentMethod.hasChargebackRisk(paymentMethod, currencyCode) || - direction == OfferPayload.Direction.SELL) { + direction == OfferDirection.SELL) { return maxTradeLimit.value; } @@ -500,7 +500,7 @@ public class AccountAgeWitnessService { return getAccountAge(getMyWitness(paymentAccountPayload), new Date()); } - public long getMyTradeLimit(PaymentAccount paymentAccount, String currencyCode, OfferPayload.Direction direction) { + public long getMyTradeLimit(PaymentAccount paymentAccount, String currencyCode, OfferDirection direction) { if (paymentAccount == null) return 0; @@ -564,7 +564,7 @@ public class AccountAgeWitnessService { return false; // Check if the peers trade limit is not less than the trade amount - if (!verifyPeersTradeLimit(trade.getOffer(), trade.getTradeAmount(), peersWitness, peersCurrentDate, + if (!verifyPeersTradeLimit(trade.getOffer(), trade.getAmount(), peersWitness, peersCurrentDate, errorMessageHandler)) { log.error("verifyPeersTradeLimit failed: peersPaymentAccountPayload {}", peersPaymentAccountPayload); return false; @@ -641,13 +641,12 @@ public class AccountAgeWitnessService { ErrorMessageHandler errorMessageHandler) { checkNotNull(offer); final String currencyCode = offer.getCurrencyCode(); - final Coin defaultMaxTradeLimit = PaymentMethod.getPaymentMethodById( - offer.getOfferPayload().getPaymentMethodId()).getMaxTradeLimitAsCoin(currencyCode); + final Coin defaultMaxTradeLimit = offer.getPaymentMethod().getMaxTradeLimitAsCoin(currencyCode); long peersCurrentTradeLimit = defaultMaxTradeLimit.value; if (!hasTradeLimitException(peersWitness)) { final long accountSignAge = getWitnessSignAge(peersWitness, peersCurrentDate); AccountAge accountAgeCategory = getPeersAccountAgeCategory(accountSignAge); - OfferPayload.Direction direction = offer.isMyOffer(keyRing) ? + OfferDirection direction = offer.isMyOffer(keyRing) ? offer.getMirroredDirection() : offer.getDirection(); peersCurrentTradeLimit = getTradeLimit(defaultMaxTradeLimit, currencyCode, peersWitness, accountAgeCategory, direction, offer.getPaymentMethod()); @@ -731,9 +730,9 @@ public class AccountAgeWitnessService { public Optional traderSignAndPublishPeersAccountAgeWitness(Trade trade) { AccountAgeWitness peersWitness = findTradePeerWitness(trade).orElse(null); - Coin tradeAmount = trade.getTradeAmount(); - checkNotNull(trade.getProcessModel().getTradingPeer().getPubKeyRing(), "Peer must have a keyring"); - PublicKey peersPubKey = trade.getProcessModel().getTradingPeer().getPubKeyRing().getSignaturePubKey(); + Coin tradeAmount = trade.getAmount(); + checkNotNull(trade.getProcessModel().getTradePeer().getPubKeyRing(), "Peer must have a keyring"); + PublicKey peersPubKey = trade.getProcessModel().getTradePeer().getPubKeyRing().getSignaturePubKey(); checkNotNull(peersWitness, "Not able to find peers witness, unable to sign for trade {}", trade.toString()); checkNotNull(tradeAmount, "Trade amount must not be null"); @@ -926,7 +925,7 @@ public class AccountAgeWitnessService { return accountIsSigner(myWitness) && !peerHasSignedWitness(trade) && - tradeAmountIsSufficient(trade.getTradeAmount()); + tradeAmountIsSufficient(trade.getAmount()); } public String getSignInfoFromAccount(PaymentAccount paymentAccount) { diff --git a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessUtils.java b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessUtils.java index 628f0a8186..f537456db6 100644 --- a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessUtils.java +++ b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessUtils.java @@ -20,7 +20,7 @@ package bisq.core.account.witness; import bisq.core.account.sign.SignedWitness; import bisq.core.account.sign.SignedWitnessService; import bisq.core.payment.payload.PaymentAccountPayload; -import bisq.core.trade.Trade; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.network.p2p.storage.P2PDataStorage; @@ -143,7 +143,7 @@ public class AccountAgeWitnessUtils { } boolean isSignWitnessTrade = accountAgeWitnessService.accountIsSigner(witness) && !accountAgeWitnessService.peerHasSignedWitness(trade) && - accountAgeWitnessService.tradeAmountIsSufficient(trade.getTradeAmount()); + accountAgeWitnessService.tradeAmountIsSufficient(trade.getAmount()); log.info("AccountSigning debug log: " + "\ntradeId: {}" + "\nis buyer: {}" + @@ -164,8 +164,8 @@ public class AccountAgeWitnessUtils { checkingSignTrade, // Following cases added to use same logic as in seller signing check accountAgeWitnessService.accountIsSigner(witness), accountAgeWitnessService.peerHasSignedWitness(trade), - trade.getTradeAmount(), - accountAgeWitnessService.tradeAmountIsSufficient(trade.getTradeAmount()), + trade.getAmount(), + accountAgeWitnessService.tradeAmountIsSufficient(trade.getAmount()), isSignWitnessTrade); } } diff --git a/core/src/main/java/bisq/core/api/CoreApi.java b/core/src/main/java/bisq/core/api/CoreApi.java index 2fb5c39c5a..380fd670ca 100644 --- a/core/src/main/java/bisq/core/api/CoreApi.java +++ b/core/src/main/java/bisq/core/api/CoreApi.java @@ -25,7 +25,9 @@ import bisq.core.offer.Offer; import bisq.core.offer.OpenOffer; import bisq.core.payment.PaymentAccount; import bisq.core.payment.payload.PaymentMethod; -import bisq.core.trade.Trade; +import bisq.core.trade.bisq_v1.TradeResultHandler; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; import bisq.core.trade.statistics.TradeStatistics3; import bisq.core.trade.statistics.TradeStatisticsManager; @@ -117,6 +119,10 @@ public class CoreApi { // Offers /////////////////////////////////////////////////////////////////////////////////////////// + public Offer getBsqSwapOffer(String id) { + return coreOffersService.getBsqSwapOffer(id); + } + public Offer getOffer(String id) { return coreOffersService.getOffer(id); } @@ -125,6 +131,14 @@ public class CoreApi { return coreOffersService.getMyOffer(id); } + public Offer getMyBsqSwapOffer(String id) { + return coreOffersService.getMyBsqSwapOffer(id); + } + + public List getBsqSwapOffers(String direction) { + return coreOffersService.getBsqSwapOffers(direction); + } + public List getOffers(String direction, String currencyCode) { return coreOffersService.getOffers(direction, currencyCode); } @@ -133,18 +147,38 @@ public class CoreApi { return coreOffersService.getMyOffers(direction, currencyCode); } - public void createAnPlaceOffer(String currencyCode, - String directionAsString, - String priceAsString, - boolean useMarketBasedPrice, - double marketPriceMargin, - long amountAsLong, - long minAmountAsLong, - double buyerSecurityDeposit, - long triggerPrice, - String paymentAccountId, - String makerFeeCurrencyCode, - Consumer resultHandler) { + public List getMyBsqSwapOffers(String direction) { + return coreOffersService.getMyBsqSwapOffers(direction); + } + + public OpenOffer getMyOpenBsqSwapOffer(String id) { + return coreOffersService.getMyOpenBsqSwapOffer(id); + } + + public void createAndPlaceBsqSwapOffer(String directionAsString, + long amountAsLong, + long minAmountAsLong, + String priceAsString, + Consumer resultHandler) { + coreOffersService.createAndPlaceBsqSwapOffer(directionAsString, + amountAsLong, + minAmountAsLong, + priceAsString, + resultHandler); + } + + public void createAndPlaceOffer(String currencyCode, + String directionAsString, + String priceAsString, + boolean useMarketBasedPrice, + double marketPriceMargin, + long amountAsLong, + long minAmountAsLong, + double buyerSecurityDeposit, + long triggerPrice, + String paymentAccountId, + String makerFeeCurrencyCode, + Consumer resultHandler) { coreOffersService.createAndPlaceOffer(currencyCode, directionAsString, priceAsString, @@ -206,11 +240,13 @@ public class CoreApi { public PaymentAccount createCryptoCurrencyPaymentAccount(String accountName, String currencyCode, String address, - boolean tradeInstant) { + boolean tradeInstant, + boolean isBsqSwap) { return paymentAccountsService.createCryptoCurrencyPaymentAccount(accountName, currencyCode, address, - tradeInstant); + tradeInstant, + isBsqSwap); } public List getCryptoCurrencyPaymentMethods() { @@ -229,6 +265,19 @@ public class CoreApi { // Trades /////////////////////////////////////////////////////////////////////////////////////////// + public void takeBsqSwapOffer(String offerId, + String paymentAccountId, + String takerFeeCurrencyCode, + TradeResultHandler tradeResultHandler, + ErrorMessageHandler errorMessageHandler) { + Offer bsqSwapOffer = coreOffersService.getBsqSwapOffer(offerId); + coreTradesService.takeBsqSwapOffer(bsqSwapOffer, + paymentAccountId, + takerFeeCurrencyCode, + tradeResultHandler, + errorMessageHandler); + } + public void takeOffer(String offerId, String paymentAccountId, String takerFeeCurrencyCode, @@ -258,6 +307,10 @@ public class CoreApi { coreTradesService.withdrawFunds(tradeId, address, memo); } + public BsqSwapTrade getBsqSwapTrade(String tradeId) { + return coreTradesService.getBsqSwapTrade(tradeId); + } + public Trade getTrade(String tradeId) { return coreTradesService.getTrade(tradeId); } diff --git a/core/src/main/java/bisq/core/api/CoreOffersService.java b/core/src/main/java/bisq/core/api/CoreOffersService.java index 4649f4f0b3..adbcee01dd 100644 --- a/core/src/main/java/bisq/core/api/CoreOffersService.java +++ b/core/src/main/java/bisq/core/api/CoreOffersService.java @@ -19,15 +19,17 @@ package bisq.core.api; import bisq.core.monetary.Altcoin; import bisq.core.monetary.Price; -import bisq.core.offer.CreateOfferService; -import bisq.core.offer.MutableOfferPayloadFields; import bisq.core.offer.Offer; import bisq.core.offer.OfferBookService; -import bisq.core.offer.OfferFilter; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferDirection; +import bisq.core.offer.OfferFilterService; import bisq.core.offer.OfferUtil; import bisq.core.offer.OpenOffer; import bisq.core.offer.OpenOfferManager; +import bisq.core.offer.bisq_v1.CreateOfferService; +import bisq.core.offer.bisq_v1.MutableOfferPayloadFields; +import bisq.core.offer.bisq_v1.OfferPayload; +import bisq.core.offer.bsq_swap.OpenBsqSwapOfferService; import bisq.core.payment.PaymentAccount; import bisq.core.provider.price.PriceFeedService; import bisq.core.user.User; @@ -57,8 +59,7 @@ import static bisq.common.util.MathUtils.roundDoubleToLong; import static bisq.common.util.MathUtils.scaleUpByPowerOf10; import static bisq.core.locale.CurrencyUtil.isCryptoCurrency; import static bisq.core.offer.Offer.State; -import static bisq.core.offer.OfferPayload.Direction; -import static bisq.core.offer.OfferPayload.Direction.BUY; +import static bisq.core.offer.OfferDirection.BUY; import static bisq.core.offer.OpenOffer.State.AVAILABLE; import static bisq.core.offer.OpenOffer.State.DEACTIVATED; import static bisq.core.payment.PaymentAccountUtil.isPaymentAccountValidForOffer; @@ -85,8 +86,9 @@ class CoreOffersService { private final CoreWalletsService coreWalletsService; private final CreateOfferService createOfferService; private final OfferBookService offerBookService; - private final OfferFilter offerFilter; + private final OfferFilterService offerFilterService; private final OpenOfferManager openOfferManager; + private final OpenBsqSwapOfferService openBsqSwapOfferService; private final OfferUtil offerUtil; private final PriceFeedService priceFeedService; private final User user; @@ -97,8 +99,9 @@ class CoreOffersService { CoreWalletsService coreWalletsService, CreateOfferService createOfferService, OfferBookService offerBookService, - OfferFilter offerFilter, + OfferFilterService offerFilterService, OpenOfferManager openOfferManager, + OpenBsqSwapOfferService openBsqSwapOfferService, OfferUtil offerUtil, PriceFeedService priceFeedService, User user) { @@ -107,18 +110,29 @@ class CoreOffersService { this.coreWalletsService = coreWalletsService; this.createOfferService = createOfferService; this.offerBookService = offerBookService; - this.offerFilter = offerFilter; + this.offerFilterService = offerFilterService; this.openOfferManager = openOfferManager; + this.openBsqSwapOfferService = openBsqSwapOfferService; this.offerUtil = offerUtil; this.priceFeedService = priceFeedService; this.user = user; } + Offer getBsqSwapOffer(String id) { + return offerBookService.getOffers().stream() + .filter(o -> o.getId().equals(id)) + .filter(o -> !o.isMyOffer(keyRing)) + .filter(o -> offerFilterService.canTakeOffer(o, coreContext.isApiUser()).isValid()) + .filter(o -> o.isBsqSwapOffer()) + .findAny().orElseThrow(() -> + new IllegalStateException(format("offer with id '%s' not found", id))); + } + Offer getOffer(String id) { return offerBookService.getOffers().stream() .filter(o -> o.getId().equals(id)) .filter(o -> !o.isMyOffer(keyRing)) - .filter(o -> offerFilter.canTakeOffer(o, coreContext.isApiUser()).isValid()) + .filter(o -> offerFilterService.canTakeOffer(o, coreContext.isApiUser()).isValid()) .findAny().orElseThrow(() -> new IllegalStateException(format("offer with id '%s' not found", id))); } @@ -131,11 +145,31 @@ class CoreOffersService { new IllegalStateException(format("offer with id '%s' not found", id))); } + Offer getMyBsqSwapOffer(String id) { + return offerBookService.getOffers().stream() + .filter(o -> o.getId().equals(id)) + .filter(o -> o.isMyOffer(keyRing)) + .filter(o -> o.isBsqSwapOffer()) + .findAny().orElseThrow(() -> + new IllegalStateException(format("offer with id '%s' not found", id))); + } + + + List getBsqSwapOffers(String direction) { + var offers = offerBookService.getOffers().stream() + .filter(o -> !o.isMyOffer(keyRing)) + .filter(o -> o.getDirection().name().equalsIgnoreCase(direction)) + .filter(o -> o.isBsqSwapOffer()) + .sorted(priceComparator(direction)) + .collect(Collectors.toList()); + return offers; + } + List getOffers(String direction, String currencyCode) { return offerBookService.getOffers().stream() .filter(o -> !o.isMyOffer(keyRing)) .filter(o -> offerMatchesDirectionAndCurrency(o, direction, currencyCode)) - .filter(o -> offerFilter.canTakeOffer(o, coreContext.isApiUser()).isValid()) + .filter(o -> offerFilterService.canTakeOffer(o, coreContext.isApiUser()).isValid()) .sorted(priceComparator(direction)) .collect(Collectors.toList()); } @@ -148,6 +182,24 @@ class CoreOffersService { .collect(Collectors.toList()); } + List getMyBsqSwapOffers(String direction) { + var offers = offerBookService.getOffers().stream() + .filter(o -> o.isMyOffer(keyRing)) + .filter(o -> o.getDirection().name().equalsIgnoreCase(direction)) + .filter(Offer::isBsqSwapOffer) + .sorted(priceComparator(direction)) + .collect(Collectors.toList()); + return offers; + } + + OpenOffer getMyOpenBsqSwapOffer(String id) { + return openOfferManager.getOpenOfferById(id) + .filter(open -> open.getOffer().isMyOffer(keyRing)) + .filter(open -> open.getOffer().isBsqSwapOffer()) + .orElseThrow(() -> + new IllegalStateException(format("openoffer with id '%s' not found", id))); + } + OpenOffer getMyOpenOffer(String id) { return openOfferManager.getOpenOfferById(id) .filter(open -> open.getOffer().isMyOffer(keyRing)) @@ -161,7 +213,28 @@ class CoreOffersService { .isPresent(); } - // Create and place new offer. + void createAndPlaceBsqSwapOffer(String directionAsString, + long amountAsLong, + long minAmountAsLong, + String priceAsString, + Consumer resultHandler) { + coreWalletsService.verifyWalletsAreAvailable(); + coreWalletsService.verifyEncryptedWalletIsUnlocked(); + + String currencyCode = "BSQ"; + String offerId = OfferUtil.getRandomOfferId(); + OfferDirection direction = OfferDirection.valueOf(directionAsString.toUpperCase()); + Coin amount = Coin.valueOf(amountAsLong); + Coin minAmount = Coin.valueOf(minAmountAsLong); + Price price = Price.valueOf(currencyCode, priceStringToLong(priceAsString, currencyCode)); + openBsqSwapOfferService.requestNewOffer(offerId, + direction, + amount, + minAmount, + price, + offer -> placeBsqSwapOffer(offer, () -> resultHandler.accept(offer))); + } + void createAndPlaceOffer(String currencyCode, String directionAsString, String priceAsString, @@ -183,8 +256,8 @@ class CoreOffersService { throw new IllegalArgumentException(format("payment account with id %s not found", paymentAccountId)); String upperCaseCurrencyCode = currencyCode.toUpperCase(); - String offerId = createOfferService.getRandomOfferId(); - Direction direction = Direction.valueOf(directionAsString.toUpperCase()); + String offerId = OfferUtil.getRandomOfferId(); + OfferDirection direction = OfferDirection.valueOf(directionAsString.toUpperCase()); Price price = Price.valueOf(upperCaseCurrencyCode, priceStringToLong(priceAsString, upperCaseCurrencyCode)); Coin amount = Coin.valueOf(amountAsLong); Coin minAmount = Coin.valueOf(minAmountAsLong); @@ -256,7 +329,7 @@ class CoreOffersService { editedMarketPriceMargin, editType); Offer editedOffer = new Offer(editedPayload); - priceFeedService.setCurrencyCode(openOffer.getOffer().getOfferPayload().getCurrencyCode()); + priceFeedService.setCurrencyCode(openOffer.getOffer().getCurrencyCode()); editedOffer.setPriceFeedService(priceFeedService); editedOffer.setState(State.AVAILABLE); openOfferManager.editOpenOfferStart(openOffer, @@ -277,6 +350,15 @@ class CoreOffersService { log::error); } + private void placeBsqSwapOffer(Offer offer, Runnable resultHandler) { + openBsqSwapOfferService.placeBsqSwapOffer(offer, + resultHandler, + log::error); + + if (offer.getErrorMessage() != null) + throw new IllegalStateException(offer.getErrorMessage()); + } + private void placeOffer(Offer offer, double buyerSecurityDeposit, long triggerPrice, @@ -302,7 +384,7 @@ class CoreOffersService { // code fields. Note: triggerPrice isDeactivated fields are in OpenOffer, not // in OfferPayload. Offer offer = openOffer.getOffer(); - String currencyCode = offer.getOfferPayload().getCurrencyCode(); + String currencyCode = offer.getCurrencyCode(); boolean isEditingPrice = editType.equals(FIXED_PRICE_ONLY) || editType.equals(FIXED_PRICE_AND_ACTIVATION_STATE); Price editedPrice; if (isEditingPrice) { @@ -320,15 +402,15 @@ class CoreOffersService { Objects.requireNonNull(editedPrice).getValue(), isUsingMktPriceMargin ? exactMultiply(editedMarketPriceMargin, 0.01) : 0.00, isUsingMktPriceMargin, - offer.getOfferPayload().getBaseCurrencyCode(), - offer.getOfferPayload().getCounterCurrencyCode(), + offer.getBaseCurrencyCode(), + offer.getCounterCurrencyCode(), offer.getPaymentMethod().getId(), offer.getMakerPaymentAccountId(), - offer.getOfferPayload().getCountryCode(), - offer.getOfferPayload().getAcceptedCountryCodes(), - offer.getOfferPayload().getBankId(), - offer.getOfferPayload().getAcceptedBankIds(), - offer.getOfferPayload().getExtraDataMap()); + offer.getCountryCode(), + offer.getAcceptedCountryCodes(), + offer.getBankId(), + offer.getAcceptedBankIds(), + offer.getExtraDataMap()); log.info("Merging OfferPayload with {}", mutableOfferPayloadFields); return offerUtil.getMergedOfferPayload(openOffer, mutableOfferPayloadFields); } @@ -336,7 +418,7 @@ class CoreOffersService { private void verifyPaymentAccountIsValidForNewOffer(Offer offer, PaymentAccount paymentAccount) { if (!isPaymentAccountValidForOffer(offer, paymentAccount)) { String error = format("cannot create %s offer with payment account %s", - offer.getOfferPayload().getCounterCurrencyCode(), + offer.getCounterCurrencyCode(), paymentAccount.getId()); throw new IllegalStateException(error); } @@ -346,7 +428,7 @@ class CoreOffersService { String direction, String currencyCode) { var offerOfWantedDirection = offer.getDirection().name().equalsIgnoreCase(direction); - var offerInWantedCurrency = offer.getOfferPayload().getCounterCurrencyCode() + var offerInWantedCurrency = offer.getCounterCurrencyCode() .equalsIgnoreCase(currencyCode); return offerOfWantedDirection && offerInWantedCurrency; } diff --git a/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java b/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java index 416dde6531..dcce34a46b 100644 --- a/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java +++ b/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java @@ -20,6 +20,7 @@ package bisq.core.api; import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.api.model.PaymentAccountForm; import bisq.core.locale.CryptoCurrency; +import bisq.core.payment.AssetAccount; import bisq.core.payment.CryptoCurrencyAccount; import bisq.core.payment.InstantCryptoCurrencyAccount; import bisq.core.payment.PaymentAccount; @@ -82,7 +83,7 @@ class CorePaymentAccountsService { List getFiatPaymentMethods() { return PaymentMethod.getPaymentMethods().stream() - .filter(paymentMethod -> !paymentMethod.isAsset()) + .filter(PaymentMethod::isFiat) .sorted(Comparator.comparing(PaymentMethod::getId)) .collect(Collectors.toList()); } @@ -102,7 +103,8 @@ class CorePaymentAccountsService { PaymentAccount createCryptoCurrencyPaymentAccount(String accountName, String currencyCode, String address, - boolean tradeInstant) { + boolean tradeInstant, + boolean isBsqSwap) { String bsqCode = currencyCode.toUpperCase(); if (!bsqCode.equals("BSQ")) throw new IllegalArgumentException("api does not currently support " + currencyCode + " accounts"); @@ -110,12 +112,21 @@ class CorePaymentAccountsService { // Validate the BSQ address string but ignore the return value. coreWalletsService.getValidBsqAddress(address); - var cryptoCurrencyAccount = tradeInstant - ? (InstantCryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS_INSTANT) - : (CryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS); + // 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); + } cryptoCurrencyAccount.init(); cryptoCurrencyAccount.setAccountName(accountName); - cryptoCurrencyAccount.setAddress(address); + if (!isBsqSwap) { + ((AssetAccount) cryptoCurrencyAccount).setAddress(address); + } + Optional cryptoCurrency = getCryptoCurrency(bsqCode); cryptoCurrency.ifPresent(cryptoCurrencyAccount::setSingleTradeCurrency); user.addPaymentAccount(cryptoCurrencyAccount); @@ -132,7 +143,7 @@ class CorePaymentAccountsService { List getCryptoCurrencyPaymentMethods() { return PaymentMethod.getPaymentMethods().stream() - .filter(PaymentMethod::isAsset) + .filter(PaymentMethod::isAltcoin) .sorted(Comparator.comparing(PaymentMethod::getId)) .collect(Collectors.toList()); } diff --git a/core/src/main/java/bisq/core/api/CoreTradesService.java b/core/src/main/java/bisq/core/api/CoreTradesService.java index 2f5683108a..5c08a61a48 100644 --- a/core/src/main/java/bisq/core/api/CoreTradesService.java +++ b/core/src/main/java/bisq/core/api/CoreTradesService.java @@ -21,14 +21,17 @@ import bisq.core.btc.model.AddressEntry; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.offer.Offer; import bisq.core.offer.OfferUtil; -import bisq.core.offer.takeoffer.TakeOfferModel; -import bisq.core.trade.Tradable; -import bisq.core.trade.Trade; +import bisq.core.offer.bisq_v1.TakeOfferModel; +import bisq.core.offer.bsq_swap.BsqSwapTakeOfferModel; +import bisq.core.trade.ClosedTradableManager; import bisq.core.trade.TradeManager; -import bisq.core.trade.TradeUtil; -import bisq.core.trade.closed.ClosedTradableManager; -import bisq.core.trade.protocol.BuyerProtocol; -import bisq.core.trade.protocol.SellerProtocol; +import bisq.core.trade.bisq_v1.TradeResultHandler; +import bisq.core.trade.bisq_v1.TradeUtil; +import bisq.core.trade.model.Tradable; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bisq_v1.BuyerProtocol; +import bisq.core.trade.protocol.bisq_v1.SellerProtocol; import bisq.core.user.User; import bisq.core.util.validation.BtcAddressValidator; @@ -60,6 +63,7 @@ class CoreTradesService { private final OfferUtil offerUtil; private final ClosedTradableManager closedTradableManager; private final TakeOfferModel takeOfferModel; + private final BsqSwapTakeOfferModel bsqSwapTakeOfferModel; private final TradeManager tradeManager; private final TradeUtil tradeUtil; private final User user; @@ -71,6 +75,7 @@ class CoreTradesService { OfferUtil offerUtil, ClosedTradableManager closedTradableManager, TakeOfferModel takeOfferModel, + BsqSwapTakeOfferModel bsqSwapTakeOfferModel, TradeManager tradeManager, TradeUtil tradeUtil, User user) { @@ -80,11 +85,33 @@ class CoreTradesService { this.offerUtil = offerUtil; this.closedTradableManager = closedTradableManager; this.takeOfferModel = takeOfferModel; + this.bsqSwapTakeOfferModel = bsqSwapTakeOfferModel; this.tradeManager = tradeManager; this.tradeUtil = tradeUtil; this.user = user; } + // todo we need to pass the intended trade amount + void takeBsqSwapOffer(Offer offer, + String paymentAccountId, + String takerFeeCurrencyCode, + TradeResultHandler tradeResultHandler, + ErrorMessageHandler errorMessageHandler) { + coreWalletsService.verifyWalletsAreAvailable(); + coreWalletsService.verifyEncryptedWalletIsUnlocked(); + + bsqSwapTakeOfferModel.initWithData(offer); + + //todo use the intended trade amount + bsqSwapTakeOfferModel.applyAmount(offer.getAmount()); + + log.info("Initiating take {} offer, {}", + offer.isBuyOffer() ? "buy" : "sell", + bsqSwapTakeOfferModel); + + bsqSwapTakeOfferModel.onTakeOffer(tradeResultHandler, log::warn, errorMessageHandler, coreContext.isApiUser()); + } + void takeOffer(Offer offer, String paymentAccountId, String takerFeeCurrencyCode, @@ -100,7 +127,7 @@ class CoreTradesService { throw new IllegalArgumentException(format("payment account with id '%s' not found", paymentAccountId)); var useSavingsWallet = true; - //noinspection ConstantConditions + takeOfferModel.initModel(offer, paymentAccount, useSavingsWallet); log.info("Initiating take {} offer, {}", offer.isBuyOffer() ? "buy" : "sell", @@ -205,6 +232,13 @@ class CoreTradesService { }); } + BsqSwapTrade getBsqSwapTrade(String tradeId) { + coreWalletsService.verifyWalletsAreAvailable(); + coreWalletsService.verifyEncryptedWalletIsUnlocked(); + return tradeManager.findBsqSwapTradeById(tradeId).orElseThrow(() -> + new IllegalArgumentException(format("trade with id '%s' not found", tradeId))); + } + String getTradeRole(String tradeId) { coreWalletsService.verifyWalletsAreAvailable(); coreWalletsService.verifyEncryptedWalletIsUnlocked(); diff --git a/core/src/main/java/bisq/core/api/CoreWalletsService.java b/core/src/main/java/bisq/core/api/CoreWalletsService.java index 54f841e125..f2dec09a53 100644 --- a/core/src/main/java/bisq/core/api/CoreWalletsService.java +++ b/core/src/main/java/bisq/core/api/CoreWalletsService.java @@ -82,7 +82,6 @@ import lombok.extern.slf4j.Slf4j; import javax.annotation.Nullable; -import static bisq.common.config.BaseCurrencyNetwork.BTC_DAO_REGTEST; import static bisq.core.btc.wallet.Restrictions.getMinNonDustOutput; import static bisq.core.util.ParsingUtils.parseToCoin; import static java.lang.String.format; @@ -583,14 +582,14 @@ class CoreWalletsService { verifyWalletsAreAvailable(); verifyEncryptedWalletIsUnlocked(); - var availableConfirmedBalance = bsqWalletService.getAvailableConfirmedBalance(); + var availableBalance = bsqWalletService.getAvailableBalance(); var unverifiedBalance = bsqWalletService.getUnverifiedBalance(); var unconfirmedChangeBalance = bsqWalletService.getUnconfirmedChangeBalance(); var lockedForVotingBalance = bsqWalletService.getLockedForVotingBalance(); var lockupBondsBalance = bsqWalletService.getLockupBondsBalance(); var unlockingBondsBalance = bsqWalletService.getUnlockingBondsBalance(); - return new BsqBalanceInfo(availableConfirmedBalance.value, + return new BsqBalanceInfo(availableBalance.value, unverifiedBalance.value, unconfirmedChangeBalance.value, lockedForVotingBalance.value, diff --git a/core/src/main/java/bisq/core/api/model/BsqBalanceInfo.java b/core/src/main/java/bisq/core/api/model/BsqBalanceInfo.java index 23324e21f3..cde41915c2 100644 --- a/core/src/main/java/bisq/core/api/model/BsqBalanceInfo.java +++ b/core/src/main/java/bisq/core/api/model/BsqBalanceInfo.java @@ -17,20 +17,20 @@ public class BsqBalanceInfo implements Payload { -1); // All balances are in BSQ satoshis. - private final long availableConfirmedBalance; + private final long availableBalance; private final long unverifiedBalance; private final long unconfirmedChangeBalance; private final long lockedForVotingBalance; private final long lockupBondsBalance; private final long unlockingBondsBalance; - public BsqBalanceInfo(long availableConfirmedBalance, + public BsqBalanceInfo(long availableBalance, long unverifiedBalance, long unconfirmedChangeBalance, long lockedForVotingBalance, long lockupBondsBalance, long unlockingBondsBalance) { - this.availableConfirmedBalance = availableConfirmedBalance; + this.availableBalance = availableBalance; this.unverifiedBalance = unverifiedBalance; this.unconfirmedChangeBalance = unconfirmedChangeBalance; this.lockedForVotingBalance = lockedForVotingBalance; @@ -39,14 +39,14 @@ public class BsqBalanceInfo implements Payload { } @VisibleForTesting - public static BsqBalanceInfo valueOf(long availableConfirmedBalance, + public static BsqBalanceInfo valueOf(long availableBalance, long unverifiedBalance, long unconfirmedChangeBalance, long lockedForVotingBalance, long lockupBondsBalance, long unlockingBondsBalance) { // Convenience for creating a model instance instead of a proto. - return new BsqBalanceInfo(availableConfirmedBalance, + return new BsqBalanceInfo(availableBalance, unverifiedBalance, unconfirmedChangeBalance, lockedForVotingBalance, @@ -58,10 +58,11 @@ public class BsqBalanceInfo implements Payload { // PROTO BUFFER /////////////////////////////////////////////////////////////////////////////////////////// + // TODO rename availableConfirmedBalance in proto if possible @Override public bisq.proto.grpc.BsqBalanceInfo toProtoMessage() { return bisq.proto.grpc.BsqBalanceInfo.newBuilder() - .setAvailableConfirmedBalance(availableConfirmedBalance) + .setAvailableConfirmedBalance(availableBalance) .setUnverifiedBalance(unverifiedBalance) .setUnconfirmedChangeBalance(unconfirmedChangeBalance) .setLockedForVotingBalance(lockedForVotingBalance) @@ -83,7 +84,7 @@ public class BsqBalanceInfo implements Payload { @Override public String toString() { return "BsqBalanceInfo{" + - "availableConfirmedBalance=" + availableConfirmedBalance + + "availableBalance=" + availableBalance + ", unverifiedBalance=" + unverifiedBalance + ", unconfirmedChangeBalance=" + unconfirmedChangeBalance + ", lockedForVotingBalance=" + lockedForVotingBalance + diff --git a/core/src/main/java/bisq/core/api/model/BsqSwapOfferInfo.java b/core/src/main/java/bisq/core/api/model/BsqSwapOfferInfo.java new file mode 100644 index 0000000000..487e4fbf23 --- /dev/null +++ b/core/src/main/java/bisq/core/api/model/BsqSwapOfferInfo.java @@ -0,0 +1,227 @@ +/* + * 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.core.api.model; + +import bisq.core.offer.Offer; + +import bisq.common.Payload; + +import java.util.Objects; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +@EqualsAndHashCode +@ToString +@Getter +public class BsqSwapOfferInfo implements Payload { + private final String id; + private final String direction; + private final long amount; + private final long minAmount; + private final long price; + private final String makerPaymentAccountId; + private final String paymentMethodId; + private final String paymentMethodShortName; + private final String baseCurrencyCode; + private final String counterCurrencyCode; + private final long date; + private final String ownerNodeAddress; + private final String pubKeyRing; // TODO ? + private final String versionNumber; + private final int protocolVersion; + + public BsqSwapOfferInfo(BsqSwapOfferInfoBuilder builder) { + this.id = builder.id; + this.direction = builder.direction; + this.amount = builder.amount; + this.minAmount = builder.minAmount; + this.price = builder.price; + this.makerPaymentAccountId = builder.makerPaymentAccountId; + this.paymentMethodId = builder.paymentMethodId; + this.paymentMethodShortName = builder.paymentMethodShortName; + this.baseCurrencyCode = builder.baseCurrencyCode; + this.counterCurrencyCode = builder.counterCurrencyCode; + this.date = builder.date; + this.ownerNodeAddress = builder.ownerNodeAddress; + this.pubKeyRing = builder.pubKeyRing; + this.versionNumber = builder.versionNumber; + this.protocolVersion = builder.protocolVersion; + } + + public static BsqSwapOfferInfo toBsqSwapOfferInfo(Offer offer) { + // TODO support triggerPrice + return getAtomicOfferInfoBuilder(offer).build(); + } + + private static BsqSwapOfferInfoBuilder getAtomicOfferInfoBuilder(Offer offer) { + return new BsqSwapOfferInfoBuilder() + .withId(offer.getId()) + .withDirection(offer.getDirection().name()) + .withAmount(offer.getAmount().value) + .withMinAmount(offer.getMinAmount().value) + .withPrice(Objects.requireNonNull(offer.getPrice()).getValue()) + //.withMakerPaymentAccountId(offer.getOfferPayloadI().getMakerPaymentAccountId()) + //.withPaymentMethodId(offer.getOfferPayloadI().getPaymentMethodId()) + //.withPaymentMethodShortName(getPaymentMethodById(offer.getOfferPayloadI().getPaymentMethodId()).getShortName()) + .withBaseCurrencyCode(offer.getOfferPayloadBase().getBaseCurrencyCode()) + .withCounterCurrencyCode(offer.getOfferPayloadBase().getCounterCurrencyCode()) + .withDate(offer.getDate().getTime()) + .withOwnerNodeAddress(offer.getOfferPayloadBase().getOwnerNodeAddress().getFullAddress()) + .withPubKeyRing(offer.getOfferPayloadBase().getPubKeyRing().toString()) + .withVersionNumber(offer.getOfferPayloadBase().getVersionNr()) + .withProtocolVersion(offer.getOfferPayloadBase().getProtocolVersion()); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public bisq.proto.grpc.BsqSwapOfferInfo toProtoMessage() { + return bisq.proto.grpc.BsqSwapOfferInfo.newBuilder() + .setId(id) + .setDirection(direction) + .setAmount(amount) + .setMinAmount(minAmount) + .setPrice(price) + .setBaseCurrencyCode(baseCurrencyCode) + .setCounterCurrencyCode(counterCurrencyCode) + .setDate(date) + .setOwnerNodeAddress(ownerNodeAddress) + .setPubKeyRing(pubKeyRing) + .setVersionNr(versionNumber) + .setProtocolVersion(protocolVersion) + .build(); + } + + public static BsqSwapOfferInfo fromProto(bisq.proto.grpc.BsqSwapOfferInfo proto) { + return new BsqSwapOfferInfoBuilder() + .withId(proto.getId()) + .withDirection(proto.getDirection()) + .withAmount(proto.getAmount()) + .withMinAmount(proto.getMinAmount()) + .withPrice(proto.getPrice()) + .withBaseCurrencyCode(proto.getBaseCurrencyCode()) + .withCounterCurrencyCode(proto.getCounterCurrencyCode()) + .withDate(proto.getDate()) + .withOwnerNodeAddress(proto.getOwnerNodeAddress()) + .withPubKeyRing(proto.getPubKeyRing()) + .withVersionNumber(proto.getVersionNr()) + .withProtocolVersion(proto.getProtocolVersion()) + .build(); + } + + public static class BsqSwapOfferInfoBuilder { + private String id; + private String direction; + private long amount; + private long minAmount; + private long price; + private String makerPaymentAccountId; + private String paymentMethodId; + private String paymentMethodShortName; + private String baseCurrencyCode; + private String counterCurrencyCode; + private long date; + private String ownerNodeAddress; + private String pubKeyRing; + private String versionNumber; + private int protocolVersion; + + public BsqSwapOfferInfoBuilder withId(String id) { + this.id = id; + return this; + } + + public BsqSwapOfferInfoBuilder withDirection(String direction) { + this.direction = direction; + return this; + } + + public BsqSwapOfferInfoBuilder withAmount(long amount) { + this.amount = amount; + return this; + } + + public BsqSwapOfferInfoBuilder withMinAmount(long minAmount) { + this.minAmount = minAmount; + return this; + } + + public BsqSwapOfferInfoBuilder withPrice(long price) { + this.price = price; + return this; + } + + public BsqSwapOfferInfoBuilder withMakerPaymentAccountId(String makerPaymentAccountId) { + this.makerPaymentAccountId = makerPaymentAccountId; + return this; + } + + public BsqSwapOfferInfoBuilder withPaymentMethodId(String paymentMethodId) { + this.paymentMethodId = paymentMethodId; + return this; + } + + public BsqSwapOfferInfoBuilder withPaymentMethodShortName(String paymentMethodShortName) { + this.paymentMethodShortName = paymentMethodShortName; + return this; + } + + public BsqSwapOfferInfoBuilder withBaseCurrencyCode(String baseCurrencyCode) { + this.baseCurrencyCode = baseCurrencyCode; + return this; + } + + public BsqSwapOfferInfoBuilder withCounterCurrencyCode(String counterCurrencyCode) { + this.counterCurrencyCode = counterCurrencyCode; + return this; + } + + public BsqSwapOfferInfoBuilder withDate(long date) { + this.date = date; + return this; + } + + public BsqSwapOfferInfoBuilder withOwnerNodeAddress(String ownerNodeAddress) { + this.ownerNodeAddress = ownerNodeAddress; + return this; + } + + public BsqSwapOfferInfoBuilder withPubKeyRing(String pubKeyRing) { + this.pubKeyRing = pubKeyRing; + return this; + } + + public BsqSwapOfferInfoBuilder withVersionNumber(String versionNumber) { + this.versionNumber = versionNumber; + return this; + } + + public BsqSwapOfferInfoBuilder withProtocolVersion(int protocolVersion) { + this.protocolVersion = protocolVersion; + return this; + } + + public BsqSwapOfferInfo build() { + return new BsqSwapOfferInfo(this); + } + } +} diff --git a/core/src/main/java/bisq/core/api/model/BsqSwapTradeInfo.java b/core/src/main/java/bisq/core/api/model/BsqSwapTradeInfo.java new file mode 100644 index 0000000000..c24dcca016 --- /dev/null +++ b/core/src/main/java/bisq/core/api/model/BsqSwapTradeInfo.java @@ -0,0 +1,323 @@ +/* + * 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.core.api.model; + +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; + +import bisq.common.Payload; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +import static bisq.core.api.model.BsqSwapOfferInfo.toBsqSwapOfferInfo; + +@EqualsAndHashCode +@ToString +@Getter +public class BsqSwapTradeInfo implements Payload { + + private final BsqSwapOfferInfo bsqSwapOffer; + private final String tradeId; + private final String tempTradingPeerNodeAddress; + private final String peerNodeAddress; + private final String txId; + private final long bsqTradeAmount; + private final long bsqMaxTradeAmount; + private final long bsqMinTradeAmount; + private final long btcTradeAmount; + private final long btcMaxTradeAmount; + private final long btcMinTradeAmount; + private final long tradePrice; + private final long bsqMakerTradeFee; + private final long bsqTakerTradeFee; + private final long txFeePerVbyte; + private final long txFee; + private final String makerBsqAddress; + private final String makerBtcAddress; + private final String takerBsqAddress; + private final String takerBtcAddress; + private final long takeOfferDate; + private final String state; + private final String errorMessage; + + public BsqSwapTradeInfo(BsqSwapTradeInfoBuilder builder) { + this.bsqSwapOffer = builder.bsqSwapOfferInfo; + this.tradeId = builder.tradeId; + this.tempTradingPeerNodeAddress = builder.tempTradingPeerNodeAddress; + this.peerNodeAddress = builder.peerNodeAddress; + this.txId = builder.txId; + this.bsqTradeAmount = builder.bsqTradeAmount; + this.bsqMaxTradeAmount = builder.bsqMaxTradeAmount; + this.bsqMinTradeAmount = builder.bsqMinTradeAmount; + this.btcTradeAmount = builder.btcTradeAmount; + this.btcMaxTradeAmount = builder.btcMaxTradeAmount; + this.btcMinTradeAmount = builder.btcMinTradeAmount; + this.tradePrice = builder.tradePrice; + this.bsqMakerTradeFee = builder.bsqMakerTradeFee; + this.bsqTakerTradeFee = builder.bsqTakerTradeFee; + this.txFeePerVbyte = builder.txFeePerVbyte; + this.txFee = builder.txFee; + this.makerBsqAddress = builder.makerBsqAddress; + this.makerBtcAddress = builder.makerBtcAddress; + this.takerBsqAddress = builder.takerBsqAddress; + this.takerBtcAddress = builder.takerBtcAddress; + this.takeOfferDate = builder.takeOfferDate; + this.state = builder.state; + this.errorMessage = builder.errorMessage; + } + + public static BsqSwapTradeInfo toBsqSwapTradeInfo(BsqSwapTrade trade) { + return toBsqSwapTradeInfo(trade, null); + } + + //TODO + public static BsqSwapTradeInfo toBsqSwapTradeInfo(BsqSwapTrade trade, String role) { + return new BsqSwapTradeInfoBuilder() + .withBsqSwapOffer(toBsqSwapOfferInfo(trade.getOffer())) + .withTradeId(trade.getId()) + .withTempTradingPeerNodeAddress(trade.getBsqSwapProtocolModel().getTempTradingPeerNodeAddress().getFullAddress()) + .withPeerNodeAddress(trade.getTradingPeerNodeAddress().getFullAddress()) + .withTxId(trade.getTxId()) + /* .withBsqTradeAmount(trade.getBsqSwapProtocolModel().getBsqTradeAmount()) + .withBsqMaxTradeAmount(trade.getBsqSwapProtocolModel().getBsqMaxTradeAmount()) + .withBsqMinTradeAmount(trade.getBsqSwapProtocolModel().getBsqMinTradeAmount()) + .withBtcTradeAmount(trade.getBsqSwapProtocolModel().getBtcTradeAmount()) + .withBtcMaxTradeAmount(trade.getBsqSwapProtocolModel().getBtcMaxTradeAmount()) + .withBtcMinTradeAmount(trade.getBsqSwapProtocolModel().getBtcMinTradeAmount()) + .withTradePrice(trade.getBsqSwapProtocolModel().getTradePrice()) + .withBsqMakerTradeFee(trade.getBsqSwapProtocolModel().getBsqMakerTradeFee()) + .withBsqTakerTradeFee(trade.getBsqSwapProtocolModel().getBsqTakerTradeFee()) + .withTxFeePerVbyte(trade.getBsqSwapProtocolModel().getTxFeePerVbyte()) + .withTxFee(trade.getBsqSwapProtocolModel().getTxFee()) + .withMakerBsqAddress(trade.getBsqSwapProtocolModel().getMakerBsqAddress()) + .withMakerBtcAddress(trade.getBsqSwapProtocolModel().getMakerBtcAddress()) + .withTakerBsqAddress(trade.getBsqSwapProtocolModel().getTakerBsqAddress()) + .withTakerBtcAddress(trade.getBsqSwapProtocolModel().getTakerBtcAddress())*/ + .withTakeOfferDate(trade.getTakeOfferDate()) + .withState(trade.getTradeState().name()) + .withErrorMessage(trade.getErrorMessage()) + .build(); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public bisq.proto.grpc.BsqSwapTradeInfo toProtoMessage() { + return bisq.proto.grpc.BsqSwapTradeInfo.newBuilder() + .setBsqSwapOfferInfo(bsqSwapOffer.toProtoMessage()) + .setTradeId(tradeId) + .setTempTradingPeerNodeAddress(tempTradingPeerNodeAddress != null ? tempTradingPeerNodeAddress : "") + .setPeerNodeAddress(peerNodeAddress != null ? peerNodeAddress : "") + .setTxId(txId != null ? txId : "") + .setBsqTradeAmount(bsqTradeAmount) + .setBsqMaxTradeAmount(bsqMaxTradeAmount) + .setBsqMinTradeAmount(bsqMinTradeAmount) + .setBtcTradeAmount(btcTradeAmount) + .setBtcMaxTradeAmount(btcMaxTradeAmount) + .setBtcMinTradeAmount(btcMinTradeAmount) + .setTradePrice(tradePrice) + .setBsqMakerTradeFee(bsqMakerTradeFee) + .setBsqTakerTradeFee(bsqTakerTradeFee) + .setTxFeePerVbyte(txFeePerVbyte) + .setTxFee(txFee) + .setMakerBsqAddress(makerBsqAddress != null ? makerBsqAddress : "") + .setTakerBsqAddress(takerBsqAddress != null ? takerBsqAddress : "") + .setMakerBtcAddress(makerBtcAddress != null ? makerBtcAddress : "") + .setTakerBtcAddress(takerBtcAddress != null ? takerBtcAddress : "") + .setTakeOfferDate(takeOfferDate) + .setState(state) + .setErrorMessage(errorMessage != null ? errorMessage : "") + .build(); + } + + public static BsqSwapTradeInfo fromProto(bisq.proto.grpc.BsqSwapTradeInfo proto) { + return new BsqSwapTradeInfoBuilder() + .withBsqSwapOffer(BsqSwapOfferInfo.fromProto(proto.getBsqSwapOfferInfo())) + .withTradeId(proto.getTradeId()) + .withTempTradingPeerNodeAddress(proto.getTempTradingPeerNodeAddress()) + .withPeerNodeAddress(proto.getPeerNodeAddress()) + .withTxId(proto.getTxId()) + .withBsqTradeAmount(proto.getBsqTradeAmount()) + .withBsqMaxTradeAmount(proto.getBsqMaxTradeAmount()) + .withBsqMinTradeAmount(proto.getBsqMinTradeAmount()) + .withBtcTradeAmount(proto.getBtcTradeAmount()) + .withBtcMaxTradeAmount(proto.getBtcMaxTradeAmount()) + .withBtcMinTradeAmount(proto.getBtcMinTradeAmount()) + .withTradePrice(proto.getTradePrice()) + .withBsqMakerTradeFee(proto.getBsqMakerTradeFee()) + .withBsqTakerTradeFee(proto.getBsqTakerTradeFee()) + .withTxFeePerVbyte(proto.getTxFeePerVbyte()) + .withTxFee(proto.getTxFee()) + .withMakerBsqAddress(proto.getMakerBsqAddress()) + .withMakerBtcAddress(proto.getMakerBtcAddress()) + .withTakerBsqAddress(proto.getTakerBsqAddress()) + .withTakerBtcAddress(proto.getTakerBtcAddress()) + .withTakeOfferDate(proto.getTakeOfferDate()) + .withState(proto.getState()) + .withErrorMessage(proto.getErrorMessage()) + .build(); + } + + public static class BsqSwapTradeInfoBuilder { + private BsqSwapOfferInfo bsqSwapOfferInfo; + private String tradeId; + private String tempTradingPeerNodeAddress; + private String peerNodeAddress; + private String txId; + private long bsqTradeAmount; + private long bsqMaxTradeAmount; + private long bsqMinTradeAmount; + private long btcTradeAmount; + private long btcMaxTradeAmount; + private long btcMinTradeAmount; + private long tradePrice; + private long bsqMakerTradeFee; + private long bsqTakerTradeFee; + private long txFeePerVbyte; + private long txFee; + private String makerBsqAddress; + private String makerBtcAddress; + private String takerBsqAddress; + private String takerBtcAddress; + private long takeOfferDate; + private String state; + private String errorMessage; + + public BsqSwapTradeInfoBuilder withBsqSwapOffer(BsqSwapOfferInfo bsqSwapOfferInfo) { + this.bsqSwapOfferInfo = bsqSwapOfferInfo; + return this; + } + + public BsqSwapTradeInfoBuilder withTradeId(String tradeId) { + this.tradeId = tradeId; + return this; + } + + public BsqSwapTradeInfoBuilder withTempTradingPeerNodeAddress(String tempTradingPeerNodeAddress) { + this.tempTradingPeerNodeAddress = tempTradingPeerNodeAddress; + return this; + } + + public BsqSwapTradeInfoBuilder withPeerNodeAddress(String peerNodeAddress) { + this.peerNodeAddress = peerNodeAddress; + return this; + } + + public BsqSwapTradeInfoBuilder withTxId(String txId) { + this.txId = txId; + return this; + } + + public BsqSwapTradeInfoBuilder withBsqTradeAmount(long bsqTradeAmount) { + this.bsqTradeAmount = bsqTradeAmount; + return this; + } + + public BsqSwapTradeInfoBuilder withBsqMaxTradeAmount(long bsqMaxTradeAmount) { + this.bsqMaxTradeAmount = bsqMaxTradeAmount; + return this; + } + + public BsqSwapTradeInfoBuilder withBsqMinTradeAmount(long bsqMinTradeAmount) { + this.bsqMinTradeAmount = bsqMinTradeAmount; + return this; + } + + public BsqSwapTradeInfoBuilder withBtcTradeAmount(long btcTradeAmount) { + this.btcTradeAmount = btcTradeAmount; + return this; + } + + public BsqSwapTradeInfoBuilder withBtcMaxTradeAmount(long btcMaxTradeAmount) { + this.btcMaxTradeAmount = btcMaxTradeAmount; + return this; + } + + public BsqSwapTradeInfoBuilder withBtcMinTradeAmount(long btcMinTradeAmount) { + this.btcMinTradeAmount = btcMinTradeAmount; + return this; + } + + public BsqSwapTradeInfoBuilder withTradePrice(long tradePrice) { + this.tradePrice = tradePrice; + return this; + } + + public BsqSwapTradeInfoBuilder withBsqMakerTradeFee(long bsqMakerTradeFee) { + this.bsqMakerTradeFee = bsqMakerTradeFee; + return this; + } + + public BsqSwapTradeInfoBuilder withBsqTakerTradeFee(long bsqTakerTradeFee) { + this.bsqTakerTradeFee = bsqTakerTradeFee; + return this; + } + + public BsqSwapTradeInfoBuilder withTxFeePerVbyte(long txFeePerVbyte) { + this.txFeePerVbyte = txFeePerVbyte; + return this; + } + + public BsqSwapTradeInfoBuilder withTxFee(long txFee) { + this.txFee = txFee; + return this; + } + + public BsqSwapTradeInfoBuilder withMakerBsqAddress(String makerBsqAddress) { + this.makerBsqAddress = makerBsqAddress; + return this; + } + + public BsqSwapTradeInfoBuilder withMakerBtcAddress(String makerBtcAddress) { + this.makerBtcAddress = makerBtcAddress; + return this; + } + + public BsqSwapTradeInfoBuilder withTakerBsqAddress(String takerBsqAddress) { + this.takerBsqAddress = takerBsqAddress; + return this; + } + + public BsqSwapTradeInfoBuilder withTakerBtcAddress(String takerBtcAddress) { + this.takerBtcAddress = takerBtcAddress; + return this; + } + + public BsqSwapTradeInfoBuilder withTakeOfferDate(long takeOfferDate) { + this.takeOfferDate = takeOfferDate; + return this; + } + + public BsqSwapTradeInfoBuilder withState(String state) { + this.state = state; + return this; + } + + public BsqSwapTradeInfoBuilder withErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + return this; + } + + public BsqSwapTradeInfo build() { + return new BsqSwapTradeInfo(this); + } + } +} diff --git a/core/src/main/java/bisq/core/api/model/OfferInfo.java b/core/src/main/java/bisq/core/api/model/OfferInfo.java index 15ad2acc10..67cee988a3 100644 --- a/core/src/main/java/bisq/core/api/model/OfferInfo.java +++ b/core/src/main/java/bisq/core/api/model/OfferInfo.java @@ -144,8 +144,8 @@ public class OfferInfo implements Payload { .withPaymentAccountId(offer.getMakerPaymentAccountId()) .withPaymentMethodId(offer.getPaymentMethod().getId()) .withPaymentMethodShortName(offer.getPaymentMethod().getShortName()) - .withBaseCurrencyCode(offer.getOfferPayload().getBaseCurrencyCode()) - .withCounterCurrencyCode(offer.getOfferPayload().getCounterCurrencyCode()) + .withBaseCurrencyCode(offer.getBaseCurrencyCode()) + .withCounterCurrencyCode(offer.getCounterCurrencyCode()) .withDate(offer.getDate().getTime()) .withState(offer.getState().name()) .withIsMyOffer(isMyOffer); diff --git a/core/src/main/java/bisq/core/api/model/TradeInfo.java b/core/src/main/java/bisq/core/api/model/TradeInfo.java index 38bf15256d..024eb69fa2 100644 --- a/core/src/main/java/bisq/core/api/model/TradeInfo.java +++ b/core/src/main/java/bisq/core/api/model/TradeInfo.java @@ -17,8 +17,8 @@ package bisq.core.api.model; -import bisq.core.trade.Contract; -import bisq.core.trade.Trade; +import bisq.core.trade.model.bisq_v1.Contract; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.common.Payload; @@ -34,7 +34,7 @@ import static bisq.core.api.model.PaymentAccountPayloadInfo.toPaymentAccountPayl @Getter public class TradeInfo implements Payload { - // The client cannot see bisq.core.trade.Trade or its fromProto method. We use the + // The client cannot see Trade or its fromProto method. We use the // lighter weight TradeInfo proto wrapper instead, containing just enough fields to // view and interact with trades. @@ -127,19 +127,19 @@ public class TradeInfo implements Payload { .withDate(trade.getDate().getTime()) .withRole(role == null ? "" : role) .withIsCurrencyForTakerFeeBtc(trade.isCurrencyForTakerFeeBtc()) - .withTxFeeAsLong(trade.getTxFeeAsLong()) + .withTxFeeAsLong(trade.getTradeTxFeeAsLong()) .withTakerFeeAsLong(trade.getTakerFeeAsLong()) .withTakerFeeAsLong(trade.getTakerFeeAsLong()) .withTakerFeeTxId(trade.getTakerFeeTxId()) .withDepositTxId(trade.getDepositTxId()) .withPayoutTxId(trade.getPayoutTxId()) - .withTradeAmountAsLong(trade.getTradeAmountAsLong()) - .withTradePrice(trade.getTradePrice().getValue()) - .withTradeVolume(trade.getTradeVolume() == null ? 0 : trade.getTradeVolume().getValue()) + .withTradeAmountAsLong(trade.getAmountAsLong()) + .withTradePrice(trade.getPrice().getValue()) + .withTradeVolume(trade.getVolume() == null ? 0 : trade.getVolume().getValue()) .withTradingPeerNodeAddress(Objects.requireNonNull( trade.getTradingPeerNodeAddress()).getHostNameWithoutPostFix()) - .withState(trade.getState().name()) - .withPhase(trade.getPhase().name()) + .withState(trade.getTradeState().name()) + .withPhase(trade.getTradePhase().name()) .withTradePeriodState(trade.getTradePeriodState().name()) .withIsDepositPublished(trade.isDepositPublished()) .withIsDepositConfirmed(trade.isDepositConfirmed()) diff --git a/core/src/main/java/bisq/core/app/BisqExecutable.java b/core/src/main/java/bisq/core/app/BisqExecutable.java index 5efcce3810..9e14df37f9 100644 --- a/core/src/main/java/bisq/core/app/BisqExecutable.java +++ b/core/src/main/java/bisq/core/app/BisqExecutable.java @@ -23,6 +23,7 @@ import bisq.core.btc.wallet.BtcWalletService; import bisq.core.dao.DaoSetup; import bisq.core.dao.node.full.RpcService; import bisq.core.offer.OpenOfferManager; +import bisq.core.offer.bsq_swap.OpenBsqSwapOfferService; import bisq.core.provider.price.PriceFeedService; import bisq.core.setup.CorePersistedDataHost; import bisq.core.setup.CoreSetup; @@ -227,6 +228,7 @@ public abstract class BisqExecutable implements GracefulShutDownHandler, BisqSet } try { + injector.getInstance(OpenBsqSwapOfferService.class).shutDown(); injector.getInstance(PriceFeedService.class).shutDown(); injector.getInstance(ArbitratorManager.class).shutDown(); injector.getInstance(TradeStatisticsManager.class).shutDown(); diff --git a/core/src/main/java/bisq/core/app/BisqSetup.java b/core/src/main/java/bisq/core/app/BisqSetup.java index 541fe069da..02713b726c 100644 --- a/core/src/main/java/bisq/core/app/BisqSetup.java +++ b/core/src/main/java/bisq/core/app/BisqSetup.java @@ -42,7 +42,7 @@ import bisq.core.support.dispute.arbitration.ArbitrationManager; import bisq.core.support.dispute.mediation.MediationManager; import bisq.core.support.dispute.refund.RefundManager; import bisq.core.trade.TradeManager; -import bisq.core.trade.TradeTxException; +import bisq.core.trade.bisq_v1.TradeTxException; import bisq.core.user.Preferences; import bisq.core.user.User; import bisq.core.util.FormattingUtils; @@ -525,6 +525,9 @@ public class BisqSetup { // miner fee was too low and the transaction got removed from mempool and got out from our wallet after a // resync. openOfferManager.getObservableList().forEach(e -> { + if (e.getOffer().isBsqSwapOffer()) { + return; + } String offerFeePaymentTxId = e.getOffer().getOfferFeePaymentTxId(); if (btcWalletService.getConfidenceForTxId(offerFeePaymentTxId) == null) { String message = Res.get("popup.warning.openOfferWithInvalidMakerFeeTx", @@ -657,7 +660,10 @@ public class BisqSetup { } private void maybeShowLocalhostRunningInfo() { - maybeTriggerDisplayHandler("bitcoinLocalhostNode", displayLocalhostHandler, localBitcoinNode.shouldBeUsed()); + if (Config.baseCurrencyNetwork().isMainnet()) { + maybeTriggerDisplayHandler("bitcoinLocalhostNode", displayLocalhostHandler, + localBitcoinNode.shouldBeUsed()); + } } private void maybeShowAccountSigningStateInfo() { diff --git a/core/src/main/java/bisq/core/app/DomainInitialisation.java b/core/src/main/java/bisq/core/app/DomainInitialisation.java index b0dbd16b77..32316b9fac 100644 --- a/core/src/main/java/bisq/core/app/DomainInitialisation.java +++ b/core/src/main/java/bisq/core/app/DomainInitialisation.java @@ -34,7 +34,8 @@ import bisq.core.notifications.alerts.TradeEvents; import bisq.core.notifications.alerts.market.MarketAlerts; import bisq.core.notifications.alerts.price.PriceAlert; import bisq.core.offer.OpenOfferManager; -import bisq.core.offer.TriggerPriceService; +import bisq.core.offer.bisq_v1.TriggerPriceService; +import bisq.core.offer.bsq_swap.OpenBsqSwapOfferService; import bisq.core.payment.AmazonGiftCardAccount; import bisq.core.payment.RevolutAccount; import bisq.core.payment.TradeLimits; @@ -48,9 +49,10 @@ import bisq.core.support.dispute.mediation.mediator.MediatorManager; import bisq.core.support.dispute.refund.RefundManager; import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; import bisq.core.support.traderchat.TraderChatManager; +import bisq.core.trade.ClosedTradableManager; import bisq.core.trade.TradeManager; -import bisq.core.trade.closed.ClosedTradableManager; -import bisq.core.trade.failed.FailedTradesManager; +import bisq.core.trade.bisq_v1.FailedTradesManager; +import bisq.core.trade.bsq_swap.BsqSwapTradeManager; import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.trade.txproof.xmr.XmrTxProofService; import bisq.core.user.User; @@ -84,6 +86,7 @@ public class DomainInitialisation { private final TraderChatManager traderChatManager; private final TradeManager tradeManager; private final ClosedTradableManager closedTradableManager; + private final BsqSwapTradeManager bsqSwapTradeManager; private final FailedTradesManager failedTradesManager; private final XmrTxProofService xmrTxProofService; private final OpenOfferManager openOfferManager; @@ -112,6 +115,7 @@ public class DomainInitialisation { private final DaoStateSnapshotService daoStateSnapshotService; private final TriggerPriceService triggerPriceService; private final MempoolService mempoolService; + private final OpenBsqSwapOfferService openBsqSwapOfferService; @Inject public DomainInitialisation(ClockWatcher clockWatcher, @@ -122,6 +126,7 @@ public class DomainInitialisation { TraderChatManager traderChatManager, TradeManager tradeManager, ClosedTradableManager closedTradableManager, + BsqSwapTradeManager bsqSwapTradeManager, FailedTradesManager failedTradesManager, XmrTxProofService xmrTxProofService, OpenOfferManager openOfferManager, @@ -149,7 +154,8 @@ public class DomainInitialisation { User user, DaoStateSnapshotService daoStateSnapshotService, TriggerPriceService triggerPriceService, - MempoolService mempoolService) { + MempoolService mempoolService, + OpenBsqSwapOfferService openBsqSwapOfferService) { this.clockWatcher = clockWatcher; this.tradeLimits = tradeLimits; this.arbitrationManager = arbitrationManager; @@ -158,6 +164,7 @@ public class DomainInitialisation { this.traderChatManager = traderChatManager; this.tradeManager = tradeManager; this.closedTradableManager = closedTradableManager; + this.bsqSwapTradeManager = bsqSwapTradeManager; this.failedTradesManager = failedTradesManager; this.xmrTxProofService = xmrTxProofService; this.openOfferManager = openOfferManager; @@ -186,6 +193,7 @@ public class DomainInitialisation { this.daoStateSnapshotService = daoStateSnapshotService; this.triggerPriceService = triggerPriceService; this.mempoolService = mempoolService; + this.openBsqSwapOfferService = openBsqSwapOfferService; } public void initDomainServices(Consumer rejectedTxErrorMessageHandler, @@ -210,10 +218,12 @@ public class DomainInitialisation { traderChatManager.onAllServicesInitialized(); closedTradableManager.onAllServicesInitialized(); + bsqSwapTradeManager.onAllServicesInitialized(); failedTradesManager.onAllServicesInitialized(); xmrTxProofService.onAllServicesInitialized(); openOfferManager.onAllServicesInitialized(); + openBsqSwapOfferService.onAllServicesInitialized(); balances.onAllServicesInitialized(); diff --git a/core/src/main/java/bisq/core/app/P2PNetworkSetup.java b/core/src/main/java/bisq/core/app/P2PNetworkSetup.java index 2643a389dc..44ff2ece1c 100644 --- a/core/src/main/java/bisq/core/app/P2PNetworkSetup.java +++ b/core/src/main/java/bisq/core/app/P2PNetworkSetup.java @@ -18,6 +18,8 @@ package bisq.core.app; import bisq.core.btc.setup.WalletsSetup; +import bisq.core.filter.Filter; +import bisq.core.filter.FilterManager; import bisq.core.locale.Res; import bisq.core.provider.price.PriceFeedService; import bisq.core.user.Preferences; @@ -27,6 +29,7 @@ import bisq.network.p2p.P2PServiceListener; import bisq.network.p2p.network.CloseConnectionReason; import bisq.network.p2p.network.Connection; import bisq.network.p2p.network.ConnectionListener; +import bisq.network.p2p.storage.payload.ProofOfWorkPayload; import javax.inject.Inject; import javax.inject.Singleton; @@ -71,25 +74,31 @@ public class P2PNetworkSetup { final BooleanProperty updatedDataReceived = new SimpleBooleanProperty(); @Getter final BooleanProperty p2pNetworkFailed = new SimpleBooleanProperty(); + final FilterManager filterManager; @Inject public P2PNetworkSetup(PriceFeedService priceFeedService, P2PService p2PService, WalletsSetup walletsSetup, - Preferences preferences) { + Preferences preferences, + FilterManager filterManager) { this.priceFeedService = priceFeedService; this.p2PService = p2PService; this.walletsSetup = walletsSetup; this.preferences = preferences; + this.filterManager = filterManager; } - BooleanProperty init(Runnable initWalletServiceHandler, @Nullable Consumer displayTorNetworkSettingsHandler) { + BooleanProperty init(Runnable initWalletServiceHandler, + @Nullable Consumer displayTorNetworkSettingsHandler) { StringProperty bootstrapState = new SimpleStringProperty(); StringProperty bootstrapWarning = new SimpleStringProperty(); BooleanProperty hiddenServicePublished = new SimpleBooleanProperty(); BooleanProperty initialP2PNetworkDataReceived = new SimpleBooleanProperty(); + addP2PMessageFilter(); + p2PNetworkInfoBinding = EasyBind.combine(bootstrapState, bootstrapWarning, p2PService.getNumConnectedPeers(), walletsSetup.numPeersProperty(), hiddenServicePublished, initialP2PNetworkDataReceived, (state, warning, numP2pPeers, numBtcPeers, hiddenService, dataReceived) -> { @@ -225,4 +234,13 @@ public class P2PNetworkSetup { public void setSplashP2PNetworkAnimationVisible(boolean value) { splashP2PNetworkAnimationVisible.set(value); } + + private void addP2PMessageFilter() { + p2PService.getP2PDataStorage().setFilterPredicate(payload -> { + Filter filter = filterManager.getFilter(); + return filter == null || + !filter.isDisablePowMessage() || + !(payload instanceof ProofOfWorkPayload); + }); + } } diff --git a/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java b/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java index ccca2a6a0c..ec9bfd65b2 100644 --- a/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java +++ b/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java @@ -24,6 +24,7 @@ import bisq.core.btc.wallet.BtcWalletService; import bisq.core.dao.DaoSetup; import bisq.core.dao.node.full.RpcService; import bisq.core.offer.OpenOfferManager; +import bisq.core.offer.bsq_swap.OpenBsqSwapOfferService; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import bisq.network.p2p.NodeAddress; @@ -87,6 +88,7 @@ public abstract class ExecutableForAppWithP2p extends BisqExecutable { try { if (injector != null) { JsonFileManager.shutDownAllInstances(); + injector.getInstance(OpenBsqSwapOfferService.class).shutDown(); injector.getInstance(RpcService.class).shutDown(); injector.getInstance(DaoSetup.class).shutDown(); injector.getInstance(ArbitratorManager.class).shutDown(); diff --git a/core/src/main/java/bisq/core/btc/Balances.java b/core/src/main/java/bisq/core/btc/Balances.java index 5d3ddd4e43..3010f8d4b4 100644 --- a/core/src/main/java/bisq/core/btc/Balances.java +++ b/core/src/main/java/bisq/core/btc/Balances.java @@ -24,10 +24,10 @@ import bisq.core.offer.OpenOffer; import bisq.core.offer.OpenOfferManager; import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.refund.RefundManager; -import bisq.core.trade.Trade; +import bisq.core.trade.ClosedTradableManager; import bisq.core.trade.TradeManager; -import bisq.core.trade.closed.ClosedTradableManager; -import bisq.core.trade.failed.FailedTradesManager; +import bisq.core.trade.bisq_v1.FailedTradesManager; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.common.UserThread; diff --git a/core/src/main/java/bisq/core/btc/TxFeeEstimationService.java b/core/src/main/java/bisq/core/btc/TxFeeEstimationService.java index 1cf1d07cd4..049b4a4d5f 100644 --- a/core/src/main/java/bisq/core/btc/TxFeeEstimationService.java +++ b/core/src/main/java/bisq/core/btc/TxFeeEstimationService.java @@ -55,11 +55,11 @@ public class TxFeeEstimationService { // segwit deposit tx with change vsize = 263 // segwit payout tx vsize = 169 // segwit delayed payout tx vsize = 139 - public static int TYPICAL_TX_WITH_1_INPUT_VSIZE = 175; - private static int DEPOSIT_TX_VSIZE = 233; +public static final int TYPICAL_TX_WITH_1_INPUT_VSIZE = 175; + private static final int DEPOSIT_TX_VSIZE = 233; - private static int BSQ_INPUT_INCREASE = 70; - private static int MAX_ITERATIONS = 10; + private static final int BSQ_INPUT_INCREASE = 70; + private static final int MAX_ITERATIONS = 10; private final FeeService feeService; private final BtcWalletService btcWalletService; diff --git a/core/src/main/java/bisq/core/btc/listeners/BsqBalanceListener.java b/core/src/main/java/bisq/core/btc/listeners/BsqBalanceListener.java index 7923db370c..fa6c5a1af6 100644 --- a/core/src/main/java/bisq/core/btc/listeners/BsqBalanceListener.java +++ b/core/src/main/java/bisq/core/btc/listeners/BsqBalanceListener.java @@ -20,7 +20,7 @@ package bisq.core.btc.listeners; import org.bitcoinj.core.Coin; public interface BsqBalanceListener { - void onUpdateBalances(Coin availableConfirmedBalance, + void onUpdateBalances(Coin availableBalance, Coin availableNonBsqBalance, Coin unverifiedBalance, Coin unconfirmedChangeBalance, diff --git a/core/src/main/java/bisq/core/btc/listeners/TxConfidenceListener.java b/core/src/main/java/bisq/core/btc/listeners/TxConfidenceListener.java index 6677555e81..6eaa1b4b5d 100644 --- a/core/src/main/java/bisq/core/btc/listeners/TxConfidenceListener.java +++ b/core/src/main/java/bisq/core/btc/listeners/TxConfidenceListener.java @@ -19,18 +19,15 @@ package bisq.core.btc.listeners; import org.bitcoinj.core.TransactionConfidence; -public class TxConfidenceListener { - private final String txID; +import lombok.Getter; - public TxConfidenceListener(String txID) { - this.txID = txID; +public abstract class TxConfidenceListener { + @Getter + private final String txId; + + public TxConfidenceListener(String txId) { + this.txId = txId; } - public String getTxID() { - return txID; - } - - @SuppressWarnings("UnusedParameters") - public void onTransactionConfidenceChanged(TransactionConfidence confidence) { - } + abstract public void onTransactionConfidenceChanged(TransactionConfidence confidence); } diff --git a/core/src/main/java/bisq/core/btc/model/RawTransactionInput.java b/core/src/main/java/bisq/core/btc/model/RawTransactionInput.java index 3a59ab07f9..0b2d74de36 100644 --- a/core/src/main/java/bisq/core/btc/model/RawTransactionInput.java +++ b/core/src/main/java/bisq/core/btc/model/RawTransactionInput.java @@ -17,33 +17,82 @@ package bisq.core.btc.model; +import bisq.core.btc.wallet.BtcWalletService; + import bisq.common.proto.network.NetworkPayload; import bisq.common.proto.persistable.PersistablePayload; import bisq.common.util.Utilities; import com.google.protobuf.ByteString; +import org.bitcoinj.core.Transaction; +import org.bitcoinj.core.TransactionInput; +import org.bitcoinj.script.Script; + +import java.util.Objects; + import lombok.EqualsAndHashCode; +import lombok.Getter; import javax.annotation.concurrent.Immutable; @EqualsAndHashCode @Immutable +@Getter public final class RawTransactionInput implements NetworkPayload, PersistablePayload { public final long index; // Index of spending txo public final byte[] parentTransaction; // Spending tx (fromTx) public final long value; + // Added at Bsq swap release + // id of the org.bitcoinj.script.Script.ScriptType. Useful to know if input is segwit. + // Lowest Script.ScriptType.id value is 1, so we use 0 as value for not defined + public final int scriptTypeId; + + public RawTransactionInput(TransactionInput input) { + this(input.getOutpoint().getIndex(), + Objects.requireNonNull(Objects.requireNonNull(input.getConnectedOutput()).getParentTransaction()), + Objects.requireNonNull(input.getValue()).value, + input.getConnectedOutput() != null && + input.getConnectedOutput().getScriptPubKey() != null && + input.getConnectedOutput().getScriptPubKey().getScriptType() != null ? + input.getConnectedOutput().getScriptPubKey().getScriptType().id : -1); + } + + // Does not set the scriptTypeId. Use RawTransactionInput(TransactionInput input) for any new code. + @Deprecated + public RawTransactionInput(long index, byte[] parentTransaction, long value) { + this(index, parentTransaction, value, 0); + } + + private RawTransactionInput(long index, Transaction parentTransaction, long value, int scriptTypeId) { + this(index, + parentTransaction.bitcoinSerialize(scriptTypeId == Script.ScriptType.P2WPKH.id || + scriptTypeId == Script.ScriptType.P2WSH.id), + value, scriptTypeId); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + /** * Holds the relevant data for the connected output for a tx input. * @param index the index of the parentTransaction * @param parentTransaction the spending output tx, not the parent tx of the input * @param value the number of satoshis being spent + * @param scriptTypeId The id of the org.bitcoinj.script.Script.ScriptType of the spending output + * If not set it is 0. */ - public RawTransactionInput(long index, byte[] parentTransaction, long value) { + private RawTransactionInput(long index, + byte[] parentTransaction, + long value, + int scriptTypeId) { this.index = index; this.parentTransaction = parentTransaction; this.value = value; + this.scriptTypeId = scriptTypeId; } @Override @@ -52,11 +101,36 @@ public final class RawTransactionInput implements NetworkPayload, PersistablePay .setIndex(index) .setParentTransaction(ByteString.copyFrom(parentTransaction)) .setValue(value) + .setScriptTypeId(scriptTypeId) .build(); } public static RawTransactionInput fromProto(protobuf.RawTransactionInput proto) { - return new RawTransactionInput(proto.getIndex(), proto.getParentTransaction().toByteArray(), proto.getValue()); + return new RawTransactionInput(proto.getIndex(), + proto.getParentTransaction().toByteArray(), + proto.getValue(), + proto.getScriptTypeId()); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + public boolean isSegwit() { + return isP2WPKH() || isP2WSH(); + } + + public boolean isP2WPKH() { + return scriptTypeId == Script.ScriptType.P2WPKH.id; + } + + public boolean isP2WSH() { + return scriptTypeId == Script.ScriptType.P2WSH.id; + } + + public String getParentTxId(BtcWalletService btcWalletService) { + return btcWalletService.getTxFromSerializedTx(parentTransaction).getTxId().toString(); } @Override @@ -65,6 +139,7 @@ public final class RawTransactionInput implements NetworkPayload, PersistablePay "index=" + index + ", parentTransaction as HEX " + Utilities.bytesAsHexString(parentTransaction) + ", value=" + value + + ", scriptTypeId=" + scriptTypeId + '}'; } } diff --git a/core/src/main/java/bisq/core/btc/nodes/BtcNodes.java b/core/src/main/java/bisq/core/btc/nodes/BtcNodes.java index 8285188632..1f4d2652a7 100644 --- a/core/src/main/java/bisq/core/btc/nodes/BtcNodes.java +++ b/core/src/main/java/bisq/core/btc/nodes/BtcNodes.java @@ -64,9 +64,6 @@ public class BtcNodes { new BtcNode("btc1.sqrrm.net", "jygcc54etaubgdpcvzgbihjaqbc37cstpvum5sjzvka4bibkp4wrgnqd.onion", "185.25.48.184", BtcNode.DEFAULT_PORT, "@sqrrm"), new BtcNode("btc2.sqrrm.net", "h32haomoe52ljz6qopedsocvotvoj5lm2zmecfhdhawb3flbsf64l2qd.onion", "81.171.22.143", BtcNode.DEFAULT_PORT, "@sqrrm"), - // KanoczTomas -// new BtcNode("btc.ispol.sk", "mbm6ffx6j5ygi2ck.onion", "193.58.196.212", BtcNode.DEFAULT_PORT, "@KanoczTomas"), - // Devin Bileck new BtcNode("btc1.bisq.services", "devinbtctu7uctl7hly2juu3thbgeivfnvw3ckj3phy6nyvpnx66yeyd.onion", "172.105.21.216", BtcNode.DEFAULT_PORT, "@devinbileck"), new BtcNode("btc2.bisq.services", "devinbtcyk643iruzfpaxw3on2jket7rbjmwygm42dmdyub3ietrbmid.onion", "173.255.240.205", BtcNode.DEFAULT_PORT, "@devinbileck"), @@ -77,9 +74,6 @@ public class BtcNodes { new BtcNode("node140.hnl.wiz.biz", "jto2jfbsxhb6yvhcrrjddrgbakte6tgsy3c3z3prss64gndgvovvosyd.onion", "103.99.168.140", BtcNode.DEFAULT_PORT, "@wiz"), new BtcNode("node210.fmt.wiz.biz", "rfqmn3qe36uaptkxhdvi74p4hyrzhir6vhmzb2hqryxodig4gue2zbyd.onion", "103.99.170.210", BtcNode.DEFAULT_PORT, "@wiz"), new BtcNode("node220.fmt.wiz.biz", "azbpsh4arqlm6442wfimy7qr65bmha2zhgjg7wbaji6vvaug53hur2qd.onion", "103.99.170.220", BtcNode.DEFAULT_PORT, "@wiz") - - // Rob Kaandorp -// new BtcNode(null, "2pj2o2mrawj7yotg.onion", null, BtcNode.DEFAULT_PORT, "@robkaandorp") // cannot provide IP because no static IP ) : new ArrayList<>(); } diff --git a/core/src/main/java/bisq/core/btc/wallet/BsqCoinSelector.java b/core/src/main/java/bisq/core/btc/wallet/BsqCoinSelector.java index e6d122a6b8..2d893a54bd 100644 --- a/core/src/main/java/bisq/core/btc/wallet/BsqCoinSelector.java +++ b/core/src/main/java/bisq/core/btc/wallet/BsqCoinSelector.java @@ -26,6 +26,8 @@ import org.bitcoinj.core.TransactionOutput; import javax.inject.Inject; +import lombok.Getter; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; /** @@ -36,9 +38,13 @@ import lombok.extern.slf4j.Slf4j; public class BsqCoinSelector extends BisqDefaultCoinSelector { private final DaoStateService daoStateService; private final UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService; + @Setter + @Getter + private boolean allowSpendMyOwnUnconfirmedTxOutputs = true; @Inject - public BsqCoinSelector(DaoStateService daoStateService, UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService) { + public BsqCoinSelector(DaoStateService daoStateService, + UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService) { // permitForeignPendingTx is not relevant here as we do not support pending foreign utxos anyway. super(false); this.daoStateService = daoStateService; @@ -53,17 +59,19 @@ public class BsqCoinSelector extends BisqDefaultCoinSelector { return false; // If it is a normal confirmed BSQ output we use the default lookup at the daoState - if (daoStateService.isTxOutputSpendable(new TxOutputKey(parentTransaction.getTxId().toString(), output.getIndex()))) + TxOutputKey txOutputKey = new TxOutputKey(parentTransaction.getTxId().toString(), output.getIndex()); + if (daoStateService.isTxOutputSpendable(txOutputKey)) return true; // It might be that it is an unconfirmed change output which we allow to be used for spending without requiring a confirmation. // We check if we have the output in the dao state, if so we have a confirmed but unspendable output (e.g. confiscated). - if (daoStateService.getTxOutput(new TxOutputKey(parentTransaction.getTxId().toString(), output.getIndex())).isPresent()) + if (daoStateService.getTxOutput(txOutputKey).isPresent()) return false; + // If we have set the isUnconfirmedSpendable flag to true (default) we check for unconfirmed own change outputs. // Only if it's not existing yet in the dao state (unconfirmed) we use our unconfirmedBsqChangeOutputList to // check if it is an own change output. - return unconfirmedBsqChangeOutputListService.hasTransactionOutput(output); + return allowSpendMyOwnUnconfirmedTxOutputs && unconfirmedBsqChangeOutputListService.hasTransactionOutput(output); } // For BSQ we do not check for dust attack utxos as they are 5.46 BSQ and a considerable value. diff --git a/core/src/main/java/bisq/core/btc/wallet/BsqTransferService.java b/core/src/main/java/bisq/core/btc/wallet/BsqTransferService.java index 5639004c70..29b9282589 100644 --- a/core/src/main/java/bisq/core/btc/wallet/BsqTransferService.java +++ b/core/src/main/java/bisq/core/btc/wallet/BsqTransferService.java @@ -42,7 +42,7 @@ public class BsqTransferService { Transaction preparedSendTx = bsqWalletService.getPreparedSendBsqTx(address.toString(), receiverAmount); Transaction txWithBtcFee = btcWalletService.completePreparedSendBsqTx(preparedSendTx, txFeePerVbyte); - Transaction signedTx = bsqWalletService.signTx(txWithBtcFee); + Transaction signedTx = bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee); return new BsqTransferModel(address, receiverAmount, diff --git a/core/src/main/java/bisq/core/btc/wallet/BsqWalletService.java b/core/src/main/java/bisq/core/btc/wallet/BsqWalletService.java index 708c504467..ad28df8be5 100644 --- a/core/src/main/java/bisq/core/btc/wallet/BsqWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/BsqWalletService.java @@ -22,6 +22,7 @@ import bisq.core.btc.exceptions.InsufficientBsqException; import bisq.core.btc.exceptions.TransactionVerificationException; import bisq.core.btc.exceptions.WalletException; import bisq.core.btc.listeners.BsqBalanceListener; +import bisq.core.btc.model.RawTransactionInput; import bisq.core.btc.setup.WalletsSetup; import bisq.core.dao.DaoKillSwitch; import bisq.core.dao.state.DaoStateListener; @@ -34,8 +35,10 @@ import bisq.core.dao.state.model.blockchain.TxType; import bisq.core.dao.state.unconfirmed.UnconfirmedBsqChangeOutputListService; import bisq.core.provider.fee.FeeService; import bisq.core.user.Preferences; +import bisq.core.util.coin.BsqFormatter; import bisq.common.UserThread; +import bisq.common.util.Tuple2; import org.bitcoinj.core.Address; import org.bitcoinj.core.AddressFormatException; @@ -95,15 +98,20 @@ public class BsqWalletService extends WalletService implements DaoStateListener private final CopyOnWriteArraySet bsqBalanceListeners = new CopyOnWriteArraySet<>(); private final List walletTransactionsChangeListeners = new ArrayList<>(); private boolean updateBsqWalletTransactionsPending; + @Getter + private final BsqFormatter bsqFormatter; + // balance of non BSQ satoshis @Getter private Coin availableNonBsqBalance = Coin.ZERO; @Getter - private Coin availableConfirmedBalance = Coin.ZERO; + private Coin availableBalance = Coin.ZERO; @Getter private Coin unverifiedBalance = Coin.ZERO; @Getter + private Coin verifiedBalance = Coin.ZERO; + @Getter private Coin unconfirmedChangeBalance = Coin.ZERO; @Getter private Coin lockedForVotingBalance = Coin.ZERO; @@ -125,7 +133,8 @@ public class BsqWalletService extends WalletService implements DaoStateListener UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService, Preferences preferences, FeeService feeService, - DaoKillSwitch daoKillSwitch) { + DaoKillSwitch daoKillSwitch, + BsqFormatter bsqFormatter) { super(walletsSetup, preferences, feeService); @@ -135,6 +144,7 @@ public class BsqWalletService extends WalletService implements DaoStateListener this.daoStateService = daoStateService; this.unconfirmedBsqChangeOutputListService = unconfirmedBsqChangeOutputListService; this.daoKillSwitch = daoKillSwitch; + this.bsqFormatter = bsqFormatter; nonBsqCoinSelector.setPreferences(preferences); @@ -284,18 +294,20 @@ public class BsqWalletService extends WalletService implements DaoStateListener .mapToLong(TxOutput::getValue) .sum()); - availableConfirmedBalance = bsqCoinSelector.select(NetworkParameters.MAX_MONEY, + availableBalance = bsqCoinSelector.select(NetworkParameters.MAX_MONEY, wallet.calculateAllSpendCandidates()).valueGathered; - if (availableConfirmedBalance.isNegative()) - availableConfirmedBalance = Coin.ZERO; + if (availableBalance.isNegative()) + availableBalance = Coin.ZERO; unconfirmedChangeBalance = unconfirmedBsqChangeOutputListService.getBalance(); availableNonBsqBalance = nonBsqCoinSelector.select(NetworkParameters.MAX_MONEY, wallet.calculateAllSpendCandidates()).valueGathered; - bsqBalanceListeners.forEach(e -> e.onUpdateBalances(availableConfirmedBalance, availableNonBsqBalance, unverifiedBalance, + verifiedBalance = availableBalance.subtract(unconfirmedChangeBalance); + + bsqBalanceListeners.forEach(e -> e.onUpdateBalances(availableBalance, availableNonBsqBalance, unverifiedBalance, unconfirmedChangeBalance, lockedForVotingBalance, lockupBondsBalance, unlockingBondsBalance)); log.info("updateBsqBalance took {} ms", System.currentTimeMillis() - ts); } @@ -481,28 +493,10 @@ public class BsqWalletService extends WalletService implements DaoStateListener // Sign tx /////////////////////////////////////////////////////////////////////////////////////////// - public Transaction signTx(Transaction tx) throws WalletException, TransactionVerificationException { - for (int i = 0; i < tx.getInputs().size(); i++) { - TransactionInput txIn = tx.getInputs().get(i); - TransactionOutput connectedOutput = txIn.getConnectedOutput(); - if (connectedOutput != null && connectedOutput.isMine(wallet)) { - signTransactionInput(wallet, aesKey, tx, txIn, i); - checkScriptSig(tx, txIn, i); - } - } - - for (TransactionOutput txo : tx.getOutputs()) { - Coin value = txo.getValue(); - // OpReturn outputs have value 0 - if (value.isPositive()) { - checkArgument(Restrictions.isAboveDust(txo.getValue()), - "An output value is below dust limit. Transaction=" + tx); - } - } - - checkWalletConsistency(wallet); - verifyTransaction(tx); - printTx("BSQ wallet: Signed Tx", tx); + public Transaction signTxAndVerifyNoDustOutputs(Transaction tx) + throws WalletException, TransactionVerificationException { + WalletService.signTx(wallet, aesKey, tx); + WalletService.verifyNonDustTxo(tx); return tx; } @@ -540,6 +534,7 @@ public class BsqWalletService extends WalletService implements DaoStateListener return getPreparedSendTx(receiverAddress, receiverAmount, bsqCoinSelector); } + /////////////////////////////////////////////////////////////////////////////////////////// // Send BTC (non-BSQ) with BTC fee (e.g. the issuance output from a lost comp. request) /////////////////////////////////////////////////////////////////////////////////////////// @@ -732,6 +727,46 @@ public class BsqWalletService extends WalletService implements DaoStateListener } + /////////////////////////////////////////////////////////////////////////////////////////// + // BsqSwap tx + /////////////////////////////////////////////////////////////////////////////////////////// + + public Tuple2, Coin> getBuyersBsqInputsForBsqSwapTx(Coin required) + throws InsufficientBsqException { + daoKillSwitch.assertDaoIsNotDisabled(); + // As unconfirmed BSQ inputs cannot be verified by the peer we can only use confirmed BSQ. + boolean prev = bsqCoinSelector.isAllowSpendMyOwnUnconfirmedTxOutputs(); + bsqCoinSelector.setAllowSpendMyOwnUnconfirmedTxOutputs(false); + CoinSelection coinSelection = bsqCoinSelector.select(required, wallet.calculateAllSpendCandidates()); + Coin change; + try { + change = bsqCoinSelector.getChange(required, coinSelection); + } catch (InsufficientMoneyException e) { + throw new InsufficientBsqException(e.missing); + } finally { + bsqCoinSelector.setAllowSpendMyOwnUnconfirmedTxOutputs(prev); + } + + Transaction dummyTx = new Transaction(params); + coinSelection.gathered.forEach(dummyTx::addInput); + List inputs = dummyTx.getInputs().stream() + .map(RawTransactionInput::new) + .collect(Collectors.toList()); + return new Tuple2<>(inputs, change); + } + + public void signBsqSwapTransaction(Transaction transaction, List myInputs) + throws TransactionVerificationException { + for (TransactionInput input : myInputs) { + TransactionOutput connectedOutput = input.getConnectedOutput(); + checkNotNull(connectedOutput, "connectedOutput must not be null"); + checkArgument(connectedOutput.isMine(wallet), "connectedOutput is not mine"); + signTransactionInput(wallet, aesKey, transaction, input, input.getIndex()); + checkScriptSig(transaction, input, input.getIndex()); + } + } + + /////////////////////////////////////////////////////////////////////////////////////////// // Blind vote tx /////////////////////////////////////////////////////////////////////////////////////////// @@ -781,6 +816,7 @@ public class BsqWalletService extends WalletService implements DaoStateListener return tx; } + /////////////////////////////////////////////////////////////////////////////////////////// // Unlock bond tx /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java b/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java index bef6378b79..2437b2b8f3 100644 --- a/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java @@ -23,6 +23,7 @@ import bisq.core.btc.exceptions.TransactionVerificationException; import bisq.core.btc.exceptions.WalletException; import bisq.core.btc.model.AddressEntry; import bisq.core.btc.model.AddressEntryList; +import bisq.core.btc.model.RawTransactionInput; import bisq.core.btc.setup.WalletsSetup; import bisq.core.btc.wallet.http.MemPoolSpaceTxBroadcaster; import bisq.core.provider.fee.FeeService; @@ -47,6 +48,7 @@ import org.bitcoinj.crypto.KeyCrypterScrypt; import org.bitcoinj.script.Script; import org.bitcoinj.script.ScriptBuilder; import org.bitcoinj.script.ScriptPattern; +import org.bitcoinj.wallet.CoinSelection; import org.bitcoinj.wallet.SendRequest; import org.bitcoinj.wallet.Wallet; @@ -61,6 +63,7 @@ import org.bouncycastle.crypto.params.KeyParameter; import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -1330,4 +1333,30 @@ public class BtcWalletService extends WalletService { return resultTx; } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Find inputs and change + /////////////////////////////////////////////////////////////////////////////////////////// + + public Tuple2, Coin> getInputsAndChange(Coin required) throws InsufficientMoneyException { + BtcCoinSelector coinSelector = new BtcCoinSelector(walletsSetup.getAddressesByContext(AddressEntry.Context.AVAILABLE), + preferences.getIgnoreDustThreshold()); + CoinSelection coinSelection = coinSelector.select(required, Objects.requireNonNull(wallet).calculateAllSpendCandidates()); + + Coin change; + try { + change = coinSelector.getChange(required, coinSelection); + } catch (InsufficientMoneyException e) { + log.error("Missing funds in getSellersBtcInputsForBsqSwapTx. missing={}", e.missing); + throw new InsufficientMoneyException(e.missing); + } + + Transaction dummyTx = new Transaction(params); + coinSelection.gathered.forEach(dummyTx::addInput); + List inputs = dummyTx.getInputs().stream() + .map(RawTransactionInput::new) + .collect(Collectors.toList()); + return new Tuple2<>(inputs, change); + } } diff --git a/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java b/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java index 94d6e75cf3..2df405287d 100644 --- a/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java @@ -63,13 +63,12 @@ import org.bouncycastle.crypto.params.KeyParameter; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.jetbrains.annotations.NotNull; - import javax.annotation.Nullable; import static com.google.common.base.Preconditions.checkArgument; @@ -891,8 +890,7 @@ public class TradeWalletService { input.setScriptSig(inputScript); } else { input.setScriptSig(ScriptBuilder.createEmpty()); - TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig); - input.setWitness(witness); + input.setWitness(TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig)); } WalletService.printTx("payoutTx", payoutTx); WalletService.verifyTransaction(payoutTx); @@ -971,8 +969,7 @@ public class TradeWalletService { input.setScriptSig(inputScript); } else { input.setScriptSig(ScriptBuilder.createEmpty()); - TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig); - input.setWitness(witness); + input.setWitness(TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig)); } WalletService.printTx("mediated payoutTx", payoutTx); WalletService.verifyTransaction(payoutTx); @@ -1059,8 +1056,7 @@ public class TradeWalletService { input.setScriptSig(inputScript); } else { input.setScriptSig(ScriptBuilder.createEmpty()); - TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript, arbitratorTxSig, tradersTxSig); - input.setWitness(witness); + input.setWitness(TransactionWitness.redeemP2WSH(redeemScript, arbitratorTxSig, tradersTxSig)); } WalletService.printTx("disputed payoutTx", payoutTx); WalletService.verifyTransaction(payoutTx); @@ -1077,14 +1073,14 @@ public class TradeWalletService { /////////////////////////////////////////////////////////////////////////////////////////// public Tuple2 emergencyBuildPayoutTxFrom2of2MultiSig(String depositTxHex, - Coin buyerPayoutAmount, - Coin sellerPayoutAmount, - Coin txFee, - String buyerAddressString, - String sellerAddressString, - String buyerPubKeyAsHex, - String sellerPubKeyAsHex, - boolean hashedMultiSigOutputIsLegacy) { + Coin buyerPayoutAmount, + Coin sellerPayoutAmount, + Coin txFee, + String buyerAddressString, + String sellerAddressString, + String buyerPubKeyAsHex, + String sellerPubKeyAsHex, + boolean hashedMultiSigOutputIsLegacy) { byte[] buyerPubKey = ECKey.fromPublicOnly(Utils.HEX.decode(buyerPubKeyAsHex)).getPubKey(); byte[] sellerPubKey = ECKey.fromPublicOnly(Utils.HEX.decode(sellerPubKeyAsHex)).getPubKey(); Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey); @@ -1105,7 +1101,10 @@ public class TradeWalletService { return new Tuple2<>(redeemScriptHex, unsignedTxHex); } - public String emergencyGenerateSignature(String rawTxHex, String redeemScriptHex, Coin inputValue, String myPrivKeyAsHex) + public String emergencyGenerateSignature(String rawTxHex, + String redeemScriptHex, + Coin inputValue, + String myPrivKeyAsHex) throws IllegalArgumentException { boolean hashedMultiSigOutputIsLegacy = true; if (rawTxHex.startsWith("010000000001")) @@ -1129,10 +1128,10 @@ public class TradeWalletService { } public Tuple2 emergencyApplySignatureToPayoutTxFrom2of2MultiSig(String unsignedTxHex, - String redeemScriptHex, - String buyerSignatureAsHex, - String sellerSignatureAsHex, - boolean hashedMultiSigOutputIsLegacy) + String redeemScriptHex, + String buyerSignatureAsHex, + String sellerSignatureAsHex, + boolean hashedMultiSigOutputIsLegacy) throws AddressFormatException, SignatureDecodeException { Transaction payoutTx = new Transaction(params, Utils.HEX.decode(unsignedTxHex)); TransactionSignature buyerTxSig = TransactionSignature.decodeFromBitcoin(Utils.HEX.decode(buyerSignatureAsHex), true, true); @@ -1146,8 +1145,7 @@ public class TradeWalletService { input.setScriptSig(inputScript); } else { input.setScriptSig(ScriptBuilder.createEmpty()); - TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig); - input.setWitness(witness); + input.setWitness(TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig)); } String txId = payoutTx.getTxId().toString(); String signedTxHex = Utils.HEX.encode(payoutTx.bitcoinSerialize(!hashedMultiSigOutputIsLegacy)); @@ -1163,6 +1161,96 @@ public class TradeWalletService { broadcastTx(payoutTx, callback, 20); } + /////////////////////////////////////////////////////////////////////////////////////////// + // BsqSwap tx + /////////////////////////////////////////////////////////////////////////////////////////// + + public Transaction sellerBuildBsqSwapTx(List buyersBsqInputs, + List sellersBtcInputs, + Coin sellersBsqPayoutAmount, + String sellersBsqPayoutAddress, + @Nullable Coin buyersBsqChangeAmount, + @Nullable String buyersBsqChangeAddress, + Coin buyersBtcPayoutAmount, + String buyersBtcPayoutAddress, + @Nullable Coin sellersBtcChangeAmount, + @Nullable String sellersBtcChangeAddress) throws AddressFormatException { + + Transaction transaction = new Transaction(params); + List sellersBtcTransactionInput = sellersBtcInputs.stream() + .map(rawInput -> getTransactionInput(transaction, new byte[]{}, rawInput)) + .collect(Collectors.toList()); + return buildBsqSwapTx(buyersBsqInputs, + sellersBtcTransactionInput, + sellersBsqPayoutAmount, + sellersBsqPayoutAddress, + buyersBsqChangeAmount, + buyersBsqChangeAddress, + buyersBtcPayoutAmount, + buyersBtcPayoutAddress, + sellersBtcChangeAmount, + sellersBtcChangeAddress, + transaction); + } + + public Transaction buyerBuildBsqSwapTx(List buyersBsqInputs, + List sellersBtcInputs, + Coin sellersBsqPayoutAmount, + String sellersBsqPayoutAddress, + @Nullable Coin buyersBsqChangeAmount, + @Nullable String buyersBsqChangeAddress, + Coin buyersBtcPayoutAmount, + String buyersBtcPayoutAddress, + @Nullable Coin sellersBtcChangeAmount, + @Nullable String sellersBtcChangeAddress) throws AddressFormatException { + Transaction transaction = new Transaction(params); + return buildBsqSwapTx(buyersBsqInputs, + sellersBtcInputs, + sellersBsqPayoutAmount, + sellersBsqPayoutAddress, + buyersBsqChangeAmount, + buyersBsqChangeAddress, + buyersBtcPayoutAmount, + buyersBtcPayoutAddress, + sellersBtcChangeAmount, + sellersBtcChangeAddress, + transaction); + } + + private Transaction buildBsqSwapTx(List buyersBsqInputs, + List sellersBtcInputs, + Coin sellersBsqPayoutAmount, + String sellersBsqPayoutAddress, + @Nullable Coin buyersBsqChangeAmount, + @Nullable String buyersBsqChangeAddress, + Coin buyersBtcPayoutAmount, + String buyersBtcPayoutAddress, + @Nullable Coin sellersBtcChangeAmount, + @Nullable String sellersBtcChangeAddress, + Transaction transaction) throws AddressFormatException { + + buyersBsqInputs.forEach(rawInput -> transaction.addInput(getTransactionInput(transaction, new byte[]{}, rawInput))); + sellersBtcInputs.forEach(transaction::addInput); + + transaction.addOutput(sellersBsqPayoutAmount, Address.fromString(params, sellersBsqPayoutAddress)); + + if (buyersBsqChangeAmount != null && buyersBsqChangeAmount.isPositive()) + transaction.addOutput(buyersBsqChangeAmount, Address.fromString(params, Objects.requireNonNull(buyersBsqChangeAddress))); + + transaction.addOutput(buyersBtcPayoutAmount, Address.fromString(params, buyersBtcPayoutAddress)); + + if (sellersBtcChangeAmount != null && sellersBtcChangeAmount.isPositive()) + transaction.addOutput(sellersBtcChangeAmount, Address.fromString(params, Objects.requireNonNull(sellersBtcChangeAddress))); + + return transaction; + } + + public void signBsqSwapTransaction(Transaction transaction, List myInputs) + throws SigningException { + for (TransactionInput input : myInputs) { + signInput(transaction, input, input.getIndex()); + } + } /////////////////////////////////////////////////////////////////////////////////////////// // Broadcast tx @@ -1207,7 +1295,12 @@ public class TradeWalletService { // Private methods /////////////////////////////////////////////////////////////////////////////////////////// - private RawTransactionInput getRawInputFromTransactionInput(@NotNull TransactionInput input) { + // This method might be replace by RawTransactionInput constructor taking the TransactionInput as param. + // As we used segwit=false for the bitcoinSerialize method here we still keep it to not risk to break anything, + // though it very likely should be fine to replace it with the RawTransactionInput constructor call. + @Deprecated + private RawTransactionInput getRawInputFromTransactionInput(TransactionInput input) { + checkNotNull(input, "input must not be null"); checkNotNull(input.getConnectedOutput(), "input.getConnectedOutput() must not be null"); checkNotNull(input.getConnectedOutput().getParentTransaction(), "input.getConnectedOutput().getParentTransaction() must not be null"); @@ -1222,10 +1315,13 @@ public class TradeWalletService { input.getValue().value); } - private TransactionInput getTransactionInput(Transaction depositTx, + private TransactionInput getTransactionInput(Transaction parentTransaction, byte[] scriptProgram, RawTransactionInput rawTransactionInput) { - return new TransactionInput(params, depositTx, scriptProgram, getConnectedOutPoint(rawTransactionInput), + return new TransactionInput(params, + parentTransaction, + scriptProgram, + getConnectedOutPoint(rawTransactionInput), Coin.valueOf(rawTransactionInput.value)); } @@ -1239,7 +1335,6 @@ public class TradeWalletService { checkNotNull(getConnectedOutPoint(rawTransactionInput).getConnectedOutput()).getScriptPubKey()); } - // TODO: Once we have removed legacy arbitrator from dispute domain we can remove that method as well. // Atm it is still used by traderSignAndFinalizeDisputedPayoutTx which is used by ArbitrationManager. @@ -1299,7 +1394,6 @@ public class TradeWalletService { private void signInput(Transaction transaction, TransactionInput input, int inputIndex) throws SigningException { checkNotNull(input.getConnectedOutput(), "input.getConnectedOutput() must not be null"); Script scriptPubKey = input.getConnectedOutput().getScriptPubKey(); - checkNotNull(wallet); ECKey sigKey = input.getOutpoint().getConnectedKey(wallet); checkNotNull(sigKey, "signInput: sigKey must not be null. input.getOutpoint()=" + input.getOutpoint().toString()); diff --git a/core/src/main/java/bisq/core/btc/wallet/WalletService.java b/core/src/main/java/bisq/core/btc/wallet/WalletService.java index 0b893e470a..c2badb3518 100644 --- a/core/src/main/java/bisq/core/btc/wallet/WalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/WalletService.java @@ -102,6 +102,7 @@ import org.jetbrains.annotations.NotNull; import javax.annotation.Nullable; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; @@ -278,6 +279,31 @@ public abstract class WalletService { // Sign tx /////////////////////////////////////////////////////////////////////////////////////////// + public static void signTx(Wallet wallet, + KeyParameter aesKey, + Transaction tx) + throws WalletException, TransactionVerificationException { + for (int i = 0; i < tx.getInputs().size(); i++) { + TransactionInput input = tx.getInput(i); + TransactionOutput connectedOutput = input.getConnectedOutput(); + if (connectedOutput == null) { + log.error("connectedOutput is null"); + continue; + } + if (!connectedOutput.isMine(wallet)) { + log.error("connectedOutput is not mine"); + continue; + } + + signTransactionInput(wallet, aesKey, tx, input, i); + checkScriptSig(tx, input, i); + } + + checkWalletConsistency(wallet); + verifyTransaction(tx); + printTx("Signed Tx", tx); + } + public static void signTransactionInput(Wallet wallet, KeyParameter aesKey, Transaction tx, @@ -357,12 +383,13 @@ public abstract class WalletService { txIn.setScriptSig(ScriptBuilder.createEmpty()); txIn.setWitness(TransactionWitness.redeemP2WPKH(txSig, key)); } catch (ECKey.KeyIsEncryptedException e1) { + log.error(e1.toString()); throw e1; } catch (ECKey.MissingPrivateKeyException e1) { log.warn("No private key in keypair for input {}", index); } } else { - // log.error("Unexpected script type."); + log.error("Unexpected script type."); throw new RuntimeException("Unexpected script type."); } } else { @@ -374,6 +401,23 @@ public abstract class WalletService { } + /////////////////////////////////////////////////////////////////////////////////////////// + // Dust + /////////////////////////////////////////////////////////////////////////////////////////// + + public static void verifyNonDustTxo(Transaction tx) { + for (TransactionOutput txo : tx.getOutputs()) { + Coin value = txo.getValue(); + // OpReturn outputs have value 0 + if (value.isPositive()) { + checkArgument(Restrictions.isAboveDust(txo.getValue()), + "An output value is below dust limit. Transaction=" + tx); + } + } + + } + + /////////////////////////////////////////////////////////////////////////////////////////// // Broadcast tx /////////////////////////////////////////////////////////////////////////////////////////// @@ -482,7 +526,7 @@ public abstract class WalletService { // Balance /////////////////////////////////////////////////////////////////////////////////////////// - public Coin getAvailableConfirmedBalance() { + public Coin getAvailableBalance() { return wallet != null ? wallet.getBalance(Wallet.BalanceType.AVAILABLE) : Coin.ZERO; } @@ -546,6 +590,10 @@ public abstract class WalletService { return getNumTxOutputsForAddress(address) == 0; } + public boolean isMine(TransactionOutput transactionOutput) { + return transactionOutput.isMine(wallet); + } + // BISQ issue #4039: Prevent dust outputs from being created. // Check the outputs of a proposed transaction. If any are below the dust threshold, // add up the dust, log the details, and return the cumulative dust amount. @@ -818,11 +866,15 @@ public abstract class WalletService { return maybeAddTxToWallet(transaction.bitcoinSerialize(), wallet, source); } + /////////////////////////////////////////////////////////////////////////////////////////// // bisqWalletEventListener /////////////////////////////////////////////////////////////////////////////////////////// - public class BisqWalletListener implements WalletCoinsReceivedEventListener, WalletCoinsSentEventListener, WalletReorganizeEventListener, TransactionConfidenceEventListener { + public class BisqWalletListener implements WalletCoinsReceivedEventListener, + WalletCoinsSentEventListener, + WalletReorganizeEventListener, + TransactionConfidenceEventListener { @Override public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) { notifyBalanceListeners(tx); @@ -848,7 +900,7 @@ public abstract class WalletService { .filter(txConfidenceListener -> tx != null && tx.getTxId().toString() != null && txConfidenceListener != null && - tx.getTxId().toString().equals(txConfidenceListener.getTxID())) + tx.getTxId().toString().equals(txConfidenceListener.getTxId())) .forEach(txConfidenceListener -> txConfidenceListener.onTransactionConfidenceChanged(tx.getConfidence())); } @@ -859,7 +911,7 @@ public abstract class WalletService { if (balanceListener.getAddress() != null) balance = getBalanceForAddress(balanceListener.getAddress()); else - balance = getAvailableConfirmedBalance(); + balance = getAvailableBalance(); balanceListener.onBalanceChanged(balance, tx); } diff --git a/core/src/main/java/bisq/core/dao/DaoFacade.java b/core/src/main/java/bisq/core/dao/DaoFacade.java index 8adfe466a2..2d1ac71601 100644 --- a/core/src/main/java/bisq/core/dao/DaoFacade.java +++ b/core/src/main/java/bisq/core/dao/DaoFacade.java @@ -60,6 +60,7 @@ import bisq.core.dao.state.model.blockchain.BaseTxOutput; import bisq.core.dao.state.model.blockchain.Block; import bisq.core.dao.state.model.blockchain.Tx; import bisq.core.dao.state.model.blockchain.TxOutput; +import bisq.core.dao.state.model.blockchain.TxOutputKey; import bisq.core.dao.state.model.blockchain.TxType; import bisq.core.dao.state.model.governance.Ballot; import bisq.core.dao.state.model.governance.BondedRoleType; @@ -649,6 +650,14 @@ public class DaoFacade implements DaoSetupService { return daoStateService.getUnspentTxOutputs(); } + public boolean isTxOutputSpendable(TxOutputKey txOutputKey) { + return daoStateService.isTxOutputSpendable(txOutputKey); + } + + public long getUnspentTxOutputValue(TxOutputKey key) { + return daoStateService.getUnspentTxOutputValue(key); + } + public int getNumTxs() { return daoStateService.getNumTxs(); } @@ -796,4 +805,8 @@ public class DaoFacade implements DaoSetupService { return allPastParamValues; } + + public boolean isParseBlockChainComplete() { + return daoStateService.isParseBlockChainComplete(); + } } diff --git a/core/src/main/java/bisq/core/dao/governance/asset/AssetService.java b/core/src/main/java/bisq/core/dao/governance/asset/AssetService.java index 0a6843a644..6db225836c 100644 --- a/core/src/main/java/bisq/core/dao/governance/asset/AssetService.java +++ b/core/src/main/java/bisq/core/dao/governance/asset/AssetService.java @@ -205,7 +205,7 @@ public class AssetService implements DaoSetupService, DaoStateListener { // We add the BTC inputs for the miner fee. Transaction txWithBtcFee = btcWalletService.completePreparedBurnBsqTx(preparedBurnFeeTx, opReturnData); // We sign the BSQ inputs of the final tx. - Transaction transaction = bsqWalletService.signTx(txWithBtcFee); + Transaction transaction = bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee); log.info("Asset listing fee tx: " + transaction); return transaction; } catch (WalletException | TransactionVerificationException e) { diff --git a/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteListService.java b/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteListService.java index d0edacc099..c0eb65001c 100644 --- a/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteListService.java +++ b/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteListService.java @@ -356,7 +356,7 @@ public class MyBlindVoteListService implements PersistedDataHost, DaoStateListen throws InsufficientMoneyException, WalletException, TransactionVerificationException { Transaction preparedTx = bsqWalletService.getPreparedBlindVoteTx(fee, stake); Transaction txWithBtcFee = btcWalletService.completePreparedBlindVoteTx(preparedTx, opReturnData); - return bsqWalletService.signTx(txWithBtcFee); + return bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee); } private void maybeRePublishMyBlindVote() { diff --git a/core/src/main/java/bisq/core/dao/governance/bond/lockup/LockupTxService.java b/core/src/main/java/bisq/core/dao/governance/bond/lockup/LockupTxService.java index 901793c74d..eb8772b0ec 100644 --- a/core/src/main/java/bisq/core/dao/governance/bond/lockup/LockupTxService.java +++ b/core/src/main/java/bisq/core/dao/governance/bond/lockup/LockupTxService.java @@ -104,7 +104,7 @@ public class LockupTxService { byte[] opReturnData = BondConsensus.getLockupOpReturnData(lockTime, lockupReason, hash); Transaction preparedTx = bsqWalletService.getPreparedLockupTx(lockupAmount); Transaction txWithBtcFee = btcWalletService.completePreparedBsqTx(preparedTx, opReturnData); - Transaction transaction = bsqWalletService.signTx(txWithBtcFee); + Transaction transaction = bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee); log.info("Lockup tx: " + transaction); return transaction; } diff --git a/core/src/main/java/bisq/core/dao/governance/bond/unlock/UnlockTxService.java b/core/src/main/java/bisq/core/dao/governance/bond/unlock/UnlockTxService.java index a4c6691cf6..2afa7b0664 100644 --- a/core/src/main/java/bisq/core/dao/governance/bond/unlock/UnlockTxService.java +++ b/core/src/main/java/bisq/core/dao/governance/bond/unlock/UnlockTxService.java @@ -104,7 +104,7 @@ public class UnlockTxService { TxOutput lockupTxOutput = optionalLockupTxOutput.get(); Transaction preparedTx = bsqWalletService.getPreparedUnlockTx(lockupTxOutput); Transaction txWithBtcFee = btcWalletService.completePreparedBsqTx(preparedTx, null); - Transaction transaction = bsqWalletService.signTx(txWithBtcFee); + Transaction transaction = bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee); log.info("Unlock tx: " + transaction); return transaction; } diff --git a/core/src/main/java/bisq/core/dao/governance/proofofburn/ProofOfBurnService.java b/core/src/main/java/bisq/core/dao/governance/proofofburn/ProofOfBurnService.java index 47125ad155..febcb8b449 100644 --- a/core/src/main/java/bisq/core/dao/governance/proofofburn/ProofOfBurnService.java +++ b/core/src/main/java/bisq/core/dao/governance/proofofburn/ProofOfBurnService.java @@ -140,7 +140,7 @@ public class ProofOfBurnService implements DaoSetupService, DaoStateListener { // We add the BTC inputs for the miner fee. Transaction txWithBtcFee = btcWalletService.completePreparedBurnBsqTx(preparedBurnFeeTx, opReturnData); // We sign the BSQ inputs of the final tx. - Transaction transaction = bsqWalletService.signTx(txWithBtcFee); + Transaction transaction = bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee); log.info("Proof of burn tx: " + transaction); return transaction; } catch (WalletException | TransactionVerificationException e) { diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/BaseProposalFactory.java b/core/src/main/java/bisq/core/dao/governance/proposal/BaseProposalFactory.java index 53c2967feb..6c634a6c13 100644 --- a/core/src/main/java/bisq/core/dao/governance/proposal/BaseProposalFactory.java +++ b/core/src/main/java/bisq/core/dao/governance/proposal/BaseProposalFactory.java @@ -99,7 +99,7 @@ public abstract class BaseProposalFactory { Transaction txWithBtcFee = completeTx(preparedBurnFeeTx, opReturnData, proposal); // We sign the BSQ inputs of the final tx. - Transaction transaction = bsqWalletService.signTx(txWithBtcFee); + Transaction transaction = bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee); log.info("Proposal tx: " + transaction); return transaction; } catch (WalletException | TransactionVerificationException e) { diff --git a/core/src/main/java/bisq/core/dao/governance/votereveal/VoteRevealService.java b/core/src/main/java/bisq/core/dao/governance/votereveal/VoteRevealService.java index 94b4f0b9a6..b02c03ba37 100644 --- a/core/src/main/java/bisq/core/dao/governance/votereveal/VoteRevealService.java +++ b/core/src/main/java/bisq/core/dao/governance/votereveal/VoteRevealService.java @@ -270,6 +270,6 @@ public class VoteRevealService implements DaoStateListener, DaoSetupService { throws InsufficientMoneyException, WalletException, TransactionVerificationException { Transaction preparedTx = bsqWalletService.getPreparedVoteRevealTx(stakeTxOutput); Transaction txWithBtcFee = btcWalletService.completePreparedVoteRevealTx(preparedTx, opReturnData); - return bsqWalletService.signTx(txWithBtcFee); + return bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee); } } diff --git a/core/src/main/java/bisq/core/dao/node/explorer/ExportJsonFilesService.java b/core/src/main/java/bisq/core/dao/node/explorer/ExportJsonFilesService.java index 0c40fc3683..1175a26446 100644 --- a/core/src/main/java/bisq/core/dao/node/explorer/ExportJsonFilesService.java +++ b/core/src/main/java/bisq/core/dao/node/explorer/ExportJsonFilesService.java @@ -25,6 +25,7 @@ import bisq.core.dao.state.model.blockchain.PubKeyScript; import bisq.core.dao.state.model.blockchain.Tx; import bisq.core.dao.state.model.blockchain.TxOutput; import bisq.core.dao.state.model.blockchain.TxType; +import bisq.core.util.JsonUtil; import bisq.common.config.Config; import bisq.common.file.FileUtil; @@ -154,9 +155,9 @@ public class ExportJsonFilesService implements DaoSetupService { JsonBlocks jsonBlocks = new JsonBlocks(daoState.getChainHeight(), jsonBlockList); ListenableFuture future = executor.submit(() -> { - bsqStateFileManager.writeToDisc(Utilities.objectToJson(jsonBlocks), "blocks"); - allJsonTxOutputs.forEach(jsonTxOutput -> txOutputFileManager.writeToDisc(Utilities.objectToJson(jsonTxOutput), jsonTxOutput.getId())); - jsonTxs.forEach(jsonTx -> txFileManager.writeToDisc(Utilities.objectToJson(jsonTx), jsonTx.getId())); + bsqStateFileManager.writeToDisc(JsonUtil.objectToJson(jsonBlocks), "blocks"); + allJsonTxOutputs.forEach(jsonTxOutput -> txOutputFileManager.writeToDisc(JsonUtil.objectToJson(jsonTxOutput), jsonTxOutput.getId())); + jsonTxs.forEach(jsonTx -> txFileManager.writeToDisc(JsonUtil.objectToJson(jsonTx), jsonTx.getId())); GcUtil.maybeReleaseMemory(); diff --git a/core/src/main/java/bisq/core/dao/state/DaoStateService.java b/core/src/main/java/bisq/core/dao/state/DaoStateService.java index bc75fb3602..e3869e06be 100644 --- a/core/src/main/java/bisq/core/dao/state/DaoStateService.java +++ b/core/src/main/java/bisq/core/dao/state/DaoStateService.java @@ -21,6 +21,7 @@ import bisq.core.dao.DaoSetupService; import bisq.core.dao.governance.bond.BondConsensus; import bisq.core.dao.governance.param.Param; import bisq.core.dao.state.model.DaoState; +import bisq.core.dao.state.model.blockchain.BaseTxOutput; import bisq.core.dao.state.model.blockchain.Block; import bisq.core.dao.state.model.blockchain.SpentInfo; import bisq.core.dao.state.model.blockchain.Tx; @@ -484,6 +485,12 @@ public class DaoStateService implements DaoSetupService { return Optional.ofNullable(getUnspentTxOutputMap().getOrDefault(key, null)); } + public long getUnspentTxOutputValue(TxOutputKey key) { + return getUnspentTxOutput(key) + .map(BaseTxOutput::getValue) + .orElse(0L); + } + public boolean isTxOutputSpendable(TxOutputKey key) { if (!isUnspent(key)) return false; @@ -492,7 +499,12 @@ public class DaoStateService implements DaoSetupService { // The above isUnspent call satisfies optionalTxOutput.isPresent() checkArgument(optionalTxOutput.isPresent(), "optionalTxOutput must be present"); TxOutput txOutput = optionalTxOutput.get(); + return isTxOutputSpendable(txOutput); + } + public boolean isTxOutputSpendable(TxOutput txOutput) { + // OP_RETURN_OUTPUTs are actually not spendable but as we have no value on them + // they would not be used anyway. switch (txOutput.getTxOutputType()) { case UNDEFINED_OUTPUT: return false; diff --git a/core/src/main/java/bisq/core/filter/Filter.java b/core/src/main/java/bisq/core/filter/Filter.java index be43c3f746..0a684cd9e9 100644 --- a/core/src/main/java/bisq/core/filter/Filter.java +++ b/core/src/main/java/bisq/core/filter/Filter.java @@ -39,13 +39,15 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import lombok.Value; +import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import javax.annotation.Nullable; @Slf4j -@Value +@Getter +@EqualsAndHashCode public final class Filter implements ProtectedStoragePayload, ExpirablePayload { public static final long TTL = TimeUnit.DAYS.toMillis(180); @@ -101,6 +103,12 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload { // added at v1.6.0 private final boolean disableMempoolValidation; + // added at BsqSwap release + private final boolean disablePowMessage; + // Number of leading zeros for pow for BSQ swap offers. Difficulty of 8 requires 0.856 ms in average, 15 about 100 ms. + // See ProofOfWorkTest for more info. + private final int powDifficulty; + // After we have created the signature from the filter data we clone it and apply the signature static Filter cloneWithSig(Filter filter, String signatureAsBase64) { return new Filter(filter.getBannedOfferIds(), @@ -130,7 +138,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload { filter.getBannedAutoConfExplorers(), filter.getNodeAddressesBannedFromNetwork(), filter.isDisableMempoolValidation(), - filter.isDisableApi()); + filter.isDisableApi(), + filter.isDisablePowMessage(), + filter.getPowDifficulty()); } // Used for signature verification as we created the sig without the signatureAsBase64 field we set it to null again @@ -162,7 +172,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload { filter.getBannedAutoConfExplorers(), filter.getNodeAddressesBannedFromNetwork(), filter.isDisableMempoolValidation(), - filter.isDisableApi()); + filter.isDisableApi(), + filter.isDisablePowMessage(), + filter.getPowDifficulty()); } public Filter(List bannedOfferIds, @@ -189,7 +201,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload { List bannedAutoConfExplorers, Set nodeAddressesBannedFromNetwork, boolean disableMempoolValidation, - boolean disableApi) { + boolean disableApi, + boolean disablePowMessage, + int powDifficulty) { this(bannedOfferIds, nodeAddressesBannedFromTrading, bannedPaymentAccounts, @@ -217,7 +231,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload { bannedAutoConfExplorers, nodeAddressesBannedFromNetwork, disableMempoolValidation, - disableApi); + disableApi, + disablePowMessage, + powDifficulty); } @@ -253,7 +269,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload { List bannedAutoConfExplorers, Set nodeAddressesBannedFromNetwork, boolean disableMempoolValidation, - boolean disableApi) { + boolean disableApi, + boolean disablePowMessage, + int powDifficulty) { this.bannedOfferIds = bannedOfferIds; this.nodeAddressesBannedFromTrading = nodeAddressesBannedFromTrading; this.bannedPaymentAccounts = bannedPaymentAccounts; @@ -282,6 +300,8 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload { this.nodeAddressesBannedFromNetwork = nodeAddressesBannedFromNetwork; this.disableMempoolValidation = disableMempoolValidation; this.disableApi = disableApi; + this.disablePowMessage = disablePowMessage; + this.powDifficulty = powDifficulty; // ownerPubKeyBytes can be null when called from tests if (ownerPubKeyBytes != null) { @@ -322,7 +342,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload { .addAllBannedAutoConfExplorers(bannedAutoConfExplorers) .addAllNodeAddressesBannedFromNetwork(nodeAddressesBannedFromNetwork) .setDisableMempoolValidation(disableMempoolValidation) - .setDisableApi(disableApi); + .setDisableApi(disableApi) + .setDisablePowMessage(disablePowMessage) + .setPowDifficulty(powDifficulty); Optional.ofNullable(signatureAsBase64).ifPresent(builder::setSignatureAsBase64); Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData); @@ -363,7 +385,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload { ProtoUtil.protocolStringListToList(proto.getBannedAutoConfExplorersList()), ProtoUtil.protocolStringListToSet(proto.getNodeAddressesBannedFromNetworkList()), proto.getDisableMempoolValidation(), - proto.getDisableApi() + proto.getDisableApi(), + proto.getDisablePowMessage(), + proto.getPowDifficulty() ); } @@ -409,6 +433,8 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload { ",\n nodeAddressesBannedFromNetwork=" + nodeAddressesBannedFromNetwork + ",\n disableMempoolValidation=" + disableMempoolValidation + ",\n disableApi=" + disableApi + + ",\n disablePowMessage=" + disablePowMessage + + ",\n powDifficulty=" + powDifficulty + "\n}"; } } diff --git a/core/src/main/java/bisq/core/filter/FilterManager.java b/core/src/main/java/bisq/core/filter/FilterManager.java index 472ba4bfcf..dfae9cf685 100644 --- a/core/src/main/java/bisq/core/filter/FilterManager.java +++ b/core/src/main/java/bisq/core/filter/FilterManager.java @@ -19,6 +19,7 @@ package bisq.core.filter; import bisq.core.btc.nodes.BtcNodes; import bisq.core.locale.Res; +import bisq.core.offer.Offer; import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.payment.payload.PaymentMethod; import bisq.core.provider.ProvidersRepository; @@ -36,6 +37,7 @@ import bisq.common.app.DevEnv; import bisq.common.app.Version; import bisq.common.config.Config; import bisq.common.config.ConfigFileEditor; +import bisq.common.crypto.HashCashService; import bisq.common.crypto.KeyRing; import org.bitcoinj.core.ECKey; @@ -55,6 +57,7 @@ import java.nio.charset.StandardCharsets; import java.math.BigInteger; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -62,6 +65,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.BiFunction; import java.util.function.Consumer; import java.lang.reflect.Method; @@ -70,6 +74,7 @@ import lombok.extern.slf4j.Slf4j; import javax.annotation.Nullable; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static org.bitcoinj.core.Utils.HEX; @@ -82,6 +87,11 @@ public class FilterManager { private static final String BANNED_SEED_NODES = "bannedSeedNodes"; private static final String BANNED_BTC_NODES = "bannedBtcNodes"; + private final BiFunction challengeValidation = Arrays::equals; + // We only require a new pow if difficulty has increased + private final BiFunction difficultyValidation = + (value, controlValue) -> value - controlValue >= 0; + /////////////////////////////////////////////////////////////////////////////////////////// // Listener @@ -476,6 +486,20 @@ public class FilterManager { .anyMatch(e -> e.equals(witnessSignerPubKeyAsHex)); } + public boolean isProofOfWorkValid(Offer offer) { + Filter filter = getFilter(); + if (filter == null) { + return true; + } + checkArgument(offer.getBsqSwapOfferPayload().isPresent(), + "Offer payload must be BsqSwapOfferPayload"); + return HashCashService.verify(offer.getBsqSwapOfferPayload().get().getProofOfWork(), + HashCashService.getBytes(offer.getId() + offer.getOwnerNodeAddress().toString()), + filter.getPowDifficulty(), + challengeValidation, + difficultyValidation); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Private @@ -499,13 +523,13 @@ public class FilterManager { if (currentFilter != null) { if (currentFilter.getCreationDate() > newFilter.getCreationDate()) { - log.warn("We received a new filter from the network but the creation date is older than the " + + log.debug("We received a new filter from the network but the creation date is older than the " + "filter we have already. We ignore the new filter."); addToInvalidFilters(newFilter); return; } else { - log.warn("We received a new filter from the network and the creation date is newer than the " + + log.debug("We received a new filter from the network and the creation date is newer than the " + "filter we have already. We ignore the old filter."); addToInvalidFilters(currentFilter); } diff --git a/core/src/main/java/bisq/core/locale/Res.java b/core/src/main/java/bisq/core/locale/Res.java index cec006aff0..5bd882f176 100644 --- a/core/src/main/java/bisq/core/locale/Res.java +++ b/core/src/main/java/bisq/core/locale/Res.java @@ -120,13 +120,13 @@ public class Res { .replace("bitcoin", baseCurrencyNameLowerCase); } catch (MissingResourceException e) { log.warn("Missing resource for key: {}", key); - e.printStackTrace(); - if (DevEnv.isDevMode()) + if (DevEnv.isDevMode()) { + e.printStackTrace(); UserThread.runAfter(() -> { // We delay a bit to not throw while UI is not ready throw new RuntimeException("Missing resource for key: " + key); }, 1); - + } return key; } } diff --git a/core/src/main/java/bisq/core/notifications/alerts/TradeEvents.java b/core/src/main/java/bisq/core/notifications/alerts/TradeEvents.java index 7bfe955693..d0565d4db2 100644 --- a/core/src/main/java/bisq/core/notifications/alerts/TradeEvents.java +++ b/core/src/main/java/bisq/core/notifications/alerts/TradeEvents.java @@ -21,8 +21,8 @@ import bisq.core.locale.Res; import bisq.core.notifications.MobileMessage; import bisq.core.notifications.MobileMessageType; import bisq.core.notifications.MobileNotificationService; -import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.common.crypto.KeyRing; import bisq.common.crypto.PubKeyRing; diff --git a/core/src/main/java/bisq/core/notifications/alerts/market/MarketAlerts.java b/core/src/main/java/bisq/core/notifications/alerts/market/MarketAlerts.java index bd170c0879..6f25f2782a 100644 --- a/core/src/main/java/bisq/core/notifications/alerts/market/MarketAlerts.java +++ b/core/src/main/java/bisq/core/notifications/alerts/market/MarketAlerts.java @@ -26,7 +26,7 @@ import bisq.core.notifications.MobileMessageType; import bisq.core.notifications.MobileNotificationService; import bisq.core.offer.Offer; import bisq.core.offer.OfferBookService; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferDirection; import bisq.core.provider.price.MarketPrice; import bisq.core.provider.price.PriceFeedService; import bisq.core.user.User; @@ -109,7 +109,7 @@ public class MarketAlerts { // % price get multiplied by 10000 to have 0.12% be converted to 12. For fixed price we have precision of 8 for // altcoins and precision of 4 for fiat. private String getAlertId(Offer offer) { - double price = offer.isUseMarketBasedPrice() ? offer.getMarketPriceMargin() * 10000 : offer.getOfferPayload().getPrice(); + double price = offer.isUseMarketBasedPrice() ? offer.getMarketPriceMargin() * 10000 : offer.getFixedPrice(); String priceString = String.valueOf((long) price); return offer.getId() + "|" + priceString; } @@ -119,7 +119,7 @@ public class MarketAlerts { MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode); Price offerPrice = offer.getPrice(); if (marketPrice != null && offerPrice != null) { - boolean isSellOffer = offer.getDirection() == OfferPayload.Direction.SELL; + boolean isSellOffer = offer.getDirection() == OfferDirection.SELL; String shortOfferId = offer.getShortId(); boolean isFiatCurrency = CurrencyUtil.isFiatCurrency(currencyCode); String alertId = getAlertId(offer); diff --git a/core/src/main/java/bisq/core/offer/Offer.java b/core/src/main/java/bisq/core/offer/Offer.java index 05305dadf0..7c3797a058 100644 --- a/core/src/main/java/bisq/core/offer/Offer.java +++ b/core/src/main/java/bisq/core/offer/Offer.java @@ -24,6 +24,9 @@ import bisq.core.monetary.Price; import bisq.core.monetary.Volume; import bisq.core.offer.availability.OfferAvailabilityModel; import bisq.core.offer.availability.OfferAvailabilityProtocol; +import bisq.core.offer.bisq_v1.MarketPriceNotAvailableException; +import bisq.core.offer.bisq_v1.OfferPayload; +import bisq.core.offer.bsq_swap.BsqSwapOfferPayload; import bisq.core.payment.payload.PaymentMethod; import bisq.core.provider.price.MarketPrice; import bisq.core.provider.price.PriceFeedService; @@ -75,7 +78,6 @@ public class Offer implements NetworkPayload, PersistablePayload { // from one provider. private final static double PRICE_TOLERANCE = 0.01; - /////////////////////////////////////////////////////////////////////////////////////////// // Enums /////////////////////////////////////////////////////////////////////////////////////////// @@ -94,7 +96,7 @@ public class Offer implements NetworkPayload, PersistablePayload { /////////////////////////////////////////////////////////////////////////////////////////// @Getter - private final OfferPayload offerPayload; + private final OfferPayloadBase offerPayloadBase; @JsonExclude @Getter final transient private ObjectProperty stateProperty = new SimpleObjectProperty<>(Offer.State.UNKNOWN); @@ -119,8 +121,8 @@ public class Offer implements NetworkPayload, PersistablePayload { // Constructor /////////////////////////////////////////////////////////////////////////////////////////// - public Offer(OfferPayload offerPayload) { - this.offerPayload = offerPayload; + public Offer(OfferPayloadBase offerPayloadBase) { + this.offerPayloadBase = offerPayloadBase; } @@ -130,11 +132,21 @@ public class Offer implements NetworkPayload, PersistablePayload { @Override public protobuf.Offer toProtoMessage() { - return protobuf.Offer.newBuilder().setOfferPayload(offerPayload.toProtoMessage().getOfferPayload()).build(); + if (isBsqSwapOffer()) { + return protobuf.Offer.newBuilder().setBsqSwapOfferPayload(((BsqSwapOfferPayload) offerPayloadBase) + .toProtoMessage().getBsqSwapOfferPayload()).build(); + } else { + return protobuf.Offer.newBuilder().setOfferPayload(((OfferPayload) offerPayloadBase) + .toProtoMessage().getOfferPayload()).build(); + } } public static Offer fromProto(protobuf.Offer proto) { - return new Offer(OfferPayload.fromProto(proto.getOfferPayload())); + if (proto.hasOfferPayload()) { + return new Offer(OfferPayload.fromProto(proto.getOfferPayload())); + } else { + return new Offer(BsqSwapOfferPayload.fromProto(proto.getBsqSwapOfferPayload())); + } } @@ -166,43 +178,53 @@ public class Offer implements NetworkPayload, PersistablePayload { @Nullable public Price getPrice() { String currencyCode = getCurrencyCode(); - if (offerPayload.isUseMarketBasedPrice()) { - checkNotNull(priceFeedService, "priceFeed must not be null"); - MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode); - if (marketPrice != null && marketPrice.isRecentExternalPriceAvailable()) { - double factor; - double marketPriceMargin = offerPayload.getMarketPriceMargin(); - if (CurrencyUtil.isCryptoCurrency(currencyCode)) { - factor = getDirection() == OfferPayload.Direction.SELL ? - 1 - marketPriceMargin : 1 + marketPriceMargin; - } else { - factor = getDirection() == OfferPayload.Direction.BUY ? - 1 - marketPriceMargin : 1 + marketPriceMargin; - } - double marketPriceAsDouble = marketPrice.getPrice(); - double targetPriceAsDouble = marketPriceAsDouble * factor; - try { - int precision = CurrencyUtil.isCryptoCurrency(currencyCode) ? - Altcoin.SMALLEST_UNIT_EXPONENT : - Fiat.SMALLEST_UNIT_EXPONENT; - double scaled = MathUtils.scaleUpByPowerOf10(targetPriceAsDouble, precision); - final long roundedToLong = MathUtils.roundDoubleToLong(scaled); - return Price.valueOf(currencyCode, roundedToLong); - } catch (Exception e) { - log.error("Exception at getPrice / parseToFiat: " + e.toString() + "\n" + - "That case should never happen."); - return null; - } + Optional optionalOfferPayload = getOfferPayload(); + if (!optionalOfferPayload.isPresent()) { + return Price.valueOf(currencyCode, offerPayloadBase.getPrice()); + } + + OfferPayload offerPayload = optionalOfferPayload.get(); + if (!offerPayload.isUseMarketBasedPrice()) { + return Price.valueOf(currencyCode, offerPayloadBase.getPrice()); + } + + checkNotNull(priceFeedService, "priceFeed must not be null"); + MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode); + if (marketPrice != null && marketPrice.isRecentExternalPriceAvailable()) { + double factor; + double marketPriceMargin = offerPayload.getMarketPriceMargin(); + if (CurrencyUtil.isCryptoCurrency(currencyCode)) { + factor = getDirection() == OfferDirection.SELL ? + 1 - marketPriceMargin : 1 + marketPriceMargin; } else { - log.trace("We don't have a market price. " + - "That case could only happen if you don't have a price feed."); + factor = getDirection() == OfferDirection.BUY ? + 1 - marketPriceMargin : 1 + marketPriceMargin; + } + double marketPriceAsDouble = marketPrice.getPrice(); + double targetPriceAsDouble = marketPriceAsDouble * factor; + try { + int precision = CurrencyUtil.isCryptoCurrency(currencyCode) ? + Altcoin.SMALLEST_UNIT_EXPONENT : + Fiat.SMALLEST_UNIT_EXPONENT; + double scaled = MathUtils.scaleUpByPowerOf10(targetPriceAsDouble, precision); + final long roundedToLong = MathUtils.roundDoubleToLong(scaled); + return Price.valueOf(currencyCode, roundedToLong); + } catch (Exception e) { + log.error("Exception at getPrice / parseToFiat: " + e + "\n" + + "That case should never happen."); return null; } } else { - return Price.valueOf(currencyCode, offerPayload.getPrice()); + log.trace("We don't have a market price. " + + "That case could only happen if you don't have a price feed."); + return null; } } + public long getFixedPrice() { + return offerPayloadBase.getPrice(); + } + public void checkTradePriceTolerance(long takersTradePrice) throws TradePriceOutOfToleranceException, MarketPriceNotAvailableException, IllegalArgumentException { Price tradePrice = Price.valueOf(getCurrencyCode(), takersTradePrice); @@ -234,17 +256,16 @@ public class Offer implements NetworkPayload, PersistablePayload { @Nullable public Volume getVolumeByAmount(Coin amount) { Price price = getPrice(); - if (price != null && amount != null) { - Volume volumeByAmount = price.getVolumeByAmount(amount); - if (offerPayload.getPaymentMethodId().equals(PaymentMethod.HAL_CASH_ID)) - volumeByAmount = VolumeUtil.getAdjustedVolumeForHalCash(volumeByAmount); - else if (CurrencyUtil.isFiatCurrency(offerPayload.getCurrencyCode())) - volumeByAmount = VolumeUtil.getRoundedFiatVolume(volumeByAmount); - - return volumeByAmount; - } else { + if (price == null || amount == null) { return null; } + Volume volumeByAmount = price.getVolumeByAmount(amount); + if (offerPayloadBase.getPaymentMethodId().equals(PaymentMethod.HAL_CASH_ID)) + volumeByAmount = VolumeUtil.getAdjustedVolumeForHalCash(volumeByAmount); + else if (CurrencyUtil.isFiatCurrency(offerPayloadBase.getCurrencyCode())) + volumeByAmount = VolumeUtil.getRoundedFiatVolume(volumeByAmount); + + return volumeByAmount; } public void resetState() { @@ -265,7 +286,7 @@ public class Offer implements NetworkPayload, PersistablePayload { } public void setOfferFeePaymentTxId(String offerFeePaymentTxID) { - offerPayload.setOfferFeePaymentTxId(offerFeePaymentTxID); + getOfferPayload().ifPresent(p -> p.setOfferFeePaymentTxId(offerFeePaymentTxID)); } public void setErrorMessage(String errorMessage) { @@ -279,52 +300,52 @@ public class Offer implements NetworkPayload, PersistablePayload { // converted payload properties public Coin getTxFee() { - return Coin.valueOf(offerPayload.getTxFee()); + return Coin.valueOf(getOfferPayload().map(OfferPayload::getTxFee).orElse(0L)); } public Coin getMakerFee() { - return Coin.valueOf(offerPayload.getMakerFee()); + return getOfferPayload().map(OfferPayload::getMakerFee).map(Coin::valueOf).orElse(Coin.ZERO); } public boolean isCurrencyForMakerFeeBtc() { - return offerPayload.isCurrencyForMakerFeeBtc(); + return getOfferPayload().map(OfferPayload::isCurrencyForMakerFeeBtc).orElse(false); } public Coin getBuyerSecurityDeposit() { - return Coin.valueOf(offerPayload.getBuyerSecurityDeposit()); + return Coin.valueOf(getOfferPayload().map(OfferPayload::getBuyerSecurityDeposit).orElse(0L)); } public Coin getSellerSecurityDeposit() { - return Coin.valueOf(offerPayload.getSellerSecurityDeposit()); + return Coin.valueOf(getOfferPayload().map(OfferPayload::getSellerSecurityDeposit).orElse(0L)); } public Coin getMaxTradeLimit() { - return Coin.valueOf(offerPayload.getMaxTradeLimit()); + return getOfferPayload().map(OfferPayload::getMaxTradeLimit).map(Coin::valueOf).orElse(Coin.ZERO); } public Coin getAmount() { - return Coin.valueOf(offerPayload.getAmount()); + return Coin.valueOf(offerPayloadBase.getAmount()); } public Coin getMinAmount() { - return Coin.valueOf(offerPayload.getMinAmount()); + return Coin.valueOf(offerPayloadBase.getMinAmount()); } public boolean isRange() { - return offerPayload.getAmount() != offerPayload.getMinAmount(); + return offerPayloadBase.getAmount() != offerPayloadBase.getMinAmount(); } public Date getDate() { - return new Date(offerPayload.getDate()); + return new Date(offerPayloadBase.getDate()); } public PaymentMethod getPaymentMethod() { - return PaymentMethod.getPaymentMethodById(offerPayload.getPaymentMethodId()); + return PaymentMethod.getPaymentMethodById(offerPayloadBase.getPaymentMethodId()); } // utils public String getShortId() { - return Utilities.getShortId(offerPayload.getId()); + return Utilities.getShortId(offerPayloadBase.getId()); } @Nullable @@ -338,18 +359,17 @@ public class Offer implements NetworkPayload, PersistablePayload { } public boolean isBuyOffer() { - return getDirection() == OfferPayload.Direction.BUY; + return getDirection() == OfferDirection.BUY; } - public OfferPayload.Direction getMirroredDirection() { - return getDirection() == OfferPayload.Direction.BUY ? OfferPayload.Direction.SELL : OfferPayload.Direction.BUY; + public OfferDirection getMirroredDirection() { + return getDirection() == OfferDirection.BUY ? OfferDirection.SELL : OfferDirection.BUY; } public boolean isMyOffer(KeyRing keyRing) { return getPubKeyRing().equals(keyRing.getPubKeyRing()); } - public Optional getAccountAgeWitnessHashAsHex() { Map extraDataMap = getExtraDataMap(); if (extraDataMap != null && extraDataMap.containsKey(OfferPayload.ACCOUNT_AGE_WITNESS_HASH)) @@ -400,32 +420,32 @@ public class Offer implements NetworkPayload, PersistablePayload { // Delegate Getter (boilerplate code generated via IntelliJ generate delegate feature) /////////////////////////////////////////////////////////////////////////////////////////// - public OfferPayload.Direction getDirection() { - return offerPayload.getDirection(); + public OfferDirection getDirection() { + return offerPayloadBase.getDirection(); } public String getId() { - return offerPayload.getId(); + return offerPayloadBase.getId(); } @Nullable public List getAcceptedBankIds() { - return offerPayload.getAcceptedBankIds(); + return getOfferPayload().map(OfferPayload::getAcceptedBankIds).orElse(null); } @Nullable public String getBankId() { - return offerPayload.getBankId(); + return getOfferPayload().map(OfferPayload::getBankId).orElse(null); } @Nullable public List getAcceptedCountryCodes() { - return offerPayload.getAcceptedCountryCodes(); + return getOfferPayload().map(OfferPayload::getAcceptedCountryCodes).orElse(null); } @Nullable public String getCountryCode() { - return offerPayload.getCountryCode(); + return getOfferPayload().map(OfferPayload::getCountryCode).orElse(null); } public String getCurrencyCode() { @@ -433,89 +453,84 @@ public class Offer implements NetworkPayload, PersistablePayload { return currencyCode; } - currencyCode = offerPayload.getBaseCurrencyCode().equals("BTC") ? - offerPayload.getCounterCurrencyCode() : - offerPayload.getBaseCurrencyCode(); + currencyCode = getBaseCurrencyCode().equals("BTC") ? + getCounterCurrencyCode() : + getBaseCurrencyCode(); return currencyCode; } + public String getCounterCurrencyCode() { + return offerPayloadBase.getCounterCurrencyCode(); + } + + public String getBaseCurrencyCode() { + return offerPayloadBase.getBaseCurrencyCode(); + } + + public String getPaymentMethodId() { + return offerPayloadBase.getPaymentMethodId(); + } + public long getProtocolVersion() { - return offerPayload.getProtocolVersion(); + return offerPayloadBase.getProtocolVersion(); } public boolean isUseMarketBasedPrice() { - return offerPayload.isUseMarketBasedPrice(); + return getOfferPayload().map(OfferPayload::isUseMarketBasedPrice).orElse(false); } public double getMarketPriceMargin() { - return offerPayload.getMarketPriceMargin(); + return getOfferPayload().map(OfferPayload::getMarketPriceMargin).orElse(0D); } public NodeAddress getMakerNodeAddress() { - return offerPayload.getOwnerNodeAddress(); + return offerPayloadBase.getOwnerNodeAddress(); } public PubKeyRing getPubKeyRing() { - return offerPayload.getPubKeyRing(); + return offerPayloadBase.getPubKeyRing(); } public String getMakerPaymentAccountId() { - return offerPayload.getMakerPaymentAccountId(); + return offerPayloadBase.getMakerPaymentAccountId(); } public String getOfferFeePaymentTxId() { - return offerPayload.getOfferFeePaymentTxId(); + return getOfferPayload().map(OfferPayload::getOfferFeePaymentTxId).orElse(null); } public String getVersionNr() { - return offerPayload.getVersionNr(); + return offerPayloadBase.getVersionNr(); } public long getMaxTradePeriod() { - return offerPayload.getMaxTradePeriod(); + return getOfferPayload().map(OfferPayload::getMaxTradePeriod).orElse(0L); } public NodeAddress getOwnerNodeAddress() { - return offerPayload.getOwnerNodeAddress(); + return offerPayloadBase.getOwnerNodeAddress(); } // Yet unused public PublicKey getOwnerPubKey() { - return offerPayload.getOwnerPubKey(); + return offerPayloadBase.getOwnerPubKey(); } @Nullable public Map getExtraDataMap() { - return offerPayload.getExtraDataMap(); + return offerPayloadBase.getExtraDataMap(); } public boolean isUseAutoClose() { - return offerPayload.isUseAutoClose(); - } - - public long getBlockHeightAtOfferCreation() { - return offerPayload.getBlockHeightAtOfferCreation(); - } - - @Nullable - public String getHashOfChallenge() { - return offerPayload.getHashOfChallenge(); - } - - public boolean isPrivateOffer() { - return offerPayload.isPrivateOffer(); - } - - public long getUpperClosePrice() { - return offerPayload.getUpperClosePrice(); - } - - public long getLowerClosePrice() { - return offerPayload.getLowerClosePrice(); + return getOfferPayload().map(OfferPayload::isUseAutoClose).orElse(false); } public boolean isUseReOpenAfterAutoClose() { - return offerPayload.isUseReOpenAfterAutoClose(); + return getOfferPayload().map(OfferPayload::isUseReOpenAfterAutoClose).orElse(false); + } + + public boolean isBsqSwapOffer() { + return getOfferPayloadBase() instanceof BsqSwapOfferPayload; } public boolean isXmrAutoConf() { @@ -533,6 +548,24 @@ public class Offer implements NetworkPayload, PersistablePayload { return getCurrencyCode().equals("XMR"); } + public Optional getOfferPayload() { + if (offerPayloadBase instanceof OfferPayload) { + return Optional.of((OfferPayload) offerPayloadBase); + } + return Optional.empty(); + } + + public Optional getBsqSwapOfferPayload() { + if (offerPayloadBase instanceof BsqSwapOfferPayload) { + return Optional.of((BsqSwapOfferPayload) offerPayloadBase); + } + return Optional.empty(); + } + + public byte[] getOfferPayloadHash() { + return offerPayloadBase.getHash(); + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -540,7 +573,8 @@ public class Offer implements NetworkPayload, PersistablePayload { Offer offer = (Offer) o; - if (offerPayload != null ? !offerPayload.equals(offer.offerPayload) : offer.offerPayload != null) return false; + if (offerPayloadBase != null ? !offerPayloadBase.equals(offer.offerPayloadBase) : offer.offerPayloadBase != null) + return false; //noinspection SimplifiableIfStatement if (getState() != offer.getState()) return false; return !(getErrorMessage() != null ? !getErrorMessage().equals(offer.getErrorMessage()) : offer.getErrorMessage() != null); @@ -549,7 +583,7 @@ public class Offer implements NetworkPayload, PersistablePayload { @Override public int hashCode() { - int result = offerPayload != null ? offerPayload.hashCode() : 0; + int result = offerPayloadBase != null ? offerPayloadBase.hashCode() : 0; result = 31 * result + (getState() != null ? getState().hashCode() : 0); result = 31 * result + (getErrorMessage() != null ? getErrorMessage().hashCode() : 0); return result; @@ -560,7 +594,7 @@ public class Offer implements NetworkPayload, PersistablePayload { return "Offer{" + "getErrorMessage()='" + getErrorMessage() + '\'' + ", state=" + getState() + - ", offerPayload=" + offerPayload + + ", offerPayloadBase=" + offerPayloadBase + '}'; } } diff --git a/core/src/main/java/bisq/core/offer/OfferBookService.java b/core/src/main/java/bisq/core/offer/OfferBookService.java index 71a28280ef..60099f0bc6 100644 --- a/core/src/main/java/bisq/core/offer/OfferBookService.java +++ b/core/src/main/java/bisq/core/offer/OfferBookService.java @@ -20,6 +20,7 @@ package bisq.core.offer; import bisq.core.filter.FilterManager; import bisq.core.locale.Res; import bisq.core.provider.price.PriceFeedService; +import bisq.core.util.JsonUtil; import bisq.network.p2p.BootstrapListener; import bisq.network.p2p.P2PService; @@ -31,7 +32,6 @@ import bisq.common.config.Config; import bisq.common.file.JsonFileManager; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ResultHandler; -import bisq.common.util.Utilities; import javax.inject.Inject; import javax.inject.Named; @@ -87,9 +87,9 @@ public class OfferBookService { @Override public void onAdded(Collection protectedStorageEntries) { protectedStorageEntries.forEach(protectedStorageEntry -> offerBookChangedListeners.forEach(listener -> { - if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayload) { - OfferPayload offerPayload = (OfferPayload) protectedStorageEntry.getProtectedStoragePayload(); - Offer offer = new Offer(offerPayload); + if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayloadBase) { + OfferPayloadBase offerPayloadBase = (OfferPayloadBase) protectedStorageEntry.getProtectedStoragePayload(); + Offer offer = new Offer(offerPayloadBase); offer.setPriceFeedService(priceFeedService); listener.onAdded(offer); } @@ -99,9 +99,9 @@ public class OfferBookService { @Override public void onRemoved(Collection protectedStorageEntries) { protectedStorageEntries.forEach(protectedStorageEntry -> offerBookChangedListeners.forEach(listener -> { - if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayload) { - OfferPayload offerPayload = (OfferPayload) protectedStorageEntry.getProtectedStoragePayload(); - Offer offer = new Offer(offerPayload); + if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayloadBase) { + OfferPayloadBase offerPayloadBase = (OfferPayloadBase) protectedStorageEntry.getProtectedStoragePayload(); + Offer offer = new Offer(offerPayloadBase); offer.setPriceFeedService(priceFeedService); listener.onRemoved(offer); } @@ -141,7 +141,7 @@ public class OfferBookService { return; } - boolean result = p2PService.addProtectedStorageEntry(offer.getOfferPayload()); + boolean result = p2PService.addProtectedStorageEntry(offer.getOfferPayloadBase()); if (result) { resultHandler.handleResult(); } else { @@ -149,7 +149,7 @@ public class OfferBookService { } } - public void refreshTTL(OfferPayload offerPayload, + public void refreshTTL(OfferPayloadBase offerPayloadBase, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { if (filterManager.requireUpdateToNewVersionForTrading()) { @@ -157,7 +157,7 @@ public class OfferBookService { return; } - boolean result = p2PService.refreshTTL(offerPayload); + boolean result = p2PService.refreshTTL(offerPayloadBase); if (result) { resultHandler.handleResult(); } else { @@ -171,16 +171,16 @@ public class OfferBookService { addOffer(offer, resultHandler, errorMessageHandler); } - public void deactivateOffer(OfferPayload offerPayload, + public void deactivateOffer(OfferPayloadBase offerPayloadBase, @Nullable ResultHandler resultHandler, @Nullable ErrorMessageHandler errorMessageHandler) { - removeOffer(offerPayload, resultHandler, errorMessageHandler); + removeOffer(offerPayloadBase, resultHandler, errorMessageHandler); } - public void removeOffer(OfferPayload offerPayload, + public void removeOffer(OfferPayloadBase offerPayloadBase, @Nullable ResultHandler resultHandler, @Nullable ErrorMessageHandler errorMessageHandler) { - if (p2PService.removeData(offerPayload)) { + if (p2PService.removeData(offerPayloadBase)) { if (resultHandler != null) resultHandler.handleResult(); } else { @@ -191,18 +191,18 @@ public class OfferBookService { public List getOffers() { return p2PService.getDataMap().values().stream() - .filter(data -> data.getProtectedStoragePayload() instanceof OfferPayload) + .filter(data -> data.getProtectedStoragePayload() instanceof OfferPayloadBase) .map(data -> { - OfferPayload offerPayload = (OfferPayload) data.getProtectedStoragePayload(); - Offer offer = new Offer(offerPayload); + OfferPayloadBase offerPayloadBase = (OfferPayloadBase) data.getProtectedStoragePayload(); + Offer offer = new Offer(offerPayloadBase); offer.setPriceFeedService(priceFeedService); return offer; }) .collect(Collectors.toList()); } - public void removeOfferAtShutDown(OfferPayload offerPayload) { - removeOffer(offerPayload, null, null); + public void removeOfferAtShutDown(OfferPayloadBase offerPayloadBase) { + removeOffer(offerPayloadBase, null, null); } public boolean isBootstrapped() { @@ -243,6 +243,6 @@ public class OfferBookService { }) .filter(Objects::nonNull) .collect(Collectors.toList()); - jsonFileManager.writeToDiscThreaded(Utilities.objectToJson(offerForJsonList), "offers_statistics"); + jsonFileManager.writeToDiscThreaded(JsonUtil.objectToJson(offerForJsonList), "offers_statistics"); } } diff --git a/core/src/main/java/bisq/core/offer/OfferDirection.java b/core/src/main/java/bisq/core/offer/OfferDirection.java new file mode 100644 index 0000000000..ade5334a88 --- /dev/null +++ b/core/src/main/java/bisq/core/offer/OfferDirection.java @@ -0,0 +1,33 @@ +/* + * 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.core.offer; + +import bisq.common.proto.ProtoUtil; + +public enum OfferDirection { + BUY, + SELL; + + public static OfferDirection fromProto(protobuf.OfferDirection direction) { + return ProtoUtil.enumFromProto(OfferDirection.class, direction.name()); + } + + public static protobuf.OfferDirection toProtoMessage(OfferDirection direction) { + return protobuf.OfferDirection.valueOf(direction.name()); + } +} diff --git a/core/src/main/java/bisq/core/offer/OfferFilter.java b/core/src/main/java/bisq/core/offer/OfferFilterService.java similarity index 93% rename from core/src/main/java/bisq/core/offer/OfferFilter.java rename to core/src/main/java/bisq/core/offer/OfferFilterService.java index bfd37f2af1..7a18db1f96 100644 --- a/core/src/main/java/bisq/core/offer/OfferFilter.java +++ b/core/src/main/java/bisq/core/offer/OfferFilterService.java @@ -25,6 +25,7 @@ import bisq.core.payment.PaymentAccountUtil; import bisq.core.user.Preferences; import bisq.core.user.User; +import bisq.common.app.DevEnv; import bisq.common.app.Version; import org.bitcoinj.core.Coin; @@ -43,7 +44,7 @@ import lombok.extern.slf4j.Slf4j; @Slf4j @Singleton -public class OfferFilter { +public class OfferFilterService { private final User user; private final Preferences preferences; private final FilterManager filterManager; @@ -52,10 +53,10 @@ public class OfferFilter { private final Map myInsufficientTradeLimitCache = new HashMap<>(); @Inject - public OfferFilter(User user, - Preferences preferences, - FilterManager filterManager, - AccountAgeWitnessService accountAgeWitnessService) { + public OfferFilterService(User user, + Preferences preferences, + FilterManager filterManager, + AccountAgeWitnessService accountAgeWitnessService) { this.user = user; this.preferences = preferences; this.filterManager = filterManager; @@ -80,7 +81,8 @@ public class OfferFilter { IS_NODE_ADDRESS_BANNED, REQUIRE_UPDATE_TO_NEW_VERSION, IS_INSUFFICIENT_COUNTERPARTY_TRADE_LIMIT, - IS_MY_INSUFFICIENT_TRADE_LIMIT; + IS_MY_INSUFFICIENT_TRADE_LIMIT, + HIDE_BSQ_SWAPS_DUE_DAO_DEACTIVATED; @Getter private final boolean isValid; @@ -128,6 +130,9 @@ public class OfferFilter { if (isMyInsufficientTradeLimit(offer)) { return Result.IS_MY_INSUFFICIENT_TRADE_LIMIT; } + if (!DevEnv.isDaoActivated() && offer.isBsqSwapOffer()) { + return Result.HIDE_BSQ_SWAPS_DUE_DAO_DEACTIVATED; + } return Result.VALID; } diff --git a/core/src/main/java/bisq/core/offer/OfferForJson.java b/core/src/main/java/bisq/core/offer/OfferForJson.java index afac82ae4e..1ac2b658b1 100644 --- a/core/src/main/java/bisq/core/offer/OfferForJson.java +++ b/core/src/main/java/bisq/core/offer/OfferForJson.java @@ -40,7 +40,7 @@ import javax.annotation.Nullable; public class OfferForJson { private static final Logger log = LoggerFactory.getLogger(OfferForJson.class); - public final OfferPayload.Direction direction; + public final OfferDirection direction; public final String currencyCode; public final long minAmount; public final long amount; @@ -53,7 +53,7 @@ public class OfferForJson { // primaryMarket fields are based on industry standard where primaryMarket is always in the focus (in the app BTC is always in the focus - will be changed in a larger refactoring once) public String currencyPair; - public OfferPayload.Direction primaryMarketDirection; + public OfferDirection primaryMarketDirection; public String priceDisplayString; public String primaryMarketAmountDisplayString; @@ -75,7 +75,7 @@ public class OfferForJson { transient private final MonetaryFormat coinFormat = MonetaryFormat.BTC; - public OfferForJson(OfferPayload.Direction direction, + public OfferForJson(OfferDirection direction, String currencyCode, Coin minAmount, Coin amount, @@ -104,7 +104,7 @@ public class OfferForJson { try { final Price price = getPrice(); if (CurrencyUtil.isCryptoCurrency(currencyCode)) { - primaryMarketDirection = direction == OfferPayload.Direction.BUY ? OfferPayload.Direction.SELL : OfferPayload.Direction.BUY; + primaryMarketDirection = direction == OfferDirection.BUY ? OfferDirection.SELL : OfferDirection.BUY; currencyPair = currencyCode + "/" + Res.getBaseCurrencyCode(); // int precision = 8; diff --git a/core/src/main/java/bisq/core/offer/OfferPayloadBase.java b/core/src/main/java/bisq/core/offer/OfferPayloadBase.java new file mode 100644 index 0000000000..4ce2c8304f --- /dev/null +++ b/core/src/main/java/bisq/core/offer/OfferPayloadBase.java @@ -0,0 +1,147 @@ +/* + * 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.core.offer; + +import bisq.network.p2p.NodeAddress; +import bisq.network.p2p.storage.payload.ExpirablePayload; +import bisq.network.p2p.storage.payload.ProtectedStoragePayload; +import bisq.network.p2p.storage.payload.RequiresOwnerIsOnlinePayload; + +import bisq.common.crypto.Hash; +import bisq.common.crypto.PubKeyRing; +import bisq.common.util.Hex; +import bisq.common.util.JsonExclude; + +import java.security.PublicKey; + +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import lombok.EqualsAndHashCode; +import lombok.Getter; + +import javax.annotation.Nullable; + +@EqualsAndHashCode(exclude = {"hash"}) +@Getter +public abstract class OfferPayloadBase implements ProtectedStoragePayload, ExpirablePayload, RequiresOwnerIsOnlinePayload { + public static final long TTL = TimeUnit.MINUTES.toMillis(9); + + protected final String id; + protected final long date; + // For fiat offer the baseCurrencyCode is BTC and the counterCurrencyCode is the fiat currency + // For altcoin offers it is the opposite. baseCurrencyCode is the altcoin and the counterCurrencyCode is BTC. + protected final String baseCurrencyCode; + protected final String counterCurrencyCode; + // price if fixed price is used (usePercentageBasedPrice = false), otherwise 0 + protected final long price; + protected final long amount; + protected final long minAmount; + protected final String paymentMethodId; + protected final String makerPaymentAccountId; + protected final NodeAddress ownerNodeAddress; + protected final OfferDirection direction; + protected final String versionNr; + protected final int protocolVersion; + @JsonExclude + protected final PubKeyRing pubKeyRing; + // cache + protected transient byte[] hash; + @Nullable + protected final Map extraDataMap; + + public OfferPayloadBase(String id, + long date, + NodeAddress ownerNodeAddress, + PubKeyRing pubKeyRing, + String baseCurrencyCode, + String counterCurrencyCode, + OfferDirection direction, + long price, + long amount, + long minAmount, + String paymentMethodId, + String makerPaymentAccountId, + @Nullable Map extraDataMap, + String versionNr, + int protocolVersion) { + this.id = id; + this.date = date; + this.ownerNodeAddress = ownerNodeAddress; + this.pubKeyRing = pubKeyRing; + this.baseCurrencyCode = baseCurrencyCode; + this.counterCurrencyCode = counterCurrencyCode; + this.direction = direction; + this.price = price; + this.amount = amount; + this.minAmount = minAmount; + this.paymentMethodId = paymentMethodId; + this.makerPaymentAccountId = makerPaymentAccountId; + this.extraDataMap = extraDataMap; + this.versionNr = versionNr; + this.protocolVersion = protocolVersion; + } + + public byte[] getHash() { + if (this.hash == null) { + this.hash = Hash.getSha256Hash(this.toProtoMessage().toByteArray()); + } + return this.hash; + } + + @Override + public PublicKey getOwnerPubKey() { + return pubKeyRing.getSignaturePubKey(); + } + + // In the offer we support base and counter currency + // Fiat offers have base currency BTC and counterCurrency Fiat + // Altcoins have base currency Altcoin and counterCurrency BTC + // The rest of the app does not support yet that concept of base currency and counter currencies + // so we map here for convenience + public String getCurrencyCode() { + return getBaseCurrencyCode().equals("BTC") ? getCounterCurrencyCode() : getBaseCurrencyCode(); + } + + @Override + public long getTTL() { + return TTL; + } + + @Override + public String toString() { + return "OfferPayloadBase{" + + "\r\n id='" + id + '\'' + + ",\r\n date=" + date + + ",\r\n baseCurrencyCode='" + baseCurrencyCode + '\'' + + ",\r\n counterCurrencyCode='" + counterCurrencyCode + '\'' + + ",\r\n price=" + price + + ",\r\n amount=" + amount + + ",\r\n minAmount=" + minAmount + + ",\r\n paymentMethodId='" + paymentMethodId + '\'' + + ",\r\n makerPaymentAccountId='" + makerPaymentAccountId + '\'' + + ",\r\n ownerNodeAddress=" + ownerNodeAddress + + ",\r\n direction=" + direction + + ",\r\n versionNr='" + versionNr + '\'' + + ",\r\n protocolVersion=" + protocolVersion + + ",\r\n pubKeyRing=" + pubKeyRing + + ",\r\n hash=" + (hash != null ? Hex.encode(hash) : "null") + + ",\r\n extraDataMap=" + extraDataMap + + "\r\n}"; + } +} diff --git a/core/src/main/java/bisq/core/offer/OfferRestrictions.java b/core/src/main/java/bisq/core/offer/OfferRestrictions.java index eec00333d3..04e824c715 100644 --- a/core/src/main/java/bisq/core/offer/OfferRestrictions.java +++ b/core/src/main/java/bisq/core/offer/OfferRestrictions.java @@ -17,6 +17,8 @@ package bisq.core.offer; +import bisq.core.offer.bisq_v1.OfferPayload; + import bisq.common.app.Capabilities; import bisq.common.app.Capability; import bisq.common.config.Config; @@ -40,7 +42,7 @@ public class OfferRestrictions { public static Coin TOLERATED_SMALL_TRADE_AMOUNT = Coin.parseCoin("0.01"); static boolean hasOfferMandatoryCapability(Offer offer, Capability mandatoryCapability) { - Map extraDataMap = offer.getOfferPayload().getExtraDataMap(); + Map extraDataMap = offer.getExtraDataMap(); if (extraDataMap != null && extraDataMap.containsKey(OfferPayload.CAPABILITIES)) { String commaSeparatedOrdinals = extraDataMap.get(OfferPayload.CAPABILITIES); Capabilities capabilities = Capabilities.fromStringList(commaSeparatedOrdinals); diff --git a/core/src/main/java/bisq/core/offer/OfferUtil.java b/core/src/main/java/bisq/core/offer/OfferUtil.java index fb76a69c89..c676c57bbd 100644 --- a/core/src/main/java/bisq/core/offer/OfferUtil.java +++ b/core/src/main/java/bisq/core/offer/OfferUtil.java @@ -25,9 +25,12 @@ import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; import bisq.core.monetary.Price; import bisq.core.monetary.Volume; +import bisq.core.offer.bisq_v1.MutableOfferPayloadFields; +import bisq.core.offer.bisq_v1.OfferPayload; import bisq.core.payment.CashByMailAccount; import bisq.core.payment.F2FAccount; import bisq.core.payment.PaymentAccount; +import bisq.core.payment.payload.PaymentMethod; import bisq.core.provider.fee.FeeService; import bisq.core.provider.price.MarketPrice; import bisq.core.provider.price.PriceFeedService; @@ -42,8 +45,10 @@ import bisq.core.util.coin.CoinUtil; import bisq.network.p2p.P2PService; import bisq.common.app.Capabilities; +import bisq.common.app.Version; import bisq.common.util.MathUtils; import bisq.common.util.Tuple2; +import bisq.common.util.Utilities; import org.bitcoinj.core.Coin; import org.bitcoinj.core.Transaction; @@ -57,6 +62,7 @@ import javax.inject.Singleton; import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.UUID; import java.util.function.Predicate; import lombok.extern.slf4j.Slf4j; @@ -69,7 +75,7 @@ import static bisq.core.btc.wallet.Restrictions.getMaxBuyerSecurityDepositAsPerc import static bisq.core.btc.wallet.Restrictions.getMinBuyerSecurityDepositAsPercent; import static bisq.core.btc.wallet.Restrictions.getMinNonDustOutput; import static bisq.core.btc.wallet.Restrictions.isDust; -import static bisq.core.offer.OfferPayload.*; +import static bisq.core.offer.bisq_v1.OfferPayload.*; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static java.lang.String.format; @@ -112,6 +118,40 @@ public class OfferUtil { this.tradeStatisticsManager = tradeStatisticsManager; } + public static String getRandomOfferId() { + return Utilities.getRandomPrefix(5, 8) + "-" + + UUID.randomUUID() + "-" + + getStrippedVersion(); + } + + public static String getStrippedVersion() { + return Version.VERSION.replace(".", ""); + } + + // We add a counter at the end of the offer id signalling the number of times that offer has + // been mutated ether due edit or due pow adjustments. + public static String getOfferIdWithMutationCounter(String id) { + String[] split = id.split("-"); + String base = id; + int counter = 0; + if (split.length > 7) { + String counterString = split[7]; + int endIndex = id.length() - counterString.length() - 1; + base = id.substring(0, endIndex); + try { + counter = Integer.parseInt(counterString); + } catch (Exception ignore) { + } + } + counter++; + return base + "-" + counter; + } + + public static String getVersionFromId(String id) { + String[] split = id.split("-"); + return split[6]; + } + public void maybeSetFeePaymentCurrencyPreference(String feeCurrencyCode) { if (!feeCurrencyCode.isEmpty()) { if (!isValidFeePaymentCurrencyCode.test(feeCurrencyCode)) @@ -132,13 +172,13 @@ public class OfferUtil { * @return {@code true} for an offer to buy BTC from the taker, {@code false} for an * offer to sell BTC to the taker */ - public boolean isBuyOffer(Direction direction) { - return direction == Direction.BUY; + public boolean isBuyOffer(OfferDirection direction) { + return direction == OfferDirection.BUY; } public long getMaxTradeLimit(PaymentAccount paymentAccount, String currencyCode, - Direction direction) { + OfferDirection direction) { return paymentAccount != null ? accountAgeWitnessService.getMyTradeLimit(paymentAccount, currencyCode, direction) : 0; @@ -181,7 +221,7 @@ public class OfferUtil { // We have to keep a minimum amount of BSQ == bitcoin dust limit, otherwise there // would be dust violations for change UTXOs; essentially means the minimum usable // balance of BSQ is 5.46. - Coin usableBsqBalance = bsqWalletService.getAvailableConfirmedBalance().subtract(getMinNonDustOutput()); + Coin usableBsqBalance = bsqWalletService.getAvailableBalance().subtract(getMinNonDustOutput()); return usableBsqBalance.isNegative() ? Coin.ZERO : usableBsqBalance; } @@ -240,7 +280,7 @@ public class OfferUtil { * @return {@code true} if the balance is sufficient, {@code false} otherwise */ public boolean isBsqForMakerFeeAvailable(@Nullable Coin amount) { - Coin availableBalance = bsqWalletService.getAvailableConfirmedBalance(); + Coin availableBalance = bsqWalletService.getAvailableBalance(); Coin makerFee = CoinUtil.getMakerFee(false, amount); // If we don't know yet the maker fee (amount is not set) we return true, @@ -274,7 +314,7 @@ public class OfferUtil { } public boolean isBsqForTakerFeeAvailable(@Nullable Coin amount) { - Coin availableBalance = bsqWalletService.getAvailableConfirmedBalance(); + Coin availableBalance = bsqWalletService.getAvailableBalance(); Coin takerFee = getTakerFee(false, amount); // If we don't know yet the maker fee (amount is not set) we return true, @@ -291,7 +331,7 @@ public class OfferUtil { } public boolean isBlockChainPaymentMethod(Offer offer) { - return offer != null && offer.getPaymentMethod().isAsset(); + return offer != null && offer.getPaymentMethod().isBlockchain(); } public Optional getFeeInUserFiatCurrency(Coin makerFee, @@ -313,7 +353,7 @@ public class OfferUtil { public Map getExtraDataMap(PaymentAccount paymentAccount, String currencyCode, - Direction direction) { + OfferDirection direction) { Map extraDataMap = new HashMap<>(); if (CurrencyUtil.isFiatCurrency(currencyCode)) { String myWitnessHashAsHex = accountAgeWitnessService @@ -336,7 +376,7 @@ public class OfferUtil { extraDataMap.put(CAPABILITIES, Capabilities.app.toStringList()); - if (currencyCode.equals("XMR") && direction == Direction.SELL) { + if (currencyCode.equals("XMR") && direction == OfferDirection.SELL) { preferences.getAutoConfirmSettingsList().stream() .filter(e -> e.getCurrencyCode().equals("XMR")) .filter(AutoConfirmSettings::isEnabled) @@ -350,17 +390,21 @@ public class OfferUtil { PaymentAccount paymentAccount, String currencyCode, Coin makerFeeAsCoin) { + validateBasicOfferData(paymentAccount.getPaymentMethod(), currencyCode); checkNotNull(makerFeeAsCoin, "makerFee must not be null"); - checkNotNull(p2PService.getAddress(), "Address must not be null"); checkArgument(buyerSecurityDeposit <= getMaxBuyerSecurityDepositAsPercent(), "securityDeposit must not exceed " + getMaxBuyerSecurityDepositAsPercent()); checkArgument(buyerSecurityDeposit >= getMinBuyerSecurityDepositAsPercent(), "securityDeposit must not be less than " + getMinBuyerSecurityDepositAsPercent()); + } + + public void validateBasicOfferData(PaymentMethod paymentMethod, String currencyCode) { + checkNotNull(p2PService.getAddress(), "Address must not be null"); checkArgument(!filterManager.isCurrencyBanned(currencyCode), Res.get("offerbook.warning.currencyBanned")); - checkArgument(!filterManager.isPaymentMethodBanned(paymentAccount.getPaymentMethod()), + checkArgument(!filterManager.isPaymentMethodBanned(paymentMethod), Res.get("offerbook.warning.paymentMethodBanned")); } @@ -370,45 +414,45 @@ public class OfferUtil { // Immutable fields are sourced from the original openOffer param. public OfferPayload getMergedOfferPayload(OpenOffer openOffer, MutableOfferPayloadFields mutableOfferPayloadFields) { - OfferPayload originalOfferPayload = openOffer.getOffer().getOfferPayload(); - return new OfferPayload(originalOfferPayload.getId(), - originalOfferPayload.getDate(), - originalOfferPayload.getOwnerNodeAddress(), - originalOfferPayload.getPubKeyRing(), - originalOfferPayload.getDirection(), + OfferPayload original = openOffer.getOffer().getOfferPayload().orElseThrow(); + return new OfferPayload(original.getId(), + original.getDate(), + original.getOwnerNodeAddress(), + original.getPubKeyRing(), + original.getDirection(), mutableOfferPayloadFields.getPrice(), mutableOfferPayloadFields.getMarketPriceMargin(), mutableOfferPayloadFields.isUseMarketBasedPrice(), - originalOfferPayload.getAmount(), - originalOfferPayload.getMinAmount(), + original.getAmount(), + original.getMinAmount(), mutableOfferPayloadFields.getBaseCurrencyCode(), mutableOfferPayloadFields.getCounterCurrencyCode(), - originalOfferPayload.getArbitratorNodeAddresses(), - originalOfferPayload.getMediatorNodeAddresses(), + original.getArbitratorNodeAddresses(), + original.getMediatorNodeAddresses(), mutableOfferPayloadFields.getPaymentMethodId(), mutableOfferPayloadFields.getMakerPaymentAccountId(), - originalOfferPayload.getOfferFeePaymentTxId(), + original.getOfferFeePaymentTxId(), mutableOfferPayloadFields.getCountryCode(), mutableOfferPayloadFields.getAcceptedCountryCodes(), mutableOfferPayloadFields.getBankId(), mutableOfferPayloadFields.getAcceptedBankIds(), - originalOfferPayload.getVersionNr(), - originalOfferPayload.getBlockHeightAtOfferCreation(), - originalOfferPayload.getTxFee(), - originalOfferPayload.getMakerFee(), - originalOfferPayload.isCurrencyForMakerFeeBtc(), - originalOfferPayload.getBuyerSecurityDeposit(), - originalOfferPayload.getSellerSecurityDeposit(), - originalOfferPayload.getMaxTradeLimit(), - originalOfferPayload.getMaxTradePeriod(), - originalOfferPayload.isUseAutoClose(), - originalOfferPayload.isUseReOpenAfterAutoClose(), - originalOfferPayload.getLowerClosePrice(), - originalOfferPayload.getUpperClosePrice(), - originalOfferPayload.isPrivateOffer(), - originalOfferPayload.getHashOfChallenge(), + original.getVersionNr(), + original.getBlockHeightAtOfferCreation(), + original.getTxFee(), + original.getMakerFee(), + original.isCurrencyForMakerFeeBtc(), + original.getBuyerSecurityDeposit(), + original.getSellerSecurityDeposit(), + original.getMaxTradeLimit(), + original.getMaxTradePeriod(), + original.isUseAutoClose(), + original.isUseReOpenAfterAutoClose(), + original.getLowerClosePrice(), + original.getUpperClosePrice(), + original.isPrivateOffer(), + original.getHashOfChallenge(), mutableOfferPayloadFields.getExtraDataMap(), - originalOfferPayload.getProtocolVersion()); + original.getProtocolVersion()); } private Optional getFeeInUserFiatCurrency(Coin makerFee, @@ -482,7 +526,7 @@ public class OfferUtil { } } else { errorMsg = "The maker fee tx is invalid as it does not has at least 2 outputs." + extraString + - "\nMakerFeeTx=" + makerFeeTx.toString(); + "\nMakerFeeTx=" + makerFeeTx; } if (errorMsg == null) { diff --git a/core/src/main/java/bisq/core/offer/OpenOffer.java b/core/src/main/java/bisq/core/offer/OpenOffer.java index 4c2d06c308..3e47b7e1ca 100644 --- a/core/src/main/java/bisq/core/offer/OpenOffer.java +++ b/core/src/main/java/bisq/core/offer/OpenOffer.java @@ -17,7 +17,8 @@ package bisq.core.offer; -import bisq.core.trade.Tradable; +import bisq.core.offer.bsq_swap.BsqSwapOfferPayload; +import bisq.core.trade.model.Tradable; import bisq.network.p2p.NodeAddress; @@ -35,12 +36,13 @@ import lombok.extern.slf4j.Slf4j; import javax.annotation.Nullable; -@EqualsAndHashCode +import static com.google.common.base.Preconditions.checkArgument; + +@EqualsAndHashCode(exclude = {"bsqSwapOfferHasMissingFunds", "state", "timeoutTimer", "mempoolStatus"}) @Slf4j public final class OpenOffer implements Tradable { // Timeout for offer reservation during takeoffer process. If deposit tx is not completed in that time we reset the offer to AVAILABLE state. private static final long TIMEOUT = 60; - transient private Timer timeoutTimer; public enum State { AVAILABLE, @@ -54,10 +56,11 @@ public final class OpenOffer implements Tradable { private final Offer offer; @Getter private State state; + + // TODO Not used. Could be removed? @Getter - @Setter @Nullable - private NodeAddress arbitratorNodeAddress; + private final NodeAddress arbitratorNodeAddress; @Getter @Setter @Nullable @@ -76,15 +79,29 @@ public final class OpenOffer implements Tradable { @Getter @Setter transient private long mempoolStatus = -1; + transient private Timer timeoutTimer; + + // Added at BsqSwap release. We do not persist that field + @Getter + @Setter + transient private boolean bsqSwapOfferHasMissingFunds; public OpenOffer(Offer offer) { this(offer, 0); } + public OpenOffer(Offer offer, State state) { + this.offer = offer; + this.triggerPrice = 0; + this.state = state; + arbitratorNodeAddress = null; + } + public OpenOffer(Offer offer, long triggerPrice) { this.offer = offer; this.triggerPrice = triggerPrice; state = State.AVAILABLE; + arbitratorNodeAddress = null; } /////////////////////////////////////////////////////////////////////////////////////////// @@ -131,9 +148,8 @@ public final class OpenOffer implements Tradable { proto.getTriggerPrice()); } - /////////////////////////////////////////////////////////////////////////////////////////// - // Getters + // Tradable /////////////////////////////////////////////////////////////////////////////////////////// @Override @@ -151,6 +167,11 @@ public final class OpenOffer implements Tradable { return offer.getShortId(); } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Misc + /////////////////////////////////////////////////////////////////////////////////////////// + public void setState(State state) { this.state = state; @@ -166,6 +187,12 @@ public final class OpenOffer implements Tradable { return state == State.DEACTIVATED; } + public BsqSwapOfferPayload getBsqSwapOfferPayload() { + checkArgument(getOffer().getBsqSwapOfferPayload().isPresent(), + "getBsqSwapOfferPayload must be called only when BsqSwapOfferPayload is the expected payload"); + return getOffer().getBsqSwapOfferPayload().get(); + } + private void startTimeout() { stopTimeout(); @@ -185,7 +212,6 @@ public final class OpenOffer implements Tradable { } } - @Override public String toString() { return "OpenOffer{" + @@ -195,6 +221,7 @@ public final class OpenOffer implements Tradable { ",\n mediatorNodeAddress=" + mediatorNodeAddress + ",\n refundAgentNodeAddress=" + refundAgentNodeAddress + ",\n triggerPrice=" + triggerPrice + + ",\n bsqSwapOfferHasMissingFunds=" + bsqSwapOfferHasMissingFunds + "\n}"; } } diff --git a/core/src/main/java/bisq/core/offer/OpenOfferManager.java b/core/src/main/java/bisq/core/offer/OpenOfferManager.java index 7c8ddb2c65..2aab258b9c 100644 --- a/core/src/main/java/bisq/core/offer/OpenOfferManager.java +++ b/core/src/main/java/bisq/core/offer/OpenOfferManager.java @@ -25,18 +25,22 @@ import bisq.core.dao.DaoFacade; import bisq.core.exceptions.TradePriceOutOfToleranceException; import bisq.core.filter.FilterManager; import bisq.core.locale.Res; +import bisq.core.offer.availability.AvailabilityResult; import bisq.core.offer.availability.DisputeAgentSelection; -import bisq.core.offer.messages.OfferAvailabilityRequest; -import bisq.core.offer.messages.OfferAvailabilityResponse; -import bisq.core.offer.placeoffer.PlaceOfferModel; -import bisq.core.offer.placeoffer.PlaceOfferProtocol; +import bisq.core.offer.availability.messages.OfferAvailabilityRequest; +import bisq.core.offer.availability.messages.OfferAvailabilityResponse; +import bisq.core.offer.bisq_v1.CreateOfferService; +import bisq.core.offer.bisq_v1.MarketPriceNotAvailableException; +import bisq.core.offer.bisq_v1.OfferPayload; +import bisq.core.offer.placeoffer.bisq_v1.PlaceOfferModel; +import bisq.core.offer.placeoffer.bisq_v1.PlaceOfferProtocol; import bisq.core.provider.price.PriceFeedService; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import bisq.core.support.dispute.mediation.mediator.MediatorManager; import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; -import bisq.core.trade.TradableList; -import bisq.core.trade.closed.ClosedTradableManager; -import bisq.core.trade.handlers.TransactionResultHandler; +import bisq.core.trade.ClosedTradableManager; +import bisq.core.trade.bisq_v1.TransactionResultHandler; +import bisq.core.trade.model.TradableList; import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.user.Preferences; import bisq.core.user.User; @@ -84,19 +88,16 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import lombok.Getter; - -import org.jetbrains.annotations.NotNull; +import lombok.extern.slf4j.Slf4j; import javax.annotation.Nullable; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +@Slf4j public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMessageListener, PersistedDataHost { - private static final Logger log = LoggerFactory.getLogger(OpenOfferManager.class); private static final long RETRY_REPUBLISH_DELAY_SEC = 10; private static final long REPUBLISH_AGAIN_AT_STARTUP_DELAY_SEC = 30; @@ -207,8 +208,9 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe cleanUpAddressEntries(); openOffers.stream() - .forEach(openOffer -> OfferUtil.getInvalidMakerFeeTxErrorMessage(openOffer.getOffer(), btcWalletService) - .ifPresent(errorMsg -> invalidOffers.add(new Tuple2<>(openOffer, errorMsg)))); + .forEach(openOffer -> + OfferUtil.getInvalidMakerFeeTxErrorMessage(openOffer.getOffer(), btcWalletService) + .ifPresent(errorMsg -> invalidOffers.add(new Tuple2<>(openOffer, errorMsg)))); } private void cleanUpAddressEntries() { @@ -238,7 +240,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe log.info("Remove open offers at shutDown. Number of open offers: {}", size); if (offerBookService.isBootstrapped() && size > 0) { UserThread.execute(() -> openOffers.forEach( - openOffer -> offerBookService.removeOfferAtShutDown(openOffer.getOffer().getOfferPayload()) + openOffer -> offerBookService.removeOfferAtShutDown(openOffer.getOffer().getOfferPayloadBase()) )); // Force broadcaster to send out immediately, otherwise we could have a 2 sec delay until the @@ -265,11 +267,9 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe int size = openOffers.size(); // Copy list as we remove in the loop List openOffersList = new ArrayList<>(openOffers); - openOffersList.forEach(openOffer -> removeOpenOffer(openOffer, () -> { - }, errorMessage -> { - })); + openOffersList.forEach(this::removeOpenOffer); if (completeHandler != null) - UserThread.runAfter(completeHandler, size * 200 + 500, TimeUnit.MILLISECONDS); + UserThread.runAfter(completeHandler, size * 200L + 500, TimeUnit.MILLISECONDS); } @@ -372,6 +372,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { checkNotNull(offer.getMakerFee(), "makerFee must not be null"); + checkArgument(!offer.isBsqSwapOffer()); Coin reservedFundsForOffer = createOfferService.getReservedFundsForOffer(offer.getDirection(), offer.getAmount(), @@ -394,21 +395,30 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe model, transaction -> { OpenOffer openOffer = new OpenOffer(offer, triggerPrice); - openOffers.add(openOffer); - requestPersistence(); - resultHandler.handleResult(transaction); + addOpenOfferToList(openOffer); if (!stopped) { startPeriodicRepublishOffersTimer(); startPeriodicRefreshOffersTimer(); } else { log.debug("We have stopped already. We ignore that placeOfferProtocol.placeOffer.onResult call."); } + resultHandler.handleResult(transaction); }, errorMessageHandler ); placeOfferProtocol.placeOffer(); } + public void addOpenBsqSwapOffer(OpenOffer openOffer) { + addOpenOfferToList(openOffer); + if (!stopped) { + startPeriodicRepublishOffersTimer(); + startPeriodicRefreshOffersTimer(); + } else { + log.debug("We have stopped already. We ignore that placeOfferProtocol.placeOffer.onResult call."); + } + } + // Remove from offerbook public void removeOffer(Offer offer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { Optional openOfferOptional = getOpenOfferById(offer.getId()); @@ -418,7 +428,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe log.warn("Offer was not found in our list of open offers. We still try to remove it from the offerbook."); errorMessageHandler.handleErrorMessage("Offer was not found in our list of open offers. " + "We still try to remove it from the offerbook."); - offerBookService.removeOffer(offer.getOfferPayload(), + offerBookService.removeOffer(offer.getOfferPayloadBase(), () -> offer.setState(Offer.State.REMOVED), null); } @@ -427,26 +437,36 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe public void activateOpenOffer(OpenOffer openOffer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { - if (!offersToBeEdited.containsKey(openOffer.getId())) { - Offer offer = openOffer.getOffer(); - offerBookService.activateOffer(offer, - () -> { - openOffer.setState(OpenOffer.State.AVAILABLE); - requestPersistence(); - log.debug("activateOpenOffer, offerId={}", offer.getId()); - resultHandler.handleResult(); - }, - errorMessageHandler); - } else { + if (offersToBeEdited.containsKey(openOffer.getId())) { errorMessageHandler.handleErrorMessage("You can't activate an offer that is currently edited."); + return; } + + // If there is not enough funds for a BsqSwapOffer we do not publish the offer, but still apply the state change. + // Once the wallet gets funded the offer gets published automatically. + if (isBsqSwapOfferLackingFunds(openOffer)) { + openOffer.setState(OpenOffer.State.AVAILABLE); + requestPersistence(); + resultHandler.handleResult(); + return; + } + + Offer offer = openOffer.getOffer(); + offerBookService.activateOffer(offer, + () -> { + openOffer.setState(OpenOffer.State.AVAILABLE); + requestPersistence(); + log.debug("activateOpenOffer, offerId={}", offer.getId()); + resultHandler.handleResult(); + }, + errorMessageHandler); } public void deactivateOpenOffer(OpenOffer openOffer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { Offer offer = openOffer.getOffer(); - offerBookService.deactivateOffer(offer.getOfferPayload(), + offerBookService.deactivateOffer(offer.getOfferPayloadBase(), () -> { openOffer.setState(OpenOffer.State.DEACTIVATED); requestPersistence(); @@ -456,6 +476,12 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe errorMessageHandler); } + public void removeOpenOffer(OpenOffer openOffer) { + removeOpenOffer(openOffer, () -> { + }, error -> { + }); + } + public void removeOpenOffer(OpenOffer openOffer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { @@ -464,7 +490,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe if (openOffer.isDeactivated()) { onRemoved(openOffer, resultHandler, offer); } else { - offerBookService.removeOffer(offer.getOfferPayload(), + offerBookService.removeOffer(offer.getOfferPayloadBase(), () -> onRemoved(openOffer, resultHandler, offer), errorMessageHandler); } @@ -508,18 +534,17 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe openOffer.getOffer().setState(Offer.State.REMOVED); openOffer.setState(OpenOffer.State.CANCELED); - openOffers.remove(openOffer); + removeOpenOfferFromList(openOffer); OpenOffer editedOpenOffer = new OpenOffer(editedOffer, triggerPrice); editedOpenOffer.setState(originalState); - openOffers.add(editedOpenOffer); + addOpenOfferToList(editedOpenOffer); if (!editedOpenOffer.isDeactivated()) - republishOffer(editedOpenOffer); + maybeRepublishOffer(editedOpenOffer); offersToBeEdited.remove(openOffer.getId()); - requestPersistence(); resultHandler.handleResult(); } else { errorMessageHandler.handleErrorMessage("There is no offer with this id existing to be published."); @@ -542,26 +567,26 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe } } - private void onRemoved(@NotNull OpenOffer openOffer, ResultHandler resultHandler, Offer offer) { + private void onRemoved(OpenOffer openOffer, ResultHandler resultHandler, Offer offer) { offer.setState(Offer.State.REMOVED); openOffer.setState(OpenOffer.State.CANCELED); - openOffers.remove(openOffer); - closedTradableManager.add(openOffer); + removeOpenOfferFromList(openOffer); + if (!openOffer.getOffer().isBsqSwapOffer()) { + closedTradableManager.add(openOffer); + btcWalletService.resetAddressEntriesForOpenOffer(offer.getId()); + } log.info("onRemoved offerId={}", offer.getId()); - btcWalletService.resetAddressEntriesForOpenOffer(offer.getId()); - requestPersistence(); resultHandler.handleResult(); } // Close openOffer after deposit published public void closeOpenOffer(Offer offer) { getOpenOfferById(offer.getId()).ifPresent(openOffer -> { - openOffers.remove(openOffer); + removeOpenOfferFromList(openOffer); openOffer.setState(OpenOffer.State.CLOSED); - offerBookService.removeOffer(openOffer.getOffer().getOfferPayload(), + offerBookService.removeOffer(openOffer.getOffer().getOfferPayloadBase(), () -> log.trace("Successful removed offer"), log::error); - requestPersistence(); }); } @@ -632,7 +657,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe Validator.nonEmptyStringOf(request.offerId); checkNotNull(request.getPubKeyRing()); } catch (Throwable t) { - errorMessage = "Message validation failed. Error=" + t.toString() + ", Message=" + request.toString(); + errorMessage = "Message validation failed. Error=" + t + ", Message=" + request; log.warn(errorMessage); sendAckMessage(request, peer, false, errorMessage); return; @@ -787,23 +812,27 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe ArrayList openOffersClone = new ArrayList<>(openOffers.getList()); openOffersClone.forEach(originalOpenOffer -> { Offer originalOffer = originalOpenOffer.getOffer(); - - OfferPayload originalOfferPayload = originalOffer.getOfferPayload(); + if (originalOffer.isBsqSwapOffer()) { + // Offer without a fee transaction don't need to be updated, they can be removed and a new + // offer created without incurring any extra costs + return; + } + OfferPayload original = originalOffer.getOfferPayload().orElseThrow(); // We added CAPABILITIES with entry for Capability.MEDIATION in v1.1.6 and // Capability.REFUND_AGENT in v1.2.0 and want to rewrite a // persisted offer after the user has updated to 1.2.0 so their offer will be accepted by the network. - if (originalOfferPayload.getProtocolVersion() < Version.TRADE_PROTOCOL_VERSION || + if (original.getProtocolVersion() < Version.TRADE_PROTOCOL_VERSION || !OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.MEDIATION) || !OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.REFUND_AGENT) || - !originalOfferPayload.getOwnerNodeAddress().equals(p2PService.getAddress())) { + !original.getOwnerNodeAddress().equals(p2PService.getAddress())) { // - Capabilities changed? // We rewrite our offer with the additional capabilities entry Map updatedExtraDataMap = new HashMap<>(); if (!OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.MEDIATION) || !OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.REFUND_AGENT)) { - Map originalExtraDataMap = originalOfferPayload.getExtraDataMap(); + Map originalExtraDataMap = original.getExtraDataMap(); if (originalExtraDataMap != null) { updatedExtraDataMap.putAll(originalExtraDataMap); @@ -814,11 +843,11 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe log.info("Converted offer to support new Capability.MEDIATION and Capability.REFUND_AGENT capability. id={}", originalOffer.getId()); } else { - updatedExtraDataMap = originalOfferPayload.getExtraDataMap(); + updatedExtraDataMap = original.getExtraDataMap(); } // - Protocol version changed? - int protocolVersion = originalOfferPayload.getProtocolVersion(); + int protocolVersion = original.getProtocolVersion(); if (protocolVersion < Version.TRADE_PROTOCOL_VERSION) { // We update the trade protocol version protocolVersion = Version.TRADE_PROTOCOL_VERSION; @@ -826,48 +855,48 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe } // - node address changed? (due to a faulty tor dir) - NodeAddress ownerNodeAddress = originalOfferPayload.getOwnerNodeAddress(); + NodeAddress ownerNodeAddress = original.getOwnerNodeAddress(); if (!ownerNodeAddress.equals(p2PService.getAddress())) { ownerNodeAddress = p2PService.getAddress(); log.info("Updated the owner nodeaddress of offer id={}", originalOffer.getId()); } - OfferPayload updatedPayload = new OfferPayload(originalOfferPayload.getId(), - originalOfferPayload.getDate(), + OfferPayload updatedPayload = new OfferPayload(original.getId(), + original.getDate(), ownerNodeAddress, - originalOfferPayload.getPubKeyRing(), - originalOfferPayload.getDirection(), - originalOfferPayload.getPrice(), - originalOfferPayload.getMarketPriceMargin(), - originalOfferPayload.isUseMarketBasedPrice(), - originalOfferPayload.getAmount(), - originalOfferPayload.getMinAmount(), - originalOfferPayload.getBaseCurrencyCode(), - originalOfferPayload.getCounterCurrencyCode(), - originalOfferPayload.getArbitratorNodeAddresses(), - originalOfferPayload.getMediatorNodeAddresses(), - originalOfferPayload.getPaymentMethodId(), - originalOfferPayload.getMakerPaymentAccountId(), - originalOfferPayload.getOfferFeePaymentTxId(), - originalOfferPayload.getCountryCode(), - originalOfferPayload.getAcceptedCountryCodes(), - originalOfferPayload.getBankId(), - originalOfferPayload.getAcceptedBankIds(), - originalOfferPayload.getVersionNr(), - originalOfferPayload.getBlockHeightAtOfferCreation(), - originalOfferPayload.getTxFee(), - originalOfferPayload.getMakerFee(), - originalOfferPayload.isCurrencyForMakerFeeBtc(), - originalOfferPayload.getBuyerSecurityDeposit(), - originalOfferPayload.getSellerSecurityDeposit(), - originalOfferPayload.getMaxTradeLimit(), - originalOfferPayload.getMaxTradePeriod(), - originalOfferPayload.isUseAutoClose(), - originalOfferPayload.isUseReOpenAfterAutoClose(), - originalOfferPayload.getLowerClosePrice(), - originalOfferPayload.getUpperClosePrice(), - originalOfferPayload.isPrivateOffer(), - originalOfferPayload.getHashOfChallenge(), + original.getPubKeyRing(), + original.getDirection(), + original.getPrice(), + original.getMarketPriceMargin(), + original.isUseMarketBasedPrice(), + original.getAmount(), + original.getMinAmount(), + original.getBaseCurrencyCode(), + original.getCounterCurrencyCode(), + original.getArbitratorNodeAddresses(), + original.getMediatorNodeAddresses(), + original.getPaymentMethodId(), + original.getMakerPaymentAccountId(), + original.getOfferFeePaymentTxId(), + original.getCountryCode(), + original.getAcceptedCountryCodes(), + original.getBankId(), + original.getAcceptedBankIds(), + original.getVersionNr(), + original.getBlockHeightAtOfferCreation(), + original.getTxFee(), + original.getMakerFee(), + original.isCurrencyForMakerFeeBtc(), + original.getBuyerSecurityDeposit(), + original.getSellerSecurityDeposit(), + original.getMaxTradeLimit(), + original.getMaxTradePeriod(), + original.isUseAutoClose(), + original.isUseReOpenAfterAutoClose(), + original.getLowerClosePrice(), + original.getUpperClosePrice(), + original.isPrivateOffer(), + original.getHashOfChallenge(), updatedExtraDataMap, protocolVersion); @@ -878,7 +907,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe // remove old offer originalOffer.setState(Offer.State.REMOVED); originalOpenOffer.setState(OpenOffer.State.CANCELED); - openOffers.remove(originalOpenOffer); + removeOpenOfferFromList(originalOpenOffer); // Create new Offer Offer updatedOffer = new Offer(updatedPayload); @@ -887,8 +916,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe OpenOffer updatedOpenOffer = new OpenOffer(updatedOffer, originalOpenOffer.getTriggerPrice()); updatedOpenOffer.setState(originalOpenOfferState); - openOffers.add(updatedOpenOffer); - requestPersistence(); + addOpenOfferToList(updatedOpenOffer); log.info("Updating offer completed. id={}", originalOffer.getId()); } @@ -904,7 +932,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe if (stopped) { return; } - stopPeriodicRefreshOffersTimer(); List openOffersList = new ArrayList<>(openOffers.getList()); @@ -917,15 +944,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe } OpenOffer openOffer = list.remove(0); - if (openOffers.contains(openOffer) && !openOffer.isDeactivated()) { - // TODO It is not clear yet if it is better for the node and the network to send out all add offer - // messages in one go or to spread it over a delay. With power users who have 100-200 offers that can have - // some significant impact to user experience and the network - republishOffer(openOffer, () -> processListForRepublishOffers(list)); - - /* republishOffer(openOffer, - () -> UserThread.runAfter(() -> processListForRepublishOffers(list), - 30, TimeUnit.MILLISECONDS));*/ + if (openOffers.contains(openOffer)) { + maybeRepublishOffer(openOffer, () -> processListForRepublishOffers(list)); } else { // If the offer was removed in the meantime or if its deactivated we skip and call // processListForRepublishOffers again with the list where we removed the offer already. @@ -933,11 +953,18 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe } } - private void republishOffer(OpenOffer openOffer) { - republishOffer(openOffer, null); + public void maybeRepublishOffer(OpenOffer openOffer) { + maybeRepublishOffer(openOffer, null); } - private void republishOffer(OpenOffer openOffer, @Nullable Runnable completeHandler) { + private void maybeRepublishOffer(OpenOffer openOffer, @Nullable Runnable completeHandler) { + if (preventedFromPublishing(openOffer)) { + if (completeHandler != null) { + completeHandler.run(); + } + return; + } + offerBookService.addOffer(openOffer.getOffer(), () -> { if (!stopped) { @@ -996,8 +1023,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe final OpenOffer openOffer = openOffersList.get(i); UserThread.runAfterRandomDelay(() -> { // we need to check if in the meantime the offer has been removed - if (openOffers.contains(openOffer) && !openOffer.isDeactivated()) - refreshOffer(openOffer); + if (openOffers.contains(openOffer)) + maybeRefreshOffer(openOffer); }, minDelay, maxDelay, TimeUnit.MILLISECONDS); } } else { @@ -1010,8 +1037,11 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe log.trace("periodicRefreshOffersTimer already stated"); } - private void refreshOffer(OpenOffer openOffer) { - offerBookService.refreshTTL(openOffer.getOffer().getOfferPayload(), + private void maybeRefreshOffer(OpenOffer openOffer) { + if (preventedFromPublishing(openOffer)) { + return; + } + offerBookService.refreshTTL(openOffer.getOffer().getOfferPayloadBase(), () -> log.debug("Successful refreshed TTL for offer"), log::warn); } @@ -1028,7 +1058,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe startPeriodicRepublishOffersTimer(); } - private void requestPersistence() { + public void requestPersistence() { persistenceManager.requestPersistence(); } @@ -1057,4 +1087,23 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe retryRepublishOffersTimer = null; } } + + private void addOpenOfferToList(OpenOffer openOffer) { + openOffers.add(openOffer); + requestPersistence(); + } + + private void removeOpenOfferFromList(OpenOffer openOffer) { + openOffers.remove(openOffer); + requestPersistence(); + } + + private boolean isBsqSwapOfferLackingFunds(OpenOffer openOffer) { + return openOffer.getOffer().isBsqSwapOffer() && + openOffer.isBsqSwapOfferHasMissingFunds(); + } + + private boolean preventedFromPublishing(OpenOffer openOffer) { + return openOffer.isDeactivated() || openOffer.isBsqSwapOfferHasMissingFunds(); + } } diff --git a/core/src/main/java/bisq/core/offer/AvailabilityResult.java b/core/src/main/java/bisq/core/offer/availability/AvailabilityResult.java similarity index 98% rename from core/src/main/java/bisq/core/offer/AvailabilityResult.java rename to core/src/main/java/bisq/core/offer/availability/AvailabilityResult.java index e4ad982163..abf3bd33cf 100644 --- a/core/src/main/java/bisq/core/offer/AvailabilityResult.java +++ b/core/src/main/java/bisq/core/offer/availability/AvailabilityResult.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.offer; +package bisq.core.offer.availability; public enum AvailabilityResult { UNKNOWN_FAILURE("cannot take offer for unknown reason"), diff --git a/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java b/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java index c1559cec8d..6b75fda67d 100644 --- a/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java +++ b/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java @@ -18,7 +18,7 @@ package bisq.core.offer.availability; import bisq.core.offer.Offer; -import bisq.core.offer.messages.OfferAvailabilityResponse; +import bisq.core.offer.availability.messages.OfferAvailabilityResponse; import bisq.core.support.dispute.mediation.mediator.MediatorManager; import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.user.User; diff --git a/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityProtocol.java b/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityProtocol.java index 218abca67b..bec3275488 100644 --- a/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityProtocol.java +++ b/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityProtocol.java @@ -18,10 +18,10 @@ package bisq.core.offer.availability; import bisq.core.offer.Offer; +import bisq.core.offer.availability.messages.OfferAvailabilityResponse; +import bisq.core.offer.availability.messages.OfferMessage; import bisq.core.offer.availability.tasks.ProcessOfferAvailabilityResponse; import bisq.core.offer.availability.tasks.SendOfferAvailabilityRequest; -import bisq.core.offer.messages.OfferAvailabilityResponse; -import bisq.core.offer.messages.OfferMessage; import bisq.core.util.Validator; import bisq.network.p2p.AckMessage; diff --git a/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityRequest.java b/core/src/main/java/bisq/core/offer/availability/messages/OfferAvailabilityRequest.java similarity index 98% rename from core/src/main/java/bisq/core/offer/messages/OfferAvailabilityRequest.java rename to core/src/main/java/bisq/core/offer/availability/messages/OfferAvailabilityRequest.java index 6d9d14eaf6..2b2d412b45 100644 --- a/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityRequest.java +++ b/core/src/main/java/bisq/core/offer/availability/messages/OfferAvailabilityRequest.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.offer.messages; +package bisq.core.offer.availability.messages; import bisq.network.p2p.SupportedCapabilitiesMessage; @@ -27,7 +27,7 @@ import java.util.Optional; import java.util.UUID; import lombok.EqualsAndHashCode; -import lombok.Value; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import javax.annotation.Nullable; @@ -35,7 +35,7 @@ import javax.annotation.Nullable; // Here we add the SupportedCapabilitiesMessage interface as that message always predates a direct connection // to the trading peer @EqualsAndHashCode(callSuper = true) -@Value +@Getter @Slf4j public final class OfferAvailabilityRequest extends OfferMessage implements SupportedCapabilitiesMessage { private final PubKeyRing pubKeyRing; diff --git a/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityResponse.java b/core/src/main/java/bisq/core/offer/availability/messages/OfferAvailabilityResponse.java similarity index 97% rename from core/src/main/java/bisq/core/offer/messages/OfferAvailabilityResponse.java rename to core/src/main/java/bisq/core/offer/availability/messages/OfferAvailabilityResponse.java index 41b5aa0fcc..5d616fe7e1 100644 --- a/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityResponse.java +++ b/core/src/main/java/bisq/core/offer/availability/messages/OfferAvailabilityResponse.java @@ -15,10 +15,10 @@ * along with Bisq. If not, see . */ -package bisq.core.offer.messages; +package bisq.core.offer.availability.messages; -import bisq.core.offer.AvailabilityResult; +import bisq.core.offer.availability.AvailabilityResult; import bisq.network.p2p.NodeAddress; import bisq.network.p2p.SupportedCapabilitiesMessage; @@ -31,14 +31,14 @@ import java.util.Optional; import java.util.UUID; import lombok.EqualsAndHashCode; -import lombok.Value; +import lombok.Getter; import javax.annotation.Nullable; // We add here the SupportedCapabilitiesMessage interface as that message always predates a direct connection // to the trading peer @EqualsAndHashCode(callSuper = true) -@Value +@Getter public final class OfferAvailabilityResponse extends OfferMessage implements SupportedCapabilitiesMessage { private final AvailabilityResult availabilityResult; @Nullable diff --git a/core/src/main/java/bisq/core/offer/messages/OfferMessage.java b/core/src/main/java/bisq/core/offer/availability/messages/OfferMessage.java similarity index 96% rename from core/src/main/java/bisq/core/offer/messages/OfferMessage.java rename to core/src/main/java/bisq/core/offer/availability/messages/OfferMessage.java index d72fdc4a27..2b230ef353 100644 --- a/core/src/main/java/bisq/core/offer/messages/OfferMessage.java +++ b/core/src/main/java/bisq/core/offer/availability/messages/OfferMessage.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.offer.messages; +package bisq.core.offer.availability.messages; import bisq.network.p2p.DirectMessage; import bisq.network.p2p.UidMessage; diff --git a/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java b/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java index f6c04fa198..3afb6fb2a3 100644 --- a/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java +++ b/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java @@ -17,11 +17,11 @@ package bisq.core.offer.availability.tasks; -import bisq.core.offer.AvailabilityResult; import bisq.core.offer.Offer; +import bisq.core.offer.availability.AvailabilityResult; import bisq.core.offer.availability.DisputeAgentSelection; import bisq.core.offer.availability.OfferAvailabilityModel; -import bisq.core.offer.messages.OfferAvailabilityResponse; +import bisq.core.offer.availability.messages.OfferAvailabilityResponse; import bisq.network.p2p.NodeAddress; diff --git a/core/src/main/java/bisq/core/offer/availability/tasks/SendOfferAvailabilityRequest.java b/core/src/main/java/bisq/core/offer/availability/tasks/SendOfferAvailabilityRequest.java index 0dbc8e69ea..6c0c11f40e 100644 --- a/core/src/main/java/bisq/core/offer/availability/tasks/SendOfferAvailabilityRequest.java +++ b/core/src/main/java/bisq/core/offer/availability/tasks/SendOfferAvailabilityRequest.java @@ -19,7 +19,7 @@ package bisq.core.offer.availability.tasks; import bisq.core.offer.Offer; import bisq.core.offer.availability.OfferAvailabilityModel; -import bisq.core.offer.messages.OfferAvailabilityRequest; +import bisq.core.offer.availability.messages.OfferAvailabilityRequest; import bisq.network.p2p.SendDirectMessageListener; diff --git a/core/src/main/java/bisq/core/offer/CreateOfferService.java b/core/src/main/java/bisq/core/offer/bisq_v1/CreateOfferService.java similarity index 95% rename from core/src/main/java/bisq/core/offer/CreateOfferService.java rename to core/src/main/java/bisq/core/offer/bisq_v1/CreateOfferService.java index 9f5f61dd68..f984dd9ddf 100644 --- a/core/src/main/java/bisq/core/offer/CreateOfferService.java +++ b/core/src/main/java/bisq/core/offer/bisq_v1/CreateOfferService.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.offer; +package bisq.core.offer.bisq_v1; import bisq.core.btc.TxFeeEstimationService; import bisq.core.btc.wallet.BtcWalletService; @@ -23,6 +23,9 @@ import bisq.core.btc.wallet.Restrictions; import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; import bisq.core.monetary.Price; +import bisq.core.offer.Offer; +import bisq.core.offer.OfferDirection; +import bisq.core.offer.OfferUtil; import bisq.core.payment.PaymentAccount; import bisq.core.payment.PaymentAccountUtil; import bisq.core.provider.price.MarketPrice; @@ -37,7 +40,6 @@ import bisq.network.p2p.P2PService; import bisq.common.app.Version; import bisq.common.crypto.PubKeyRing; import bisq.common.util.Tuple2; -import bisq.common.util.Utilities; import org.bitcoinj.core.Coin; @@ -50,7 +52,6 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; -import java.util.UUID; import lombok.extern.slf4j.Slf4j; @@ -94,14 +95,8 @@ public class CreateOfferService { // API /////////////////////////////////////////////////////////////////////////////////////////// - public String getRandomOfferId() { - return Utilities.getRandomPrefix(5, 8) + "-" + - UUID.randomUUID().toString() + "-" + - Version.VERSION.replace(".", ""); - } - public Offer createAndGetOffer(String offerId, - OfferPayload.Direction direction, + OfferDirection direction, String currencyCode, Coin amount, Coin minAmount, @@ -188,7 +183,7 @@ public class CreateOfferService { creationTime, makerAddress, pubKeyRing, - OfferPayload.Direction.valueOf(direction.name()), + direction, priceAsLong, marketPriceMarginParam, useMarketBasedPriceValue, @@ -228,7 +223,7 @@ public class CreateOfferService { } public Tuple2 getEstimatedFeeAndTxVsize(Coin amount, - OfferPayload.Direction direction, + OfferDirection direction, double buyerSecurityDeposit, double sellerSecurityDeposit) { Coin reservedFundsForOffer = getReservedFundsForOffer(direction, @@ -239,7 +234,7 @@ public class CreateOfferService { offerUtil.getMakerFee(amount)); } - public Coin getReservedFundsForOffer(OfferPayload.Direction direction, + public Coin getReservedFundsForOffer(OfferDirection direction, Coin amount, double buyerSecurityDeposit, double sellerSecurityDeposit) { @@ -254,7 +249,7 @@ public class CreateOfferService { return reservedFundsForOffer; } - public Coin getSecurityDeposit(OfferPayload.Direction direction, + public Coin getSecurityDeposit(OfferDirection direction, Coin amount, double buyerSecurityDeposit, double sellerSecurityDeposit) { diff --git a/core/src/main/java/bisq/core/offer/MarketPriceNotAvailableException.java b/core/src/main/java/bisq/core/offer/bisq_v1/MarketPriceNotAvailableException.java similarity index 96% rename from core/src/main/java/bisq/core/offer/MarketPriceNotAvailableException.java rename to core/src/main/java/bisq/core/offer/bisq_v1/MarketPriceNotAvailableException.java index 35ee04a794..a92e5950f0 100644 --- a/core/src/main/java/bisq/core/offer/MarketPriceNotAvailableException.java +++ b/core/src/main/java/bisq/core/offer/bisq_v1/MarketPriceNotAvailableException.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.offer; +package bisq.core.offer.bisq_v1; public class MarketPriceNotAvailableException extends Exception { public MarketPriceNotAvailableException(@SuppressWarnings("SameParameterValue") String message) { diff --git a/core/src/main/java/bisq/core/offer/MutableOfferPayloadFields.java b/core/src/main/java/bisq/core/offer/bisq_v1/MutableOfferPayloadFields.java similarity index 99% rename from core/src/main/java/bisq/core/offer/MutableOfferPayloadFields.java rename to core/src/main/java/bisq/core/offer/bisq_v1/MutableOfferPayloadFields.java index ec0fe502fa..b202526754 100644 --- a/core/src/main/java/bisq/core/offer/MutableOfferPayloadFields.java +++ b/core/src/main/java/bisq/core/offer/bisq_v1/MutableOfferPayloadFields.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.offer; +package bisq.core.offer.bisq_v1; import java.util.List; import java.util.Map; diff --git a/core/src/main/java/bisq/core/offer/OfferPayload.java b/core/src/main/java/bisq/core/offer/bisq_v1/OfferPayload.java similarity index 68% rename from core/src/main/java/bisq/core/offer/OfferPayload.java rename to core/src/main/java/bisq/core/offer/bisq_v1/OfferPayload.java index fa4ae7440b..228c4e43de 100644 --- a/core/src/main/java/bisq/core/offer/OfferPayload.java +++ b/core/src/main/java/bisq/core/offer/bisq_v1/OfferPayload.java @@ -15,31 +15,30 @@ * along with Bisq. If not, see . */ -package bisq.core.offer; +package bisq.core.offer.bisq_v1; + +import bisq.core.offer.OfferDirection; +import bisq.core.offer.OfferPayloadBase; import bisq.network.p2p.NodeAddress; -import bisq.network.p2p.storage.payload.ExpirablePayload; -import bisq.network.p2p.storage.payload.ProtectedStoragePayload; -import bisq.network.p2p.storage.payload.RequiresOwnerIsOnlinePayload; import bisq.common.crypto.Hash; import bisq.common.crypto.PubKeyRing; import bisq.common.proto.ProtoUtil; import bisq.common.util.CollectionUtils; -import bisq.common.util.ExtraDataMapValidator; -import bisq.common.util.Hex; -import bisq.common.util.JsonExclude; -import java.security.PublicKey; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSerializationContext; import java.util.ArrayList; -import java.util.Date; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import java.lang.reflect.Type; + import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; @@ -52,30 +51,10 @@ import static com.google.common.base.Preconditions.checkNotNull; // OfferPayload has about 1.4 kb. We should look into options to make it smaller but will be hard to do it in a // backward compatible way. Maybe a candidate when segwit activation is done as hardfork? - -@EqualsAndHashCode +@EqualsAndHashCode(callSuper = true) @Getter @Slf4j -public final class OfferPayload implements ProtectedStoragePayload, ExpirablePayload, RequiresOwnerIsOnlinePayload { - public static final long TTL = TimeUnit.MINUTES.toMillis(9); - - /////////////////////////////////////////////////////////////////////////////////////////// - // Enum - /////////////////////////////////////////////////////////////////////////////////////////// - - public enum Direction { - BUY, - SELL; - - public static OfferPayload.Direction fromProto(protobuf.OfferPayload.Direction direction) { - return ProtoUtil.enumFromProto(OfferPayload.Direction.class, direction.name()); - } - - public static protobuf.OfferPayload.Direction toProtoMessage(Direction direction) { - return protobuf.OfferPayload.Direction.valueOf(direction.name()); - } - } - +public final class OfferPayload extends OfferPayloadBase { // Keys for extra map // Only set for fiat offers public static final String ACCOUNT_AGE_WITNESS_HASH = "accountAgeWitnessHash"; @@ -98,14 +77,6 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay // Instance fields /////////////////////////////////////////////////////////////////////////////////////////// - private final String id; - private final long date; - private final NodeAddress ownerNodeAddress; - @JsonExclude - private final PubKeyRing pubKeyRing; - private final Direction direction; - // price if fixed price is used (usePercentageBasedPrice = false), otherwise 0 - private final long price; // Distance form market price if percentage based price is used (usePercentageBasedPrice = true), otherwise 0. // E.g. 0.1 -> 10%. Can be negative as well. Depending on direction the marketPriceMargin is above or below the market price. // Positive values is always the usual case where you want a better price as the market. @@ -114,13 +85,6 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay private final double marketPriceMargin; // We use 2 type of prices: fixed price or price based on distance from market price private final boolean useMarketBasedPrice; - private final long amount; - private final long minAmount; - - // For fiat offer the baseCurrencyCode is BTC and the counterCurrencyCode is the fiat currency - // For altcoin offers it is the opposite. baseCurrencyCode is the altcoin and the counterCurrencyCode is BTC. - private final String baseCurrencyCode; - private final String counterCurrencyCode; @Deprecated // Not used anymore, but we cannot set it Nullable or remove it to not break backward compatibility (diff. hash) @@ -128,8 +92,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay @Deprecated // Not used anymore, but we cannot set it Nullable or remove it to not break backward compatibility (diff. hash) private final List mediatorNodeAddresses; - private final String paymentMethodId; - private final String makerPaymentAccountId; + // Mutable property. Has to be set before offer is saved in P2P network as it changes the payload hash! @Setter @Nullable @@ -142,7 +105,6 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay private final String bankId; @Nullable private final List acceptedBankIds; - private final String versionNr; private final long blockHeightAtOfferCreation; private final long txFee; private final long makerFee; @@ -167,20 +129,6 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay @Nullable private final String hashOfChallenge; - // Should be only used in emergency case if we need to add data but do not want to break backward compatibility - // at the P2P network storage checks. The hash of the object will be used to verify if the data is valid. Any new - // field in a class would break that hash and therefore break the storage mechanism. - - // extraDataMap used from v0.6 on for hashOfPaymentAccount - // key ACCOUNT_AGE_WITNESS, value: hex string of hashOfPaymentAccount byte array - @Nullable - private final Map extraDataMap; - private final int protocolVersion; - - // Cache the payload hash to avoid some repeated calculations. - // Take care to calculate it only after the offerFeePaymentTxId is set. - private transient byte[] hash; - /////////////////////////////////////////////////////////////////////////////////////////// // Constructor @@ -190,7 +138,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay long date, NodeAddress ownerNodeAddress, PubKeyRing pubKeyRing, - Direction direction, + OfferDirection direction, long price, double marketPriceMargin, boolean useMarketBasedPrice, @@ -224,28 +172,31 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay @Nullable String hashOfChallenge, @Nullable Map extraDataMap, int protocolVersion) { - this.id = id; - this.date = date; - this.ownerNodeAddress = ownerNodeAddress; - this.pubKeyRing = pubKeyRing; - this.direction = direction; - this.price = price; + super(id, + date, + ownerNodeAddress, + pubKeyRing, + baseCurrencyCode, + counterCurrencyCode, + direction, + price, + amount, + minAmount, + paymentMethodId, + makerPaymentAccountId, + extraDataMap, + versionNr, + protocolVersion); + this.marketPriceMargin = marketPriceMargin; this.useMarketBasedPrice = useMarketBasedPrice; - this.amount = amount; - this.minAmount = minAmount; - this.baseCurrencyCode = baseCurrencyCode; - this.counterCurrencyCode = counterCurrencyCode; this.arbitratorNodeAddresses = arbitratorNodeAddresses; this.mediatorNodeAddresses = mediatorNodeAddresses; - this.paymentMethodId = paymentMethodId; - this.makerPaymentAccountId = makerPaymentAccountId; this.offerFeePaymentTxId = offerFeePaymentTxId; this.countryCode = countryCode; this.acceptedCountryCodes = acceptedCountryCodes; this.bankId = bankId; this.acceptedBankIds = acceptedBankIds; - this.versionNr = versionNr; this.blockHeightAtOfferCreation = blockHeightAtOfferCreation; this.txFee = txFee; this.makerFee = makerFee; @@ -260,11 +211,9 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay this.upperClosePrice = upperClosePrice; this.isPrivateOffer = isPrivateOffer; this.hashOfChallenge = hashOfChallenge; - this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap); - this.protocolVersion = protocolVersion; - this.hash = null; // Do not calculate hash before offerFeePaymentTxId is set. } + @Override public byte[] getHash() { if (this.hash == null && this.offerFeePaymentTxId != null) { // A proto message can be created only after the offerFeePaymentTxId is @@ -286,7 +235,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay .setDate(date) .setOwnerNodeAddress(ownerNodeAddress.toProtoMessage()) .setPubKeyRing(pubKeyRing.toProtoMessage()) - .setDirection(Direction.toProtoMessage(direction)) + .setDirection(OfferDirection.toProtoMessage(direction)) .setPrice(price) .setMarketPriceMargin(marketPriceMargin) .setUseMarketBasedPrice(useMarketBasedPrice) @@ -345,7 +294,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay proto.getDate(), NodeAddress.fromProto(proto.getOwnerNodeAddress()), PubKeyRing.fromProto(proto.getPubKeyRing()), - OfferPayload.Direction.fromProto(proto.getDirection()), + OfferDirection.fromProto(proto.getDirection()), proto.getPrice(), proto.getMarketPriceMargin(), proto.getUseMarketBasedPrice(), @@ -385,70 +334,75 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay proto.getProtocolVersion()); } - - /////////////////////////////////////////////////////////////////////////////////////////// - // API - /////////////////////////////////////////////////////////////////////////////////////////// - - @Override - public long getTTL() { - return TTL; - } - - @Override - public PublicKey getOwnerPubKey() { - return pubKeyRing.getSignaturePubKey(); - } - - // In the offer we support base and counter currency - // Fiat offers have base currency BTC and counterCurrency Fiat - // Altcoins have base currency Altcoin and counterCurrency BTC - // The rest of the app does not support yet that concept of base currency and counter currencies - // so we map here for convenience - public String getCurrencyCode() { - return getBaseCurrencyCode().equals("BTC") ? getCounterCurrencyCode() : getBaseCurrencyCode(); - } - @Override public String toString() { return "OfferPayload{" + - "\n id='" + id + '\'' + - ",\n date=" + new Date(date) + - ",\n ownerNodeAddress=" + ownerNodeAddress + - ",\n pubKeyRing=" + pubKeyRing + - ",\n direction=" + direction + - ",\n price=" + price + - ",\n marketPriceMargin=" + marketPriceMargin + - ",\n useMarketBasedPrice=" + useMarketBasedPrice + - ",\n amount=" + amount + - ",\n minAmount=" + minAmount + - ",\n baseCurrencyCode='" + baseCurrencyCode + '\'' + - ",\n counterCurrencyCode='" + counterCurrencyCode + '\'' + - ",\n paymentMethodId='" + paymentMethodId + '\'' + - ",\n makerPaymentAccountId='" + makerPaymentAccountId + '\'' + - ",\n offerFeePaymentTxId='" + offerFeePaymentTxId + '\'' + - ",\n countryCode='" + countryCode + '\'' + - ",\n acceptedCountryCodes=" + acceptedCountryCodes + - ",\n bankId='" + bankId + '\'' + - ",\n acceptedBankIds=" + acceptedBankIds + - ",\n versionNr='" + versionNr + '\'' + - ",\n blockHeightAtOfferCreation=" + blockHeightAtOfferCreation + - ",\n txFee=" + txFee + - ",\n makerFee=" + makerFee + - ",\n isCurrencyForMakerFeeBtc=" + isCurrencyForMakerFeeBtc + - ",\n buyerSecurityDeposit=" + buyerSecurityDeposit + - ",\n sellerSecurityDeposit=" + sellerSecurityDeposit + - ",\n maxTradeLimit=" + maxTradeLimit + - ",\n maxTradePeriod=" + maxTradePeriod + - ",\n useAutoClose=" + useAutoClose + - ",\n useReOpenAfterAutoClose=" + useReOpenAfterAutoClose + - ",\n lowerClosePrice=" + lowerClosePrice + - ",\n upperClosePrice=" + upperClosePrice + - ",\n isPrivateOffer=" + isPrivateOffer + - ",\n hashOfChallenge='" + hashOfChallenge + '\'' + - ",\n extraDataMap=" + extraDataMap + - ",\n protocolVersion=" + protocolVersion + - ",\n hash=" + (getHash() == null ? "null" : Hex.encode(getHash())) + - "\n}"; + "\r\n marketPriceMargin=" + marketPriceMargin + + ",\r\n useMarketBasedPrice=" + useMarketBasedPrice + + ",\r\n arbitratorNodeAddresses=" + arbitratorNodeAddresses + + ",\r\n mediatorNodeAddresses=" + mediatorNodeAddresses + + ",\r\n offerFeePaymentTxId='" + offerFeePaymentTxId + '\'' + + ",\r\n countryCode='" + countryCode + '\'' + + ",\r\n acceptedCountryCodes=" + acceptedCountryCodes + + ",\r\n bankId='" + bankId + '\'' + + ",\r\n acceptedBankIds=" + acceptedBankIds + + ",\r\n blockHeightAtOfferCreation=" + blockHeightAtOfferCreation + + ",\r\n txFee=" + txFee + + ",\r\n makerFee=" + makerFee + + ",\r\n isCurrencyForMakerFeeBtc=" + isCurrencyForMakerFeeBtc + + ",\r\n buyerSecurityDeposit=" + buyerSecurityDeposit + + ",\r\n sellerSecurityDeposit=" + sellerSecurityDeposit + + ",\r\n maxTradeLimit=" + maxTradeLimit + + ",\r\n maxTradePeriod=" + maxTradePeriod + + ",\r\n useAutoClose=" + useAutoClose + + ",\r\n useReOpenAfterAutoClose=" + useReOpenAfterAutoClose + + ",\r\n lowerClosePrice=" + lowerClosePrice + + ",\r\n upperClosePrice=" + upperClosePrice + + ",\r\n isPrivateOffer=" + isPrivateOffer + + ",\r\n hashOfChallenge='" + hashOfChallenge + '\'' + + "\r\n} " + super.toString(); + } + + // For backward compatibility we need to ensure same order for json fields as with 1.7.5. and earlier versions. + // The json is used for the hash in the contract and change of oder would cause a different hash and + // therefore a failure during trade. + public static class JsonSerializer implements com.google.gson.JsonSerializer { + @Override + public JsonElement serialize(OfferPayload offerPayload, Type type, JsonSerializationContext context) { + JsonObject object = new JsonObject(); + object.add("id", context.serialize(offerPayload.getId())); + object.add("date", context.serialize(offerPayload.getDate())); + object.add("ownerNodeAddress", context.serialize(offerPayload.getOwnerNodeAddress())); + object.add("direction", context.serialize(offerPayload.getDirection())); + object.add("price", context.serialize(offerPayload.getPrice())); + object.add("marketPriceMargin", context.serialize(offerPayload.getMarketPriceMargin())); + object.add("useMarketBasedPrice", context.serialize(offerPayload.isUseMarketBasedPrice())); + object.add("amount", context.serialize(offerPayload.getAmount())); + object.add("minAmount", context.serialize(offerPayload.getMinAmount())); + object.add("baseCurrencyCode", context.serialize(offerPayload.getBaseCurrencyCode())); + object.add("counterCurrencyCode", context.serialize(offerPayload.getCounterCurrencyCode())); + object.add("arbitratorNodeAddresses", context.serialize(offerPayload.getArbitratorNodeAddresses())); + object.add("mediatorNodeAddresses", context.serialize(offerPayload.getMediatorNodeAddresses())); + object.add("paymentMethodId", context.serialize(offerPayload.getPaymentMethodId())); + object.add("makerPaymentAccountId", context.serialize(offerPayload.getMakerPaymentAccountId())); + object.add("offerFeePaymentTxId", context.serialize(offerPayload.getOfferFeePaymentTxId())); + object.add("versionNr", context.serialize(offerPayload.getVersionNr())); + object.add("blockHeightAtOfferCreation", context.serialize(offerPayload.getBlockHeightAtOfferCreation())); + object.add("txFee", context.serialize(offerPayload.getTxFee())); + object.add("makerFee", context.serialize(offerPayload.getMakerFee())); + object.add("isCurrencyForMakerFeeBtc", context.serialize(offerPayload.isCurrencyForMakerFeeBtc())); + object.add("buyerSecurityDeposit", context.serialize(offerPayload.getBuyerSecurityDeposit())); + object.add("sellerSecurityDeposit", context.serialize(offerPayload.getSellerSecurityDeposit())); + object.add("maxTradeLimit", context.serialize(offerPayload.getMaxTradeLimit())); + object.add("maxTradePeriod", context.serialize(offerPayload.getMaxTradePeriod())); + object.add("useAutoClose", context.serialize(offerPayload.isUseAutoClose())); + object.add("useReOpenAfterAutoClose", context.serialize(offerPayload.isUseReOpenAfterAutoClose())); + object.add("lowerClosePrice", context.serialize(offerPayload.getLowerClosePrice())); + object.add("upperClosePrice", context.serialize(offerPayload.getUpperClosePrice())); + object.add("isPrivateOffer", context.serialize(offerPayload.isPrivateOffer())); + object.add("extraDataMap", context.serialize(offerPayload.getExtraDataMap())); + object.add("protocolVersion", context.serialize(offerPayload.getProtocolVersion())); + return object; + } } } diff --git a/core/src/main/java/bisq/core/offer/takeoffer/TakeOfferModel.java b/core/src/main/java/bisq/core/offer/bisq_v1/TakeOfferModel.java similarity index 99% rename from core/src/main/java/bisq/core/offer/takeoffer/TakeOfferModel.java rename to core/src/main/java/bisq/core/offer/bisq_v1/TakeOfferModel.java index 8e345b8142..447d1d7076 100644 --- a/core/src/main/java/bisq/core/offer/takeoffer/TakeOfferModel.java +++ b/core/src/main/java/bisq/core/offer/bisq_v1/TakeOfferModel.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.offer.takeoffer; +package bisq.core.offer.bisq_v1; import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.btc.model.AddressEntry; @@ -46,7 +46,7 @@ import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import static bisq.core.btc.model.AddressEntry.Context.OFFER_FUNDING; -import static bisq.core.offer.OfferPayload.Direction.SELL; +import static bisq.core.offer.OfferDirection.SELL; import static bisq.core.util.VolumeUtil.getAdjustedVolumeForHalCash; import static bisq.core.util.VolumeUtil.getRoundedFiatVolume; import static bisq.core.util.coin.CoinUtil.minCoin; diff --git a/core/src/main/java/bisq/core/offer/TriggerPriceService.java b/core/src/main/java/bisq/core/offer/bisq_v1/TriggerPriceService.java similarity index 89% rename from core/src/main/java/bisq/core/offer/TriggerPriceService.java rename to core/src/main/java/bisq/core/offer/bisq_v1/TriggerPriceService.java index 99defe325b..c18cf11ec4 100644 --- a/core/src/main/java/bisq/core/offer/TriggerPriceService.java +++ b/core/src/main/java/bisq/core/offer/bisq_v1/TriggerPriceService.java @@ -15,11 +15,15 @@ * along with Bisq. If not, see . */ -package bisq.core.offer; +package bisq.core.offer.bisq_v1; import bisq.core.locale.CurrencyUtil; import bisq.core.monetary.Altcoin; import bisq.core.monetary.Price; +import bisq.core.offer.Offer; +import bisq.core.offer.OfferDirection; +import bisq.core.offer.OpenOffer; +import bisq.core.offer.OpenOfferManager; import bisq.core.provider.mempool.MempoolService; import bisq.core.provider.price.MarketPrice; import bisq.core.provider.price.PriceFeedService; @@ -127,8 +131,8 @@ public class TriggerPriceService { return false; } - OfferPayload.Direction direction = openOffer.getOffer().getDirection(); - boolean isSellOffer = direction == OfferPayload.Direction.SELL; + OfferDirection direction = openOffer.getOffer().getDirection(); + boolean isSellOffer = direction == OfferDirection.SELL; boolean condition = isSellOffer && !cryptoCurrency || !isSellOffer && cryptoCurrency; return condition ? marketPriceAsLong < triggerPrice : @@ -136,8 +140,13 @@ public class TriggerPriceService { } private void checkPriceThreshold(MarketPrice marketPrice, OpenOffer openOffer) { + Offer offer = openOffer.getOffer(); + if (offer.isBsqSwapOffer()) { + return; + } + if (wasTriggered(marketPrice, openOffer)) { - String currencyCode = openOffer.getOffer().getCurrencyCode(); + String currencyCode = offer.getCurrencyCode(); int smallestUnitExponent = CurrencyUtil.isCryptoCurrency(currencyCode) ? Altcoin.SMALLEST_UNIT_EXPONENT : Fiat.SMALLEST_UNIT_EXPONENT; @@ -146,9 +155,9 @@ public class TriggerPriceService { log.info("Market price exceeded the trigger price of the open offer.\n" + "We deactivate the open offer with ID {}.\nCurrency: {};\nOffer direction: {};\n" + "Market price: {};\nTrigger price: {}", - openOffer.getOffer().getShortId(), + offer.getShortId(), currencyCode, - openOffer.getOffer().getDirection(), + offer.getDirection(), marketPrice.getPrice(), MathUtils.scaleDownByPowerOf10(triggerPrice, smallestUnitExponent) ); @@ -158,14 +167,16 @@ public class TriggerPriceService { }); } else if (openOffer.getState() == OpenOffer.State.AVAILABLE) { // check the mempool if it has not been done before - if (openOffer.getMempoolStatus() < 0 && mempoolService.canRequestBeMade(openOffer.getOffer().getOfferPayload())) { - mempoolService.validateOfferMakerTx(openOffer.getOffer().getOfferPayload(), (txValidator -> { + OfferPayload offerPayload = offer.getOfferPayload().orElseThrow(); + if (openOffer.getMempoolStatus() < 0 && + mempoolService.canRequestBeMade(offerPayload)) { + mempoolService.validateOfferMakerTx(offerPayload, (txValidator -> { openOffer.setMempoolStatus(txValidator.isFail() ? 0 : 1); })); } // if the mempool indicated failure then deactivate the open offer if (openOffer.getMempoolStatus() == 0) { - log.info("Deactivating open offer {} due to mempool validation", openOffer.getOffer().getShortId()); + log.info("Deactivating open offer {} due to mempool validation", offer.getShortId()); openOfferManager.deactivateOpenOffer(openOffer, () -> { }, errorMessage -> { }); diff --git a/core/src/main/java/bisq/core/offer/bsq_swap/BsqSwapOfferModel.java b/core/src/main/java/bisq/core/offer/bsq_swap/BsqSwapOfferModel.java new file mode 100644 index 0000000000..33b3f963cd --- /dev/null +++ b/core/src/main/java/bisq/core/offer/bsq_swap/BsqSwapOfferModel.java @@ -0,0 +1,392 @@ +/* + * 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.core.offer.bsq_swap; + +import bisq.core.btc.exceptions.InsufficientBsqException; +import bisq.core.btc.listeners.BalanceListener; +import bisq.core.btc.listeners.BsqBalanceListener; +import bisq.core.btc.model.RawTransactionInput; +import bisq.core.btc.wallet.BsqWalletService; +import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.monetary.Price; +import bisq.core.monetary.Volume; +import bisq.core.offer.OfferDirection; +import bisq.core.offer.OfferUtil; +import bisq.core.payment.payload.PaymentMethod; +import bisq.core.provider.fee.FeeService; +import bisq.core.trade.bsq_swap.BsqSwapCalculation; +import bisq.core.util.coin.CoinUtil; + +import bisq.common.util.Tuple2; + +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.InsufficientMoneyException; +import org.bitcoinj.core.Transaction; + +import com.google.inject.Inject; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; + +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; + +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.Nullable; + +@Slf4j +public class BsqSwapOfferModel { + public final static String BSQ = "BSQ"; + + private final OfferUtil offerUtil; + private final BtcWalletService btcWalletService; + private final BsqWalletService bsqWalletService; + private final FeeService feeService; + + // offer data + @Setter + @Getter + private String offerId; + @Getter + private OfferDirection direction; + @Getter + private boolean isMaker; + + // amounts/price + @Getter + private final ObjectProperty btcAmount = new SimpleObjectProperty<>(); + @Getter + private final ObjectProperty bsqAmount = new SimpleObjectProperty<>(); + @Getter + private final ObjectProperty minAmount = new SimpleObjectProperty<>(); + @Getter + private final ObjectProperty price = new SimpleObjectProperty<>(); + @Getter + private final ObjectProperty volume = new SimpleObjectProperty<>(); + @Getter + private final ObjectProperty minVolume = new SimpleObjectProperty<>(); + @Getter + public final ObjectProperty inputAmountAsCoin = new SimpleObjectProperty<>(); + @Getter + private final ObjectProperty payoutAmountAsCoin = new SimpleObjectProperty<>(); + @Getter + private final ObjectProperty missingFunds = new SimpleObjectProperty<>(Coin.ZERO); + @Nullable + private Coin txFee; + @Getter + private long txFeePerVbyte; + + private BalanceListener btcBalanceListener; + private BsqBalanceListener bsqBalanceListener; + + //utils + private final Predicate> isNonZeroAmount = (c) -> c.get() != null && !c.get().isZero(); + private final Predicate> isNonZeroPrice = (p) -> p.get() != null && !p.get().isZero(); + private final Predicate> isNonZeroVolume = (v) -> v.get() != null && !v.get().isZero(); + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor + /////////////////////////////////////////////////////////////////////////////////////////// + + @Inject + public BsqSwapOfferModel(OfferUtil offerUtil, + BtcWalletService btcWalletService, + BsqWalletService bsqWalletService, + FeeService feeService) { + this.offerUtil = offerUtil; + this.btcWalletService = btcWalletService; + this.bsqWalletService = bsqWalletService; + this.feeService = feeService; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + public void init(OfferDirection direction, boolean isMaker) { + this.direction = direction; + this.isMaker = isMaker; + + createListeners(); + applyTxFeePerVbyte(); + + calculateVolume(); + calculateInputAndPayout(); + } + + public void doActivate() { + addListeners(); + } + + public void doDeactivate() { + removeListeners(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Listeners + /////////////////////////////////////////////////////////////////////////////////////////// + + public void createListeners() { + btcBalanceListener = new BalanceListener() { + @Override + public void onBalanceChanged(Coin balance, Transaction tx) { + calculateInputAndPayout(); + } + }; + bsqBalanceListener = (availableBalance, availableNonBsqBalance, unverifiedBalance, + unconfirmedChangeBalance, lockedForVotingBalance, lockedInBondsBalance, + unlockingBondsBalance) -> calculateInputAndPayout(); + } + + public void addListeners() { + btcWalletService.addBalanceListener(btcBalanceListener); + bsqWalletService.addBsqBalanceListener(bsqBalanceListener); + } + + public void removeListeners() { + btcWalletService.removeBalanceListener(btcBalanceListener); + bsqWalletService.removeBsqBalanceListener(bsqBalanceListener); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Calculations + /////////////////////////////////////////////////////////////////////////////////////////// + + public void calculateVolume() { + if (isNonZeroPrice.test(price) && isNonZeroAmount.test(btcAmount)) { + try { + setVolume(calculateVolumeForAmount(btcAmount)); + calculateMinVolume(); + } catch (Throwable t) { + log.error(t.toString()); + } + } + } + + public void calculateMinVolume() { + if (isNonZeroPrice.test(price) && isNonZeroAmount.test(minAmount)) { + try { + minVolume.set(calculateVolumeForAmount(minAmount)); + } catch (Throwable t) { + log.error(t.toString()); + } + } + } + + public Volume calculateVolumeForAmount(ObjectProperty amount) { + return price.get().getVolumeByAmount(amount.get()); + } + + public void calculateAmount(Function reduceTo4DecimalsFunction) { + if (isNonZeroPrice.test(price) && isNonZeroVolume.test(volume)) { + try { + Coin amount = price.get().getAmountByVolume(volume.get()); + calculateVolume(); + btcAmount.set(reduceTo4DecimalsFunction.apply(amount)); + resetTxFeeAndMissingFunds(); + + calculateInputAndPayout(); + } catch (Throwable t) { + log.error(t.toString()); + } + } + } + + public void calculateInputAndPayout() { + Coin bsqTradeAmountAsCoin = bsqAmount.get(); + Coin btcTradeAmountAsCoin = btcAmount.get(); + Coin tradeFeeAsCoin = getTradeFee(); + if (bsqTradeAmountAsCoin == null || btcTradeAmountAsCoin == null || tradeFeeAsCoin == null) { + return; + } + long tradeFee = tradeFeeAsCoin.getValue(); + if (isBuyer()) { + inputAmountAsCoin.set(BsqSwapCalculation.getBuyersBsqInputValue(bsqTradeAmountAsCoin.getValue(), tradeFee)); + try { + payoutAmountAsCoin.set(BsqSwapCalculation.getBuyersBtcPayoutValue(bsqWalletService, + bsqTradeAmountAsCoin, + btcTradeAmountAsCoin, + txFeePerVbyte, + tradeFee)); + } catch (InsufficientBsqException e) { + // As this is for the output we do not set the missingFunds here. + + // If we do not have sufficient funds we cannot calculate the required fee from the inputs and change, + // so we use an estimated size for the tx fee. + payoutAmountAsCoin.set(BsqSwapCalculation.getEstimatedBuyersBtcPayoutValue(btcTradeAmountAsCoin, + txFeePerVbyte, + tradeFee)); + } + } else { + try { + inputAmountAsCoin.set(BsqSwapCalculation.getSellersBtcInputValue(btcWalletService, + btcTradeAmountAsCoin, + txFeePerVbyte, + tradeFee)); + } catch (InsufficientMoneyException e) { + missingFunds.set(e.missing); + + // If we do not have sufficient funds we cannot calculate the required fee from the inputs and change, + // so we use an estimated size for the tx fee. + inputAmountAsCoin.set(BsqSwapCalculation.getEstimatedSellersBtcInputValue(btcTradeAmountAsCoin, txFeePerVbyte, tradeFee)); + } + + payoutAmountAsCoin.set(BsqSwapCalculation.getSellersBsqPayoutValue(bsqTradeAmountAsCoin.getValue(), tradeFee)); + } + + evaluateMissingFunds(); + } + + private void evaluateMissingFunds() { + Coin walletBalance = isBuyer() ? + bsqWalletService.getVerifiedBalance() : + btcWalletService.getSavingWalletBalance(); + missingFunds.set(offerUtil.getBalanceShortage(inputAmountAsCoin.get(), walletBalance)); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Setters + /////////////////////////////////////////////////////////////////////////////////////////// + + public void setBtcAmount(Coin btcAmount) { + this.btcAmount.set(btcAmount); + resetTxFeeAndMissingFunds(); + } + + public void setPrice(Price price) { + this.price.set(price); + } + + public void setVolume(Volume volume) { + this.volume.set(volume); + bsqAmount.set(volume != null ? BsqSwapCalculation.getBsqTradeAmount(volume) : null); + resetTxFeeAndMissingFunds(); + } + + public void setMinAmount(Coin minAmount) { + this.minAmount.set(minAmount); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Getters + /////////////////////////////////////////////////////////////////////////////////////////// + + public Coin getTxFee() throws InsufficientMoneyException { + if (txFee == null) { + Coin tradeFeeAsCoin = getTradeFee(); + if (btcAmount.get() == null || tradeFeeAsCoin == null) { + return txFee; + } + + long tradeFee = tradeFeeAsCoin.getValue(); + Tuple2, Coin> btcInputsAndChange; + if (isBuyer()) { + btcInputsAndChange = BsqSwapCalculation.getBuyersBsqInputsAndChange(bsqWalletService, + bsqAmount.get().getValue(), + tradeFee); + } else { + btcInputsAndChange = BsqSwapCalculation.getSellersBtcInputsAndChange(btcWalletService, + btcAmount.get().getValue(), + txFeePerVbyte, + tradeFee); + } + int vBytes = BsqSwapCalculation.getVBytesSize(btcInputsAndChange.first, btcInputsAndChange.second.getValue()); + long adjustedTxFee = BsqSwapCalculation.getAdjustedTxFee(txFeePerVbyte, vBytes, tradeFee); + txFee = Coin.valueOf(adjustedTxFee); + } + return txFee; + } + + public Coin getEstimatedTxFee() { + long adjustedTxFee = BsqSwapCalculation.getAdjustedTxFee(txFeePerVbyte, + BsqSwapCalculation.ESTIMATED_V_BYTES, + getTradeFee().getValue()); + return Coin.valueOf(adjustedTxFee); + } + + public boolean hasMissingFunds() { + evaluateMissingFunds(); + return missingFunds.get().isPositive(); + } + + public Coin getTradeFee() { + return isMaker ? getMakerFee() : getTakerFee(); + } + + public Coin getTakerFee() { + return CoinUtil.getTakerFee(false, btcAmount.get()); + } + + public Coin getMakerFee() { + return CoinUtil.getMakerFee(false, btcAmount.get()); + } + + public boolean isBuyer() { + return isMaker ? isBuyOffer() : isSellOffer(); + } + + public boolean isBuyOffer() { + return direction == OfferDirection.BUY; + } + + public boolean isSellOffer() { + return direction == OfferDirection.SELL; + } + + public boolean isMinAmountLessOrEqualAmount() { + //noinspection SimplifiableIfStatement + if (minAmount.get() != null && btcAmount.get() != null) + return !minAmount.get().isGreaterThan(btcAmount.get()); + return true; + } + + public long getMaxTradeLimit() { + return PaymentMethod.BSQ_SWAP.getMaxTradeLimitAsCoin(BSQ).getValue(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Utils + /////////////////////////////////////////////////////////////////////////////////////////// + + private void applyTxFeePerVbyte() { + // We only set the txFeePerVbyte at start, otherwise we might get diff. required amounts while user has view open + txFeePerVbyte = feeService.getTxFeePerVbyte().getValue(); + resetTxFeeAndMissingFunds(); + feeService.requestFees(() -> { + txFeePerVbyte = feeService.getTxFeePerVbyte().getValue(); + calculateInputAndPayout(); + resetTxFeeAndMissingFunds(); + }); + } + + private void resetTxFeeAndMissingFunds() { + txFee = null; + missingFunds.set(Coin.ZERO); + } +} diff --git a/core/src/main/java/bisq/core/offer/bsq_swap/BsqSwapOfferPayload.java b/core/src/main/java/bisq/core/offer/bsq_swap/BsqSwapOfferPayload.java new file mode 100644 index 0000000000..2ba5ca2892 --- /dev/null +++ b/core/src/main/java/bisq/core/offer/bsq_swap/BsqSwapOfferPayload.java @@ -0,0 +1,169 @@ +/* + * 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.core.offer.bsq_swap; + +import bisq.core.offer.OfferDirection; +import bisq.core.offer.OfferPayloadBase; +import bisq.core.payment.BsqSwapAccount; +import bisq.core.payment.payload.PaymentMethod; + +import bisq.network.p2p.NodeAddress; +import bisq.network.p2p.storage.payload.CapabilityRequiringPayload; +import bisq.network.p2p.storage.payload.ProofOfWorkPayload; + +import bisq.common.app.Capabilities; +import bisq.common.app.Capability; +import bisq.common.crypto.ProofOfWork; +import bisq.common.crypto.PubKeyRing; +import bisq.common.proto.ProtoUtil; +import bisq.common.util.CollectionUtils; + +import java.util.Map; +import java.util.Optional; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import org.jetbrains.annotations.Nullable; + +@EqualsAndHashCode(callSuper = true) +@Getter +@Slf4j +public final class BsqSwapOfferPayload extends OfferPayloadBase + implements ProofOfWorkPayload, CapabilityRequiringPayload { + + public static BsqSwapOfferPayload from(BsqSwapOfferPayload original, + String offerId, + ProofOfWork proofOfWork) { + return new BsqSwapOfferPayload(offerId, + original.getDate(), + original.getOwnerNodeAddress(), + original.getPubKeyRing(), + original.getDirection(), + original.getPrice(), + original.getAmount(), + original.getMinAmount(), + proofOfWork, + original.getExtraDataMap(), + original.getVersionNr(), + original.getProtocolVersion() + ); + } + + private final ProofOfWork proofOfWork; + + public BsqSwapOfferPayload(String id, + long date, + NodeAddress ownerNodeAddress, + PubKeyRing pubKeyRing, + OfferDirection direction, + long price, + long amount, + long minAmount, + ProofOfWork proofOfWork, + @Nullable Map extraDataMap, + String versionNr, + int protocolVersion) { + super(id, + date, + ownerNodeAddress, + pubKeyRing, + "BSQ", + "BTC", + direction, + price, + amount, + minAmount, + PaymentMethod.BSQ_SWAP_ID, + BsqSwapAccount.ID, + extraDataMap, + versionNr, + protocolVersion); + + this.proofOfWork = proofOfWork; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + public static protobuf.OfferDirection toProtoMessage(OfferDirection direction) { + return protobuf.OfferDirection.valueOf(direction.name()); + } + + public static OfferDirection fromProto(protobuf.OfferDirection offerDirection) { + return ProtoUtil.enumFromProto(OfferDirection.class, offerDirection.name()); + } + + @Override + public protobuf.StoragePayload toProtoMessage() { + protobuf.BsqSwapOfferPayload.Builder builder = protobuf.BsqSwapOfferPayload.newBuilder() + .setId(id) + .setDate(date) + .setOwnerNodeAddress(ownerNodeAddress.toProtoMessage()) + .setPubKeyRing(pubKeyRing.toProtoMessage()) + .setDirection(toProtoMessage(direction)) + .setPrice(price) + .setAmount(amount) + .setMinAmount(minAmount) + .setProofOfWork(proofOfWork.toProtoMessage()) + .setVersionNr(versionNr) + .setProtocolVersion(protocolVersion); + + Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData); + + return protobuf.StoragePayload.newBuilder().setBsqSwapOfferPayload(builder).build(); + } + + public static BsqSwapOfferPayload fromProto(protobuf.BsqSwapOfferPayload proto) { + Map extraDataMapMap = CollectionUtils.isEmpty(proto.getExtraDataMap()) ? + null : proto.getExtraDataMap(); + return new BsqSwapOfferPayload(proto.getId(), + proto.getDate(), + NodeAddress.fromProto(proto.getOwnerNodeAddress()), + PubKeyRing.fromProto(proto.getPubKeyRing()), + fromProto(proto.getDirection()), + proto.getPrice(), + proto.getAmount(), + proto.getMinAmount(), + ProofOfWork.fromProto(proto.getProofOfWork()), + extraDataMapMap, + proto.getVersionNr(), + proto.getProtocolVersion() + ); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // ProofOfWorkPayload + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public Capabilities getRequiredCapabilities() { + return new Capabilities(Capability.BSQ_SWAP_OFFER); + } + + @Override + public String toString() { + return "BsqSwapOfferPayload{" + + "\r\n proofOfWork=" + proofOfWork + + "\r\n} " + super.toString(); + } +} diff --git a/core/src/main/java/bisq/core/offer/bsq_swap/BsqSwapTakeOfferModel.java b/core/src/main/java/bisq/core/offer/bsq_swap/BsqSwapTakeOfferModel.java new file mode 100644 index 0000000000..266cc2a856 --- /dev/null +++ b/core/src/main/java/bisq/core/offer/bsq_swap/BsqSwapTakeOfferModel.java @@ -0,0 +1,135 @@ +/* + * 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.core.offer.bsq_swap; + +import bisq.core.btc.wallet.BsqWalletService; +import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.filter.FilterManager; +import bisq.core.locale.Res; +import bisq.core.offer.Offer; +import bisq.core.offer.OfferUtil; +import bisq.core.provider.fee.FeeService; +import bisq.core.trade.TradeManager; +import bisq.core.trade.bisq_v1.TradeResultHandler; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; + +import bisq.common.handlers.ErrorMessageHandler; + +import org.bitcoinj.core.Coin; + +import com.google.inject.Inject; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class BsqSwapTakeOfferModel extends BsqSwapOfferModel { + private final TradeManager tradeManager; + private final FilterManager filterManager; + @Getter + private Offer offer; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor + /////////////////////////////////////////////////////////////////////////////////////////// + + @Inject + public BsqSwapTakeOfferModel(OfferUtil offerUtil, + BtcWalletService btcWalletService, + BsqWalletService bsqWalletService, + FeeService feeService, + TradeManager tradeManager, + FilterManager filterManager) { + super(offerUtil, btcWalletService, bsqWalletService, feeService); + this.tradeManager = tradeManager; + this.filterManager = filterManager; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + public void initWithData(Offer offer) { + super.init(offer.getDirection(), false); + + this.offer = offer; + setPrice(offer.getPrice()); + + setBtcAmount(Coin.valueOf(Math.min(offer.getAmount().value, getMaxTradeLimit()))); + calculateVolumeForAmount(getBtcAmount()); + + setMinAmount(offer.getMinAmount()); + calculateMinVolume(); + + offer.resetState(); + } + + public void doActivate() { + super.doActivate(); + tradeManager.checkOfferAvailability(offer, + false, + () -> { + }, + log::error); + } + + public void doDeactivate() { + super.doDeactivate(); + + if (offer != null) { + offer.cancelAvailabilityRequest(); + } + } + + public void applyAmount(Coin amount) { + setBtcAmount(Coin.valueOf(Math.min(amount.value, getMaxTradeLimit()))); + calculateVolume(); + calculateInputAndPayout(); + } + + public void onTakeOffer(TradeResultHandler tradeResultHandler, + ErrorMessageHandler warningHandler, + ErrorMessageHandler errorHandler, + boolean isTakerApiUser) { + if (filterManager.isCurrencyBanned(offer.getCurrencyCode())) { + warningHandler.handleErrorMessage(Res.get("offerbook.warning.currencyBanned")); + } else if (filterManager.isPaymentMethodBanned(offer.getPaymentMethod())) { + warningHandler.handleErrorMessage(Res.get("offerbook.warning.paymentMethodBanned")); + } else if (filterManager.isOfferIdBanned(offer.getId())) { + warningHandler.handleErrorMessage(Res.get("offerbook.warning.offerBlocked")); + } else if (filterManager.isNodeAddressBanned(offer.getMakerNodeAddress())) { + warningHandler.handleErrorMessage(Res.get("offerbook.warning.nodeBlocked")); + } else if (filterManager.requireUpdateToNewVersionForTrading()) { + warningHandler.handleErrorMessage(Res.get("offerbook.warning.requireUpdateToNewVersion")); + } else if (tradeManager.wasOfferAlreadyUsedInTrade(offer.getId())) { + warningHandler.handleErrorMessage(Res.get("offerbook.warning.offerWasAlreadyUsedInTrade")); + } else { + tradeManager.onTakeBsqSwapOffer(offer, + getBtcAmount().get(), + getTxFeePerVbyte(), + getMakerFee().getValue(), + getTakerFee().getValue(), + isTakerApiUser, + tradeResultHandler, + errorHandler + ); + } + } +} diff --git a/core/src/main/java/bisq/core/offer/bsq_swap/OpenBsqSwapOffer.java b/core/src/main/java/bisq/core/offer/bsq_swap/OpenBsqSwapOffer.java new file mode 100644 index 0000000000..13ed697afe --- /dev/null +++ b/core/src/main/java/bisq/core/offer/bsq_swap/OpenBsqSwapOffer.java @@ -0,0 +1,211 @@ +/* + * 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.core.offer.bsq_swap; + +import bisq.core.btc.listeners.BsqBalanceListener; +import bisq.core.btc.wallet.BsqWalletService; +import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.offer.Offer; +import bisq.core.offer.OpenOffer; +import bisq.core.provider.fee.FeeService; +import bisq.core.trade.bsq_swap.BsqSwapCalculation; + +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.InsufficientMoneyException; +import org.bitcoinj.wallet.listeners.WalletChangeEventListener; + +import javafx.beans.InvalidationListener; + +import java.util.Objects; + +import lombok.Getter; +import lombok.experimental.Delegate; +import lombok.extern.slf4j.Slf4j; + +/** + * Wrapper for OpenOffer listening for txFee and wallet changes. + * After a change event we recalculate the required funds and compare it with the available + * wallet funds. If not enough funds we set the bsqSwapOfferHasMissingFunds flag at + * openOffer and call the disableBsqSwapOffer at bsqSwapOpenOfferService. + * If we have been in the disabled state and we have now sufficient funds we call the + * enableBsqSwapOffer at bsqSwapOpenOfferService and update the + * bsqSwapOfferHasMissingFunds. + */ +@Slf4j +class OpenBsqSwapOffer { + @Getter + @Delegate + private final OpenOffer openOffer; + + private final OpenBsqSwapOfferService openBsqSwapOfferService; + private final FeeService feeService; + private final BtcWalletService btcWalletService; + private final BsqWalletService bsqWalletService; + + private final long tradeFee; + private final boolean isBuyOffer; + private final InvalidationListener feeChangeListener; + private final BsqBalanceListener bsqBalanceListener; + private final WalletChangeEventListener btcWalletChangeEventListener; + private final Coin btcAmount; + private final Coin requiredBsqInput; + + // Mutable data + private long txFeePerVbyte; + private Coin walletBalance; + private boolean hasMissingFunds; + + public OpenBsqSwapOffer(OpenOffer openOffer, + OpenBsqSwapOfferService openBsqSwapOfferService, + FeeService feeService, + BtcWalletService btcWalletService, + BsqWalletService bsqWalletService) { + this.openOffer = openOffer; + this.openBsqSwapOfferService = openBsqSwapOfferService; + this.feeService = feeService; + this.btcWalletService = btcWalletService; + this.bsqWalletService = bsqWalletService; + + Offer offer = openOffer.getOffer(); + isBuyOffer = offer.isBuyOffer(); + tradeFee = offer.getMakerFee().getValue(); + + txFeePerVbyte = feeService.getTxFeePerVbyte().getValue(); + feeChangeListener = observable -> { + long newTxFeePerVbyte = feeService.getTxFeePerVbyte().value; + if (newTxFeePerVbyte != this.txFeePerVbyte) { + this.txFeePerVbyte = newTxFeePerVbyte; + evaluateFundedState(); + log.info("Updated because of fee change. txFeePerVbyte={}, hasMissingFunds={}", + txFeePerVbyte, hasMissingFunds); + + } + }; + feeService.feeUpdateCounterProperty().addListener(feeChangeListener); + + if (isBuyOffer) { + + Coin bsqAmount = BsqSwapCalculation.getBsqTradeAmount(Objects.requireNonNull(offer.getVolume())); + requiredBsqInput = BsqSwapCalculation.getBuyersBsqInputValue(bsqAmount.getValue(), tradeFee); + walletBalance = bsqWalletService.getVerifiedBalance(); + bsqBalanceListener = (availableBalance, + availableNonBsqBalance, + unverifiedBalance, + unconfirmedChangeBalance, + lockedForVotingBalance, + lockedInBondsBalance, + unlockingBondsBalance) -> { + if (!walletBalance.equals(availableBalance)) { + walletBalance = bsqWalletService.getVerifiedBalance(); + evaluateFundedState(); + applyFundingState(); + log.info("Updated because of BSQ wallet balance change. walletBalance={}, hasMissingFunds={}", + walletBalance, hasMissingFunds); + } + }; + bsqWalletService.addBsqBalanceListener(bsqBalanceListener); + btcWalletChangeEventListener = null; + btcAmount = null; + } else { + btcAmount = offer.getAmount(); + walletBalance = btcWalletService.getSavingWalletBalance(); + btcWalletChangeEventListener = wallet -> { + Coin newBalance = btcWalletService.getSavingWalletBalance(); + if (!this.walletBalance.equals(newBalance)) { + this.walletBalance = newBalance; + evaluateFundedState(); + applyFundingState(); + log.info("Updated because of BTC wallet balance change. walletBalance={}, hasMissingFunds={}", + walletBalance, hasMissingFunds); + } + }; + btcWalletService.addChangeEventListener(btcWalletChangeEventListener); + bsqBalanceListener = null; + requiredBsqInput = null; + } + + // We might need to reset the state + if (openOffer.isBsqSwapOfferHasMissingFunds()) { + openOffer.setState(OpenOffer.State.AVAILABLE); + openBsqSwapOfferService.requestPersistence(); + } + + evaluateFundedState(); + applyFundingState(); + } + + public void removeListeners() { + feeService.feeUpdateCounterProperty().removeListener(feeChangeListener); + if (isBuyOffer) { + bsqWalletService.removeBsqBalanceListener(bsqBalanceListener); + } else { + btcWalletService.removeChangeEventListener(btcWalletChangeEventListener); + } + } + + // We apply the + public void applyFundingState() { + boolean prev = openOffer.isBsqSwapOfferHasMissingFunds(); + if (hasMissingFunds && !prev) { + openOffer.setBsqSwapOfferHasMissingFunds(true); + openBsqSwapOfferService.requestPersistence(); + + if (!isDeactivated()) { + openBsqSwapOfferService.disableBsqSwapOffer(getOpenOffer()); + } + + } else if (!hasMissingFunds && prev) { + openOffer.setBsqSwapOfferHasMissingFunds(false); + openBsqSwapOfferService.requestPersistence(); + + if (!isDeactivated()) { + openBsqSwapOfferService.enableBsqSwapOffer(getOpenOffer()); + } + } + } + + private void evaluateFundedState() { + if (isBuyOffer) { + hasMissingFunds = walletBalance.isLessThan(requiredBsqInput); + } else { + try { + Coin requiredInput = BsqSwapCalculation.getSellersBtcInputValue(btcWalletService, + btcAmount, + txFeePerVbyte, + tradeFee); + hasMissingFunds = walletBalance.isLessThan(requiredInput); + } catch (InsufficientMoneyException e) { + hasMissingFunds = true; + } + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + OpenBsqSwapOffer that = (OpenBsqSwapOffer) o; + return tradeFee == that.tradeFee && isBuyOffer == that.isBuyOffer && openOffer.equals(that.openOffer) && + btcAmount.equals(that.btcAmount) && requiredBsqInput.equals(that.requiredBsqInput); + } + + @Override + public int hashCode() { + return Objects.hash(openOffer, tradeFee, isBuyOffer, btcAmount, requiredBsqInput); + } +} diff --git a/core/src/main/java/bisq/core/offer/bsq_swap/OpenBsqSwapOfferService.java b/core/src/main/java/bisq/core/offer/bsq_swap/OpenBsqSwapOfferService.java new file mode 100644 index 0000000000..b55a1b70e4 --- /dev/null +++ b/core/src/main/java/bisq/core/offer/bsq_swap/OpenBsqSwapOfferService.java @@ -0,0 +1,396 @@ +/* + * 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.core.offer.bsq_swap; + +import bisq.core.btc.wallet.BsqWalletService; +import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.dao.DaoFacade; +import bisq.core.dao.state.DaoStateListener; +import bisq.core.dao.state.model.blockchain.Block; +import bisq.core.filter.Filter; +import bisq.core.filter.FilterManager; +import bisq.core.monetary.Price; +import bisq.core.offer.Offer; +import bisq.core.offer.OfferBookService; +import bisq.core.offer.OfferDirection; +import bisq.core.offer.OfferPayloadBase; +import bisq.core.offer.OfferUtil; +import bisq.core.offer.OpenOffer; +import bisq.core.offer.OpenOfferManager; +import bisq.core.offer.placeoffer.bsq_swap.PlaceBsqSwapOfferModel; +import bisq.core.offer.placeoffer.bsq_swap.PlaceBsqSwapOfferProtocol; +import bisq.core.payment.payload.PaymentMethod; +import bisq.core.provider.fee.FeeService; + +import bisq.network.p2p.BootstrapListener; +import bisq.network.p2p.NodeAddress; +import bisq.network.p2p.P2PService; + +import bisq.common.UserThread; +import bisq.common.app.Version; +import bisq.common.crypto.HashCashService; +import bisq.common.crypto.PubKeyRing; +import bisq.common.handlers.ErrorMessageHandler; +import bisq.common.handlers.ResultHandler; + +import org.bitcoinj.core.Coin; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import javafx.beans.value.ChangeListener; + +import javafx.collections.ListChangeListener; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import lombok.extern.slf4j.Slf4j; + +import static com.google.common.base.Preconditions.checkArgument; + +@Slf4j +@Singleton +public class OpenBsqSwapOfferService { + private final OpenOfferManager openOfferManager; + private final BtcWalletService btcWalletService; + private final BsqWalletService bsqWalletService; + private final FeeService feeService; + private final P2PService p2PService; + private final DaoFacade daoFacade; + private final OfferBookService offerBookService; + private final OfferUtil offerUtil; + private final FilterManager filterManager; + private final PubKeyRing pubKeyRing; + + private final Map openBsqSwapOffersById = new HashMap<>(); + private final ListChangeListener offerListChangeListener; + private final ChangeListener filterChangeListener; + private final DaoStateListener daoStateListener; + private final BootstrapListener bootstrapListener; + + @Inject + public OpenBsqSwapOfferService(OpenOfferManager openOfferManager, + BtcWalletService btcWalletService, + BsqWalletService bsqWalletService, + FeeService feeService, + P2PService p2PService, + DaoFacade daoFacade, + OfferBookService offerBookService, + OfferUtil offerUtil, + FilterManager filterManager, + PubKeyRing pubKeyRing) { + this.openOfferManager = openOfferManager; + this.btcWalletService = btcWalletService; + this.bsqWalletService = bsqWalletService; + this.feeService = feeService; + this.p2PService = p2PService; + this.daoFacade = daoFacade; + this.offerBookService = offerBookService; + this.offerUtil = offerUtil; + this.filterManager = filterManager; + this.pubKeyRing = pubKeyRing; + + offerListChangeListener = c -> { + c.next(); + if (c.wasAdded()) { + onOpenOffersAdded(c.getAddedSubList()); + } else if (c.wasRemoved()) { + onOpenOffersRemoved(c.getRemoved()); + } + }; + bootstrapListener = new BootstrapListener() { + @Override + public void onUpdatedDataReceived() { + onP2PServiceReady(); + p2PService.removeP2PServiceListener(bootstrapListener); + } + }; + daoStateListener = new DaoStateListener() { + @Override + public void onParseBlockCompleteAfterBatchProcessing(Block block) { + // The balance gets updated at the same event handler but we do not know which handler + // gets called first, so we delay here a bit to be sure the balance is set + UserThread.runAfter(() -> { + onDaoReady(); + daoFacade.removeBsqStateListener(daoStateListener); + }, 100, TimeUnit.MILLISECONDS); + } + }; + filterChangeListener = (observable, oldValue, newValue) -> { + if (newValue != null) { + onProofOfWorkDifficultyChanged(); + } + }; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + public void onAllServicesInitialized() { + if (p2PService.isBootstrapped()) { + onP2PServiceReady(); + } else { + p2PService.addP2PServiceListener(bootstrapListener); + } + } + + private void onP2PServiceReady() { + if (daoFacade.isParseBlockChainComplete()) { + onDaoReady(); + } else { + daoFacade.addBsqStateListener(daoStateListener); + } + } + + private void onDaoReady() { + filterManager.filterProperty().addListener(filterChangeListener); + openOfferManager.getObservableList().addListener(offerListChangeListener); + onOpenOffersAdded(openOfferManager.getObservableList()); + } + + public void shutDown() { + openOfferManager.getObservableList().removeListener(offerListChangeListener); + p2PService.removeP2PServiceListener(bootstrapListener); + daoFacade.removeBsqStateListener(daoStateListener); + filterManager.filterProperty().removeListener(filterChangeListener); + } + + public void requestNewOffer(String offerId, + OfferDirection direction, + Coin amount, + Coin minAmount, + Price price, + Consumer resultHandler) { + log.info("offerId={}, \n" + + "direction={}, \n" + + "price={}, \n" + + "amount={}, \n" + + "minAmount={}, \n", + offerId, + direction, + price.getValue(), + amount.value, + minAmount.value); + + NodeAddress makerAddress = p2PService.getAddress(); + offerUtil.validateBasicOfferData(PaymentMethod.BSQ_SWAP, "BSQ"); + + byte[] payload = HashCashService.getBytes(offerId); + byte[] challenge = HashCashService.getBytes(offerId + Objects.requireNonNull(makerAddress)); + int difficulty = getPowDifficulty(); + HashCashService.mint(payload, challenge, difficulty) + .whenComplete((proofOfWork, throwable) -> { + // We got called from a non user thread... + UserThread.execute(() -> { + if (throwable != null) { + log.error(throwable.toString()); + return; + } + + BsqSwapOfferPayload bsqSwapOfferPayload = new BsqSwapOfferPayload(offerId, + new Date().getTime(), + makerAddress, + pubKeyRing, + direction, + price.getValue(), + amount.getValue(), + minAmount.getValue(), + proofOfWork, + null, + Version.VERSION, + Version.TRADE_PROTOCOL_VERSION); + resultHandler.accept(new Offer(bsqSwapOfferPayload)); + }); + }); + } + + public void placeBsqSwapOffer(Offer offer, + Runnable resultHandler, + ErrorMessageHandler errorMessageHandler) { + checkArgument(offer.isBsqSwapOffer()); + PlaceBsqSwapOfferModel model = new PlaceBsqSwapOfferModel(offer, offerBookService); + PlaceBsqSwapOfferProtocol protocol = new PlaceBsqSwapOfferProtocol(model, + () -> { + OpenOffer openOffer = new OpenOffer(offer); + openOfferManager.addOpenBsqSwapOffer(openOffer); + resultHandler.run(); + }, + errorMessageHandler + ); + protocol.placeOffer(); + } + + public void activateOpenOffer(OpenOffer openOffer, + ResultHandler resultHandler, + ErrorMessageHandler errorMessageHandler) { + if (isProofOfWorkInvalid(openOffer.getOffer())) { + redoProofOrWorkAndRepublish(openOffer); + return; + } + + openOfferManager.activateOpenOffer(openOffer, resultHandler, errorMessageHandler); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Package scope + /////////////////////////////////////////////////////////////////////////////////////////// + + void requestPersistence() { + openOfferManager.requestPersistence(); + } + + void enableBsqSwapOffer(OpenOffer openOffer) { + if (isProofOfWorkInvalid(openOffer.getOffer())) { + redoProofOrWorkAndRepublish(openOffer); + return; + } + + offerBookService.addOffer(openOffer.getOffer(), + () -> { + openOffer.setState(OpenOffer.State.AVAILABLE); + openOfferManager.requestPersistence(); + log.info("enableBsqSwapOffer{}", openOffer.getShortId()); + }, + errorMessage -> log.warn("Failed to enableBsqSwapOffer {}", openOffer.getShortId())); + } + + void disableBsqSwapOffer(OpenOffer openOffer) { + OfferPayloadBase offerPayloadBase = openOffer.getOffer().getOfferPayloadBase(); + offerBookService.removeOffer(offerPayloadBase, + () -> log.info("disableBsqSwapOffer {}", openOffer.getShortId()), + errorMessage -> log.warn("Failed to disableBsqSwapOffer {}", openOffer.getShortId())); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Handlers + /////////////////////////////////////////////////////////////////////////////////////////// + + private void onOpenOffersAdded(List list) { + list.stream() + .filter(openOffer -> openOffer.getOffer().isBsqSwapOffer()) + .filter(openOffer -> !openOffer.isDeactivated()) + .forEach(openOffer -> { + if (isProofOfWorkInvalid(openOffer.getOffer())) { + // Avoiding ConcurrentModificationException + UserThread.execute(() -> redoProofOrWorkAndRepublish(openOffer)); + } else { + OpenBsqSwapOffer openBsqSwapOffer = new OpenBsqSwapOffer(openOffer, + this, + feeService, + btcWalletService, + bsqWalletService); + String offerId = openOffer.getId(); + if (openBsqSwapOffersById.containsKey(offerId)) { + openBsqSwapOffersById.get(offerId).removeListeners(); + } + openBsqSwapOffersById.put(offerId, openBsqSwapOffer); + openBsqSwapOffer.applyFundingState(); + } + }); + } + + private void onOpenOffersRemoved(List list) { + list.stream() + .filter(openOffer -> openOffer.getOffer().isBsqSwapOffer()) + .map(OpenOffer::getId) + .forEach(offerId -> { + if (openBsqSwapOffersById.containsKey(offerId)) { + openBsqSwapOffersById.get(offerId).removeListeners(); + openBsqSwapOffersById.remove(offerId); + } + }); + } + + private void onProofOfWorkDifficultyChanged() { + openBsqSwapOffersById.values().stream() + .filter(openBsqSwapOffer -> !openBsqSwapOffer.isDeactivated()) + .filter(openBsqSwapOffer -> !openBsqSwapOffer.isBsqSwapOfferHasMissingFunds()) + .filter(openBsqSwapOffer -> isProofOfWorkInvalid(openBsqSwapOffer.getOffer())) + .forEach(openBsqSwapOffer -> { + // Avoiding ConcurrentModificationException + UserThread.execute(() -> redoProofOrWorkAndRepublish(openBsqSwapOffer.getOpenOffer())); + }); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Proof of work + /////////////////////////////////////////////////////////////////////////////////////////// + + private void redoProofOrWorkAndRepublish(OpenOffer openOffer) { + // This triggers our onOpenOffersRemoved handler so we dont handle removal here + openOfferManager.removeOpenOffer(openOffer); + + String newOfferId = OfferUtil.getOfferIdWithMutationCounter(openOffer.getId()); + byte[] payload = HashCashService.getBytes(newOfferId); + NodeAddress nodeAddress = Objects.requireNonNull(openOffer.getOffer().getMakerNodeAddress()); + byte[] challenge = HashCashService.getBytes(newOfferId + nodeAddress); + int difficulty = getPowDifficulty(); + HashCashService.mint(payload, challenge, difficulty) + .whenComplete((proofOfWork, throwable) -> { + // We got called from a non user thread... + UserThread.execute(() -> { + if (throwable != null) { + log.error(throwable.toString()); + return; + } + // We mutate the offerId with a postfix counting the mutations to get a new unique id. + // This helps to avoid issues with getting added/removed at some delayed moment the offer + + BsqSwapOfferPayload newPayload = BsqSwapOfferPayload.from(openOffer.getBsqSwapOfferPayload(), + newOfferId, + proofOfWork); + Offer newOffer = new Offer(newPayload); + newOffer.setState(Offer.State.AVAILABLE); + + checkArgument(!openOffer.isDeactivated(), + "We must not get called at redoProofOrWorkAndRepublish if offer was deactivated"); + OpenOffer newOpenOffer = new OpenOffer(newOffer, OpenOffer.State.AVAILABLE); + if (!newOpenOffer.isDeactivated()) { + openOfferManager.maybeRepublishOffer(newOpenOffer); + } + // This triggers our onOpenOffersAdded handler so we dont handle adding to our list here + openOfferManager.addOpenBsqSwapOffer(newOpenOffer); + }); + }); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Utils + /////////////////////////////////////////////////////////////////////////////////////////// + + private boolean isProofOfWorkInvalid(Offer offer) { + return !filterManager.isProofOfWorkValid(offer); + } + + + private int getPowDifficulty() { + return filterManager.getFilter() != null ? filterManager.getFilter().getPowDifficulty() : 0; + } +} diff --git a/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferModel.java b/core/src/main/java/bisq/core/offer/placeoffer/bisq_v1/PlaceOfferModel.java similarity index 98% rename from core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferModel.java rename to core/src/main/java/bisq/core/offer/placeoffer/bisq_v1/PlaceOfferModel.java index 0c54d733ed..e90e4dcc00 100644 --- a/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferModel.java +++ b/core/src/main/java/bisq/core/offer/placeoffer/bisq_v1/PlaceOfferModel.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.offer.placeoffer; +package bisq.core.offer.placeoffer.bisq_v1; import bisq.core.btc.wallet.BsqWalletService; import bisq.core.btc.wallet.BtcWalletService; diff --git a/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferProtocol.java b/core/src/main/java/bisq/core/offer/placeoffer/bisq_v1/PlaceOfferProtocol.java similarity index 88% rename from core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferProtocol.java rename to core/src/main/java/bisq/core/offer/placeoffer/bisq_v1/PlaceOfferProtocol.java index cdf394435f..b3f50a95e5 100644 --- a/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferProtocol.java +++ b/core/src/main/java/bisq/core/offer/placeoffer/bisq_v1/PlaceOfferProtocol.java @@ -15,13 +15,13 @@ * along with Bisq. If not, see . */ -package bisq.core.offer.placeoffer; +package bisq.core.offer.placeoffer.bisq_v1; -import bisq.core.offer.placeoffer.tasks.AddToOfferBook; -import bisq.core.offer.placeoffer.tasks.CheckNumberOfUnconfirmedTransactions; -import bisq.core.offer.placeoffer.tasks.CreateMakerFeeTx; -import bisq.core.offer.placeoffer.tasks.ValidateOffer; -import bisq.core.trade.handlers.TransactionResultHandler; +import bisq.core.offer.placeoffer.bisq_v1.tasks.AddToOfferBook; +import bisq.core.offer.placeoffer.bisq_v1.tasks.CheckNumberOfUnconfirmedTransactions; +import bisq.core.offer.placeoffer.bisq_v1.tasks.CreateMakerFeeTx; +import bisq.core.offer.placeoffer.bisq_v1.tasks.ValidateOffer; +import bisq.core.trade.bisq_v1.TransactionResultHandler; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.taskrunner.TaskRunner; @@ -65,7 +65,7 @@ public class PlaceOfferProtocol { log.error(errorMessage); if (model.isOfferAddedToOfferBook()) { - model.getOfferBookService().removeOffer(model.getOffer().getOfferPayload(), + model.getOfferBookService().removeOffer(model.getOffer().getOfferPayloadBase(), () -> { model.setOfferAddedToOfferBook(false); log.debug("OfferPayload removed from offer book."); diff --git a/core/src/main/java/bisq/core/offer/placeoffer/tasks/AddToOfferBook.java b/core/src/main/java/bisq/core/offer/placeoffer/bisq_v1/tasks/AddToOfferBook.java similarity index 94% rename from core/src/main/java/bisq/core/offer/placeoffer/tasks/AddToOfferBook.java rename to core/src/main/java/bisq/core/offer/placeoffer/bisq_v1/tasks/AddToOfferBook.java index 16def612ba..f9314ef31b 100644 --- a/core/src/main/java/bisq/core/offer/placeoffer/tasks/AddToOfferBook.java +++ b/core/src/main/java/bisq/core/offer/placeoffer/bisq_v1/tasks/AddToOfferBook.java @@ -15,9 +15,9 @@ * along with Bisq. If not, see . */ -package bisq.core.offer.placeoffer.tasks; +package bisq.core.offer.placeoffer.bisq_v1.tasks; -import bisq.core.offer.placeoffer.PlaceOfferModel; +import bisq.core.offer.placeoffer.bisq_v1.PlaceOfferModel; import bisq.common.taskrunner.Task; import bisq.common.taskrunner.TaskRunner; diff --git a/core/src/main/java/bisq/core/offer/placeoffer/tasks/CheckNumberOfUnconfirmedTransactions.java b/core/src/main/java/bisq/core/offer/placeoffer/bisq_v1/tasks/CheckNumberOfUnconfirmedTransactions.java similarity index 85% rename from core/src/main/java/bisq/core/offer/placeoffer/tasks/CheckNumberOfUnconfirmedTransactions.java rename to core/src/main/java/bisq/core/offer/placeoffer/bisq_v1/tasks/CheckNumberOfUnconfirmedTransactions.java index 31e8d30003..689ad265c1 100644 --- a/core/src/main/java/bisq/core/offer/placeoffer/tasks/CheckNumberOfUnconfirmedTransactions.java +++ b/core/src/main/java/bisq/core/offer/placeoffer/bisq_v1/tasks/CheckNumberOfUnconfirmedTransactions.java @@ -1,7 +1,7 @@ -package bisq.core.offer.placeoffer.tasks; +package bisq.core.offer.placeoffer.bisq_v1.tasks; import bisq.core.locale.Res; -import bisq.core.offer.placeoffer.PlaceOfferModel; +import bisq.core.offer.placeoffer.bisq_v1.PlaceOfferModel; import bisq.common.taskrunner.Task; import bisq.common.taskrunner.TaskRunner; diff --git a/core/src/main/java/bisq/core/offer/placeoffer/tasks/CreateMakerFeeTx.java b/core/src/main/java/bisq/core/offer/placeoffer/bisq_v1/tasks/CreateMakerFeeTx.java similarity index 98% rename from core/src/main/java/bisq/core/offer/placeoffer/tasks/CreateMakerFeeTx.java rename to core/src/main/java/bisq/core/offer/placeoffer/bisq_v1/tasks/CreateMakerFeeTx.java index df69713431..eeb21bda5c 100644 --- a/core/src/main/java/bisq/core/offer/placeoffer/tasks/CreateMakerFeeTx.java +++ b/core/src/main/java/bisq/core/offer/placeoffer/bisq_v1/tasks/CreateMakerFeeTx.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.offer.placeoffer.tasks; +package bisq.core.offer.placeoffer.bisq_v1.tasks; import bisq.core.btc.exceptions.TxBroadcastException; import bisq.core.btc.model.AddressEntry; @@ -27,7 +27,7 @@ import bisq.core.btc.wallet.WalletService; import bisq.core.dao.exceptions.DaoDisabledException; import bisq.core.dao.state.model.blockchain.TxType; import bisq.core.offer.Offer; -import bisq.core.offer.placeoffer.PlaceOfferModel; +import bisq.core.offer.placeoffer.bisq_v1.PlaceOfferModel; import bisq.core.util.FeeReceiverSelector; import bisq.common.UserThread; @@ -119,7 +119,7 @@ public class CreateMakerFeeTx extends Task { model.isUseSavingsWallet(), offer.getTxFee()); - Transaction signedTx = model.getBsqWalletService().signTx(txWithBsqFee); + Transaction signedTx = model.getBsqWalletService().signTxAndVerifyNoDustOutputs(txWithBsqFee); WalletService.checkAllScriptSignaturesForTx(signedTx); bsqWalletService.commitTx(signedTx, TxType.PAY_TRADE_FEE); // We need to create another instance, otherwise the tx would trigger an invalid state exception diff --git a/core/src/main/java/bisq/core/offer/placeoffer/tasks/ValidateOffer.java b/core/src/main/java/bisq/core/offer/placeoffer/bisq_v1/tasks/ValidateOffer.java similarity index 96% rename from core/src/main/java/bisq/core/offer/placeoffer/tasks/ValidateOffer.java rename to core/src/main/java/bisq/core/offer/placeoffer/bisq_v1/tasks/ValidateOffer.java index 35d619feb4..e5494349e7 100644 --- a/core/src/main/java/bisq/core/offer/placeoffer/tasks/ValidateOffer.java +++ b/core/src/main/java/bisq/core/offer/placeoffer/bisq_v1/tasks/ValidateOffer.java @@ -15,11 +15,11 @@ * along with Bisq. If not, see . */ -package bisq.core.offer.placeoffer.tasks; +package bisq.core.offer.placeoffer.bisq_v1.tasks; import bisq.core.offer.Offer; -import bisq.core.offer.placeoffer.PlaceOfferModel; -import bisq.core.trade.messages.TradeMessage; +import bisq.core.offer.placeoffer.bisq_v1.PlaceOfferModel; +import bisq.core.trade.protocol.TradeMessage; import bisq.common.taskrunner.Task; import bisq.common.taskrunner.TaskRunner; @@ -40,6 +40,8 @@ public class ValidateOffer extends Task { try { runInterceptHook(); + checkArgument(!offer.isBsqSwapOffer()); + // Coins checkCoinNotNullOrZero(offer.getAmount(), "Amount"); checkCoinNotNullOrZero(offer.getMinAmount(), "MinAmount"); diff --git a/core/src/main/java/bisq/core/offer/placeoffer/bsq_swap/PlaceBsqSwapOfferModel.java b/core/src/main/java/bisq/core/offer/placeoffer/bsq_swap/PlaceBsqSwapOfferModel.java new file mode 100644 index 0000000000..a568161635 --- /dev/null +++ b/core/src/main/java/bisq/core/offer/placeoffer/bsq_swap/PlaceBsqSwapOfferModel.java @@ -0,0 +1,46 @@ +/* + * 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.core.offer.placeoffer.bsq_swap; + +import bisq.core.offer.Offer; +import bisq.core.offer.OfferBookService; + +import bisq.common.taskrunner.Model; + +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Getter +public class PlaceBsqSwapOfferModel implements Model { + private final Offer offer; + private final OfferBookService offerBookService; + + @Setter + private boolean offerAddedToOfferBook; + + public PlaceBsqSwapOfferModel(Offer offer, OfferBookService offerBookService) { + this.offer = offer; + this.offerBookService = offerBookService; + } + + @Override + public void onComplete() { + } +} diff --git a/core/src/main/java/bisq/core/offer/placeoffer/bsq_swap/PlaceBsqSwapOfferProtocol.java b/core/src/main/java/bisq/core/offer/placeoffer/bsq_swap/PlaceBsqSwapOfferProtocol.java new file mode 100644 index 0000000000..a1da128c24 --- /dev/null +++ b/core/src/main/java/bisq/core/offer/placeoffer/bsq_swap/PlaceBsqSwapOfferProtocol.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.core.offer.placeoffer.bsq_swap; + +import bisq.core.offer.placeoffer.bsq_swap.tasks.AddBsqSwapOfferToOfferBook; +import bisq.core.offer.placeoffer.bsq_swap.tasks.ValidateBsqSwapOffer; + +import bisq.common.handlers.ErrorMessageHandler; +import bisq.common.taskrunner.TaskRunner; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PlaceBsqSwapOfferProtocol { + private static final Logger log = LoggerFactory.getLogger(PlaceBsqSwapOfferProtocol.class); + + private final PlaceBsqSwapOfferModel model; + private final Runnable resultHandler; + private final ErrorMessageHandler errorMessageHandler; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor + /////////////////////////////////////////////////////////////////////////////////////////// + + public PlaceBsqSwapOfferProtocol(PlaceBsqSwapOfferModel model, + Runnable resultHandler, + ErrorMessageHandler errorMessageHandler) { + this.model = model; + this.resultHandler = resultHandler; + this.errorMessageHandler = errorMessageHandler; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Called from UI + /////////////////////////////////////////////////////////////////////////////////////////// + + public void placeOffer() { + log.debug("model.offer.id" + model.getOffer().getId()); + TaskRunner taskRunner = new TaskRunner<>(model, + () -> { + log.debug("sequence at handleRequestTakeOfferMessage completed"); + resultHandler.run(); + }, + (errorMessage) -> { + log.error(errorMessage); + + if (model.isOfferAddedToOfferBook()) { + model.getOfferBookService().removeOffer(model.getOffer().getOfferPayloadBase(), + () -> { + model.setOfferAddedToOfferBook(false); + log.debug("OfferPayload removed from offer book."); + }, + log::error); + } + model.getOffer().setErrorMessage(errorMessage); + errorMessageHandler.handleErrorMessage(errorMessage); + } + ); + taskRunner.addTasks( + ValidateBsqSwapOffer.class, + AddBsqSwapOfferToOfferBook.class + ); + + taskRunner.run(); + } +} diff --git a/core/src/main/java/bisq/core/offer/placeoffer/bsq_swap/tasks/AddBsqSwapOfferToOfferBook.java b/core/src/main/java/bisq/core/offer/placeoffer/bsq_swap/tasks/AddBsqSwapOfferToOfferBook.java new file mode 100644 index 0000000000..5824a58191 --- /dev/null +++ b/core/src/main/java/bisq/core/offer/placeoffer/bsq_swap/tasks/AddBsqSwapOfferToOfferBook.java @@ -0,0 +1,55 @@ +/* + * 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.core.offer.placeoffer.bsq_swap.tasks; + +import bisq.core.offer.placeoffer.bisq_v1.PlaceOfferModel; +import bisq.core.offer.placeoffer.bsq_swap.PlaceBsqSwapOfferModel; + +import bisq.common.taskrunner.Task; +import bisq.common.taskrunner.TaskRunner; + +public class AddBsqSwapOfferToOfferBook extends Task { + + public AddBsqSwapOfferToOfferBook(TaskRunner taskHandler, PlaceBsqSwapOfferModel model) { + super(taskHandler, model); + } + + @Override + protected void run() { + try { + runInterceptHook(); + model.getOfferBookService().addOffer(model.getOffer(), + () -> { + model.setOfferAddedToOfferBook(true); + complete(); + }, + errorMessage -> { + model.getOffer().setErrorMessage("Could not add offer to offerbook.\n" + + "Please check your network connection and try again."); + + failed(errorMessage); + }); + } catch (Throwable t) { + model.getOffer().setErrorMessage("An error occurred.\n" + + "Error message:\n" + + t.getMessage()); + + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/offer/placeoffer/bsq_swap/tasks/ValidateBsqSwapOffer.java b/core/src/main/java/bisq/core/offer/placeoffer/bsq_swap/tasks/ValidateBsqSwapOffer.java new file mode 100644 index 0000000000..465a916eea --- /dev/null +++ b/core/src/main/java/bisq/core/offer/placeoffer/bsq_swap/tasks/ValidateBsqSwapOffer.java @@ -0,0 +1,80 @@ +/* + * 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.core.offer.placeoffer.bsq_swap.tasks; + +import bisq.core.offer.Offer; +import bisq.core.offer.placeoffer.bsq_swap.PlaceBsqSwapOfferModel; + +import bisq.common.taskrunner.Task; +import bisq.common.taskrunner.TaskRunner; + +import org.bitcoinj.core.Coin; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +public class ValidateBsqSwapOffer extends Task { + public ValidateBsqSwapOffer(TaskRunner taskHandler, PlaceBsqSwapOfferModel model) { + super(taskHandler, model); + } + + @Override + protected void run() { + Offer offer = model.getOffer(); + try { + runInterceptHook(); + checkArgument(offer.isBsqSwapOffer(), + "Offer must be BsqSwapOfferPayload"); + // Coins + checkCoinNotNullOrZero(offer.getAmount(), "Amount"); + checkCoinNotNullOrZero(offer.getMinAmount(), "MinAmount"); + + checkArgument(offer.getAmount().compareTo(offer.getPaymentMethod().getMaxTradeLimitAsCoin(offer.getCurrencyCode())) <= 0, + "Amount is larger than " + offer.getPaymentMethod().getMaxTradeLimitAsCoin(offer.getCurrencyCode()).toFriendlyString()); + checkArgument(offer.getAmount().compareTo(offer.getMinAmount()) >= 0, "MinAmount is larger than Amount"); + + checkNotNull(offer.getPrice(), "Price is null"); + checkArgument(offer.getPrice().isPositive(), + "Price must be positive. price=" + offer.getPrice().toFriendlyString()); + + checkArgument(offer.getDate().getTime() > 0, + "Date must not be 0. date=" + offer.getDate().toString()); + + checkNotNull(offer.getCurrencyCode(), "Currency is null"); + checkNotNull(offer.getDirection(), "Direction is null"); + checkNotNull(offer.getId(), "Id is null"); + checkNotNull(offer.getPubKeyRing(), "pubKeyRing is null"); + checkNotNull(offer.getMinAmount(), "MinAmount is null"); + checkNotNull(offer.getPrice(), "Price is null"); + checkNotNull(offer.getVersionNr(), "VersionNr is null"); + + complete(); + } catch (Exception e) { + offer.setErrorMessage("An error occurred.\n" + + "Error message:\n" + + e.getMessage()); + failed(e); + } + } + + public static void checkCoinNotNullOrZero(Coin value, String name) { + checkNotNull(value, name + " is null"); + checkArgument(value.isPositive(), + name + " must be positive. " + name + "=" + value.toFriendlyString()); + } +} diff --git a/core/src/main/java/bisq/core/payment/BsqSwapAccount.java b/core/src/main/java/bisq/core/payment/BsqSwapAccount.java new file mode 100644 index 0000000000..bc5899d534 --- /dev/null +++ b/core/src/main/java/bisq/core/payment/BsqSwapAccount.java @@ -0,0 +1,50 @@ +/* + * 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.core.payment; + +import bisq.core.payment.payload.BsqSwapAccountPayload; +import bisq.core.payment.payload.PaymentAccountPayload; +import bisq.core.payment.payload.PaymentMethod; + +import java.util.Date; + +import lombok.EqualsAndHashCode; + +// Placeholder account for Bsq swaps. We do not hold any data here, its just used to fit into the +// standard domain. We mimic the different trade protocol as a payment method with a dedicated account. +@EqualsAndHashCode(callSuper = true) +public final class BsqSwapAccount extends PaymentAccount { + public static final String ID = "BsqSwapAccount"; + + public BsqSwapAccount() { + super(PaymentMethod.BSQ_SWAP); + } + + @Override + public void init() { + id = ID; + creationDate = new Date().getTime(); + paymentAccountPayload = createPayload(); + } + + @Override + protected PaymentAccountPayload createPayload() { + return new BsqSwapAccountPayload(paymentMethod.getId(), id); + } + +} diff --git a/core/src/main/java/bisq/core/payment/PaymentAccountFactory.java b/core/src/main/java/bisq/core/payment/PaymentAccountFactory.java index d176498054..3979eeac09 100644 --- a/core/src/main/java/bisq/core/payment/PaymentAccountFactory.java +++ b/core/src/main/java/bisq/core/payment/PaymentAccountFactory.java @@ -120,6 +120,8 @@ public class PaymentAccountFactory { return new StrikeAccount(); case PaymentMethod.SWIFT_ID: return new SwiftAccount(); + case PaymentMethod.BSQ_SWAP_ID: + return new BsqSwapAccount(); // Cannot be deleted as it would break old trade history entries case PaymentMethod.OK_PAY_ID: diff --git a/core/src/main/java/bisq/core/payment/payload/BsqSwapAccountPayload.java b/core/src/main/java/bisq/core/payment/payload/BsqSwapAccountPayload.java new file mode 100644 index 0000000000..ce8f892ddd --- /dev/null +++ b/core/src/main/java/bisq/core/payment/payload/BsqSwapAccountPayload.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.core.payment.payload; + +import bisq.core.locale.Res; + +import com.google.protobuf.Message; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; + +@EqualsAndHashCode(callSuper = true) +@ToString +@Setter +@Getter +@Slf4j +public final class BsqSwapAccountPayload extends PaymentAccountPayload { + + public BsqSwapAccountPayload(String paymentMethod, String id) { + super(paymentMethod, id); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public Message toProtoMessage() { + return getPaymentAccountPayloadBuilder() + .setBsqSwapAccountPayload(protobuf.BsqSwapAccountPayload.newBuilder()) + .build(); + } + + public static BsqSwapAccountPayload fromProto(protobuf.PaymentAccountPayload proto) { + return new BsqSwapAccountPayload(proto.getPaymentMethodId(), proto.getId()); + } + + @Override + public String getPaymentDetails() { + return Res.getWithCol("shared.na"); + } + + @Override + public String getPaymentDetailsForTradePopup() { + return getPaymentDetails(); + } + + @Override + public byte[] getAgeWitnessInputData() { + return super.getAgeWitnessInputData(new byte[]{}); + } +} diff --git a/core/src/main/java/bisq/core/payment/payload/PaymentMethod.java b/core/src/main/java/bisq/core/payment/payload/PaymentMethod.java index e6da6be373..ee9f523bf4 100644 --- a/core/src/main/java/bisq/core/payment/payload/PaymentMethod.java +++ b/core/src/main/java/bisq/core/payment/payload/PaymentMethod.java @@ -116,6 +116,7 @@ public final class PaymentMethod implements PersistablePayload, Comparable tradeCurrencies) { return tradeCurrencies.stream() .anyMatch(tradeCurrency -> hasChargebackRisk(paymentMethod, tradeCurrency.getCode())); diff --git a/core/src/main/java/bisq/core/proto/CoreProtoResolver.java b/core/src/main/java/bisq/core/proto/CoreProtoResolver.java index f76d38fdec..89d6cd4ed3 100644 --- a/core/src/main/java/bisq/core/proto/CoreProtoResolver.java +++ b/core/src/main/java/bisq/core/proto/CoreProtoResolver.java @@ -26,6 +26,7 @@ import bisq.core.payment.payload.AliPayAccountPayload; import bisq.core.payment.payload.AmazonGiftCardAccountPayload; import bisq.core.payment.payload.AustraliaPayidPayload; import bisq.core.payment.payload.BizumAccountPayload; +import bisq.core.payment.payload.BsqSwapAccountPayload; import bisq.core.payment.payload.CapitualAccountPayload; import bisq.core.payment.payload.CashAppAccountPayload; import bisq.core.payment.payload.CashByMailAccountPayload; @@ -218,6 +219,8 @@ public class CoreProtoResolver implements ProtoResolver { return VerseAccountPayload.fromProto(proto); case SWIFT_ACCOUNT_PAYLOAD: return SwiftAccountPayload.fromProto(proto); + case BSQ_SWAP_ACCOUNT_PAYLOAD: + return BsqSwapAccountPayload.fromProto(proto); // Cannot be deleted as it would break old trade history entries case O_K_PAY_ACCOUNT_PAYLOAD: diff --git a/core/src/main/java/bisq/core/proto/ProtoDevUtil.java b/core/src/main/java/bisq/core/proto/ProtoDevUtil.java index 6b70441a86..52af1c7e50 100644 --- a/core/src/main/java/bisq/core/proto/ProtoDevUtil.java +++ b/core/src/main/java/bisq/core/proto/ProtoDevUtil.java @@ -18,12 +18,12 @@ package bisq.core.proto; import bisq.core.btc.model.AddressEntry; -import bisq.core.support.dispute.DisputeResult; -import bisq.core.offer.AvailabilityResult; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferDirection; import bisq.core.offer.OpenOffer; -import bisq.core.trade.Trade; +import bisq.core.offer.availability.AvailabilityResult; +import bisq.core.support.dispute.DisputeResult; +import bisq.core.trade.model.bisq_v1.Trade; import lombok.extern.slf4j.Slf4j; @@ -81,8 +81,8 @@ public class ProtoDevUtil { sb.append(" enum Direction {\n"); sb.append(" PB_ERROR = 0;\n"); - for (int i = 0; i < OfferPayload.Direction.values().length; i++) { - OfferPayload.Direction s = OfferPayload.Direction.values()[i]; + for (int i = 0; i < OfferDirection.values().length; i++) { + OfferDirection s = OfferDirection.values()[i]; sb.append(" "); sb.append(s.toString()); sb.append(" = "); diff --git a/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java b/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java index 21fff96737..9d6a68584d 100644 --- a/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java +++ b/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java @@ -36,9 +36,10 @@ import bisq.core.dao.node.messages.NewBlockBroadcastMessage; import bisq.core.filter.Filter; import bisq.core.network.p2p.inventory.messages.GetInventoryRequest; import bisq.core.network.p2p.inventory.messages.GetInventoryResponse; -import bisq.core.offer.OfferPayload; -import bisq.core.offer.messages.OfferAvailabilityRequest; -import bisq.core.offer.messages.OfferAvailabilityResponse; +import bisq.core.offer.availability.messages.OfferAvailabilityRequest; +import bisq.core.offer.availability.messages.OfferAvailabilityResponse; +import bisq.core.offer.bisq_v1.OfferPayload; +import bisq.core.offer.bsq_swap.BsqSwapOfferPayload; import bisq.core.proto.CoreProtoResolver; import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator; import bisq.core.support.dispute.arbitration.messages.PeerPublishedDisputePayoutTxMessage; @@ -48,20 +49,25 @@ import bisq.core.support.dispute.messages.OpenNewDisputeMessage; import bisq.core.support.dispute.messages.PeerOpenedDisputeMessage; import bisq.core.support.dispute.refund.refundagent.RefundAgent; import bisq.core.support.messages.ChatMessage; -import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage; -import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest; -import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse; -import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage; -import bisq.core.trade.messages.DepositTxMessage; -import bisq.core.trade.messages.InputsForDepositTxRequest; -import bisq.core.trade.messages.InputsForDepositTxResponse; -import bisq.core.trade.messages.MediatedPayoutTxPublishedMessage; -import bisq.core.trade.messages.MediatedPayoutTxSignatureMessage; -import bisq.core.trade.messages.PayoutTxPublishedMessage; -import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage; -import bisq.core.trade.messages.RefreshTradeStateRequest; -import bisq.core.trade.messages.ShareBuyerPaymentAccountMessage; -import bisq.core.trade.messages.TraderSignedWitnessMessage; +import bisq.core.trade.protocol.bisq_v1.messages.CounterCurrencyTransferStartedMessage; +import bisq.core.trade.protocol.bisq_v1.messages.DelayedPayoutTxSignatureRequest; +import bisq.core.trade.protocol.bisq_v1.messages.DelayedPayoutTxSignatureResponse; +import bisq.core.trade.protocol.bisq_v1.messages.DepositTxAndDelayedPayoutTxMessage; +import bisq.core.trade.protocol.bisq_v1.messages.DepositTxMessage; +import bisq.core.trade.protocol.bisq_v1.messages.InputsForDepositTxRequest; +import bisq.core.trade.protocol.bisq_v1.messages.InputsForDepositTxResponse; +import bisq.core.trade.protocol.bisq_v1.messages.MediatedPayoutTxPublishedMessage; +import bisq.core.trade.protocol.bisq_v1.messages.MediatedPayoutTxSignatureMessage; +import bisq.core.trade.protocol.bisq_v1.messages.PayoutTxPublishedMessage; +import bisq.core.trade.protocol.bisq_v1.messages.PeerPublishedDelayedPayoutTxMessage; +import bisq.core.trade.protocol.bisq_v1.messages.RefreshTradeStateRequest; +import bisq.core.trade.protocol.bisq_v1.messages.ShareBuyerPaymentAccountMessage; +import bisq.core.trade.protocol.bisq_v1.messages.TraderSignedWitnessMessage; +import bisq.core.trade.protocol.bsq_swap.messages.BsqSwapFinalizeTxRequest; +import bisq.core.trade.protocol.bsq_swap.messages.BsqSwapFinalizedTxMessage; +import bisq.core.trade.protocol.bsq_swap.messages.BsqSwapTxInputsMessage; +import bisq.core.trade.protocol.bsq_swap.messages.BuyersBsqSwapRequest; +import bisq.core.trade.protocol.bsq_swap.messages.SellersBsqSwapRequest; import bisq.network.p2p.AckMessage; import bisq.network.p2p.BundleOfEnvelopes; @@ -163,6 +169,17 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo case SHARE_BUYER_PAYMENT_ACCOUNT_MESSAGE: return ShareBuyerPaymentAccountMessage.fromProto(proto.getShareBuyerPaymentAccountMessage(), this, messageVersion); + case SELLERS_BSQ_SWAP_REQUEST: + return SellersBsqSwapRequest.fromProto(proto.getSellersBsqSwapRequest(), messageVersion); + case BUYERS_BSQ_SWAP_REQUEST: + return BuyersBsqSwapRequest.fromProto(proto.getBuyersBsqSwapRequest(), messageVersion); + case BSQ_SWAP_TX_INPUTS_MESSAGE: + return BsqSwapTxInputsMessage.fromProto(proto.getBsqSwapTxInputsMessage(), messageVersion); + case BSQ_SWAP_FINALIZE_TX_REQUEST: + return BsqSwapFinalizeTxRequest.fromProto(proto.getBsqSwapFinalizeTxRequest(), messageVersion); + case BSQ_SWAP_FINALIZED_TX_MESSAGE: + return BsqSwapFinalizedTxMessage.fromProto(proto.getBsqSwapFinalizedTxMessage(), messageVersion); + case COUNTER_CURRENCY_TRANSFER_STARTED_MESSAGE: return CounterCurrencyTransferStartedMessage.fromProto(proto.getCounterCurrencyTransferStartedMessage(), messageVersion); @@ -278,6 +295,8 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo return MailboxStoragePayload.fromProto(proto.getMailboxStoragePayload()); case OFFER_PAYLOAD: return OfferPayload.fromProto(proto.getOfferPayload()); + case BSQ_SWAP_OFFER_PAYLOAD: + return BsqSwapOfferPayload.fromProto(proto.getBsqSwapOfferPayload()); case TEMP_PROPOSAL_PAYLOAD: return TempProposalPayload.fromProto(proto.getTempProposalPayload()); default: diff --git a/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java b/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java index f42670d76d..b0aa031257 100644 --- a/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java +++ b/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java @@ -37,7 +37,7 @@ import bisq.core.proto.CoreProtoResolver; import bisq.core.support.dispute.arbitration.ArbitrationDisputeList; import bisq.core.support.dispute.mediation.MediationDisputeList; import bisq.core.support.dispute.refund.RefundDisputeList; -import bisq.core.trade.TradableList; +import bisq.core.trade.model.TradableList; import bisq.core.trade.statistics.TradeStatistics2Store; import bisq.core.trade.statistics.TradeStatistics3Store; import bisq.core.user.PreferencesPayload; diff --git a/core/src/main/java/bisq/core/provider/mempool/MempoolService.java b/core/src/main/java/bisq/core/provider/mempool/MempoolService.java index 7894b55f08..9a347d8e00 100644 --- a/core/src/main/java/bisq/core/provider/mempool/MempoolService.java +++ b/core/src/main/java/bisq/core/provider/mempool/MempoolService.java @@ -20,8 +20,8 @@ package bisq.core.provider.mempool; import bisq.core.dao.DaoFacade; import bisq.core.dao.state.DaoStateService; import bisq.core.filter.FilterManager; -import bisq.core.offer.OfferPayload; -import bisq.core.trade.Trade; +import bisq.core.offer.bisq_v1.OfferPayload; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.user.Preferences; import bisq.network.Socks5ProxyProvider; @@ -106,7 +106,7 @@ public class MempoolService { } public void validateOfferTakerTx(Trade trade, Consumer resultHandler) { - validateOfferTakerTx(new TxValidator(daoStateService, trade.getTakerFeeTxId(), trade.getTradeAmount(), + validateOfferTakerTx(new TxValidator(daoStateService, trade.getTakerFeeTxId(), trade.getAmount(), trade.isCurrencyForTakerFeeBtc()), resultHandler); } diff --git a/core/src/main/java/bisq/core/setup/CoreNetworkCapabilities.java b/core/src/main/java/bisq/core/setup/CoreNetworkCapabilities.java index 69485612c7..1f270e9a12 100644 --- a/core/src/main/java/bisq/core/setup/CoreNetworkCapabilities.java +++ b/core/src/main/java/bisq/core/setup/CoreNetworkCapabilities.java @@ -41,7 +41,8 @@ public class CoreNetworkCapabilities { Capability.REFUND_AGENT, Capability.TRADE_STATISTICS_HASH_UPDATE, Capability.NO_ADDRESS_PRE_FIX, - Capability.TRADE_STATISTICS_3 + Capability.TRADE_STATISTICS_3, + Capability.BSQ_SWAP_OFFER ); if (config.daoActivated) { diff --git a/core/src/main/java/bisq/core/setup/CorePersistedDataHost.java b/core/src/main/java/bisq/core/setup/CorePersistedDataHost.java index 0e54e5527c..f8e23ee5a6 100644 --- a/core/src/main/java/bisq/core/setup/CorePersistedDataHost.java +++ b/core/src/main/java/bisq/core/setup/CorePersistedDataHost.java @@ -29,9 +29,10 @@ import bisq.core.offer.OpenOfferManager; import bisq.core.support.dispute.arbitration.ArbitrationDisputeListService; import bisq.core.support.dispute.mediation.MediationDisputeListService; import bisq.core.support.dispute.refund.RefundDisputeListService; +import bisq.core.trade.ClosedTradableManager; import bisq.core.trade.TradeManager; -import bisq.core.trade.closed.ClosedTradableManager; -import bisq.core.trade.failed.FailedTradesManager; +import bisq.core.trade.bisq_v1.FailedTradesManager; +import bisq.core.trade.bsq_swap.BsqSwapTradeManager; import bisq.core.user.Preferences; import bisq.core.user.User; @@ -63,6 +64,7 @@ public class CorePersistedDataHost { persistedDataHosts.add(injector.getInstance(OpenOfferManager.class)); persistedDataHosts.add(injector.getInstance(TradeManager.class)); persistedDataHosts.add(injector.getInstance(ClosedTradableManager.class)); + persistedDataHosts.add(injector.getInstance(BsqSwapTradeManager.class)); persistedDataHosts.add(injector.getInstance(FailedTradesManager.class)); persistedDataHosts.add(injector.getInstance(ArbitrationDisputeListService.class)); persistedDataHosts.add(injector.getInstance(MediationDisputeListService.class)); diff --git a/core/src/main/java/bisq/core/support/dispute/Dispute.java b/core/src/main/java/bisq/core/support/dispute/Dispute.java index 669f7d0854..5a0c3eac8f 100644 --- a/core/src/main/java/bisq/core/support/dispute/Dispute.java +++ b/core/src/main/java/bisq/core/support/dispute/Dispute.java @@ -21,7 +21,7 @@ import bisq.core.locale.Res; import bisq.core.proto.CoreProtoResolver; import bisq.core.support.SupportType; import bisq.core.support.messages.ChatMessage; -import bisq.core.trade.Contract; +import bisq.core.trade.model.bisq_v1.Contract; import bisq.common.crypto.PubKeyRing; import bisq.common.proto.ProtoUtil; diff --git a/core/src/main/java/bisq/core/support/dispute/DisputeListService.java b/core/src/main/java/bisq/core/support/dispute/DisputeListService.java index b38cf7fa4f..d1a1a20ba1 100644 --- a/core/src/main/java/bisq/core/support/dispute/DisputeListService.java +++ b/core/src/main/java/bisq/core/support/dispute/DisputeListService.java @@ -17,7 +17,7 @@ package bisq.core.support.dispute; -import bisq.core.trade.Contract; +import bisq.core.trade.model.bisq_v1.Contract; import bisq.network.p2p.NodeAddress; diff --git a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java index c02641b1bd..79a2e57fd6 100644 --- a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java +++ b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java @@ -26,8 +26,8 @@ import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; import bisq.core.monetary.Altcoin; import bisq.core.monetary.Price; -import bisq.core.offer.OfferPayload; import bisq.core.offer.OpenOfferManager; +import bisq.core.offer.bisq_v1.OfferPayload; import bisq.core.provider.price.MarketPrice; import bisq.core.provider.price.PriceFeedService; import bisq.core.support.SupportManager; @@ -35,11 +35,11 @@ import bisq.core.support.dispute.messages.DisputeResultMessage; import bisq.core.support.dispute.messages.OpenNewDisputeMessage; import bisq.core.support.dispute.messages.PeerOpenedDisputeMessage; import bisq.core.support.messages.ChatMessage; -import bisq.core.trade.Contract; -import bisq.core.trade.Trade; -import bisq.core.trade.TradeDataValidation; +import bisq.core.trade.ClosedTradableManager; import bisq.core.trade.TradeManager; -import bisq.core.trade.closed.ClosedTradableManager; +import bisq.core.trade.bisq_v1.TradeDataValidation; +import bisq.core.trade.model.bisq_v1.Contract; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.network.p2p.BootstrapListener; import bisq.network.p2p.NodeAddress; diff --git a/core/src/main/java/bisq/core/support/dispute/agent/MultipleHolderNameDetection.java b/core/src/main/java/bisq/core/support/dispute/agent/MultipleHolderNameDetection.java index b3b35a68c5..1be96332ef 100644 --- a/core/src/main/java/bisq/core/support/dispute/agent/MultipleHolderNameDetection.java +++ b/core/src/main/java/bisq/core/support/dispute/agent/MultipleHolderNameDetection.java @@ -24,7 +24,7 @@ import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.DisputeList; import bisq.core.support.dispute.DisputeManager; import bisq.core.support.dispute.DisputeResult; -import bisq.core.trade.Contract; +import bisq.core.trade.model.bisq_v1.Contract; import bisq.core.user.DontShowAgainLookup; import bisq.common.crypto.Hash; diff --git a/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java b/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java index 2718da778b..30a6940d14 100644 --- a/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java +++ b/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java @@ -40,11 +40,11 @@ import bisq.core.support.dispute.messages.OpenNewDisputeMessage; import bisq.core.support.dispute.messages.PeerOpenedDisputeMessage; import bisq.core.support.messages.ChatMessage; import bisq.core.support.messages.SupportMessage; -import bisq.core.trade.Contract; -import bisq.core.trade.Tradable; -import bisq.core.trade.Trade; +import bisq.core.trade.ClosedTradableManager; import bisq.core.trade.TradeManager; -import bisq.core.trade.closed.ClosedTradableManager; +import bisq.core.trade.model.Tradable; +import bisq.core.trade.model.bisq_v1.Contract; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.network.p2p.AckMessageSourceType; import bisq.network.p2p.NodeAddress; diff --git a/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java b/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java index 0b94127276..b2d64bcb94 100644 --- a/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java +++ b/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java @@ -35,11 +35,11 @@ import bisq.core.support.dispute.messages.OpenNewDisputeMessage; import bisq.core.support.dispute.messages.PeerOpenedDisputeMessage; import bisq.core.support.messages.ChatMessage; import bisq.core.support.messages.SupportMessage; -import bisq.core.trade.Trade; +import bisq.core.trade.ClosedTradableManager; import bisq.core.trade.TradeManager; -import bisq.core.trade.closed.ClosedTradableManager; -import bisq.core.trade.protocol.DisputeProtocol; -import bisq.core.trade.protocol.ProcessModel; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.DisputeProtocol; +import bisq.core.trade.protocol.bisq_v1.model.ProcessModel; import bisq.network.p2p.AckMessageSourceType; import bisq.network.p2p.NodeAddress; @@ -251,7 +251,7 @@ public final class MediationManager extends DisputeManager // If we have not got yet the peers signature we sign and send to the peer our signature. // Otherwise we sign and complete with the peers signature the payout tx. - if (processModel.getTradingPeer().getMediatedPayoutTxSignature() == null) { + if (processModel.getTradePeer().getMediatedPayoutTxSignature() == null) { tradeProtocol.onAcceptMediationResult(() -> { if (trade.getPayoutTx() != null) { tradeManager.closeDisputedTrade(tradeId, Trade.DisputeState.MEDIATION_CLOSED); diff --git a/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java b/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java index 90352c2647..914887d5aa 100644 --- a/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java +++ b/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java @@ -35,9 +35,9 @@ import bisq.core.support.dispute.messages.OpenNewDisputeMessage; import bisq.core.support.dispute.messages.PeerOpenedDisputeMessage; import bisq.core.support.messages.ChatMessage; import bisq.core.support.messages.SupportMessage; -import bisq.core.trade.Trade; +import bisq.core.trade.ClosedTradableManager; import bisq.core.trade.TradeManager; -import bisq.core.trade.closed.ClosedTradableManager; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.network.p2p.AckMessageSourceType; import bisq.network.p2p.NodeAddress; diff --git a/core/src/main/java/bisq/core/support/traderchat/TradeChatSession.java b/core/src/main/java/bisq/core/support/traderchat/TradeChatSession.java index e69a18c3f6..32afe342d5 100644 --- a/core/src/main/java/bisq/core/support/traderchat/TradeChatSession.java +++ b/core/src/main/java/bisq/core/support/traderchat/TradeChatSession.java @@ -19,9 +19,7 @@ package bisq.core.support.traderchat; import bisq.core.support.SupportSession; import bisq.core.support.messages.ChatMessage; -import bisq.core.trade.Trade; - -import bisq.common.crypto.PubKeyRing; +import bisq.core.trade.model.bisq_v1.Trade; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -66,7 +64,7 @@ public class TradeChatSession extends SupportSession { @Override public boolean chatIsOpen() { - return trade != null && trade.getState() != Trade.State.WITHDRAW_COMPLETED; + return trade != null && trade.getTradeState() != Trade.State.WITHDRAW_COMPLETED; } @Override diff --git a/core/src/main/java/bisq/core/support/traderchat/TraderChatManager.java b/core/src/main/java/bisq/core/support/traderchat/TraderChatManager.java index 1c44185f2a..3bc93e7b4f 100644 --- a/core/src/main/java/bisq/core/support/traderchat/TraderChatManager.java +++ b/core/src/main/java/bisq/core/support/traderchat/TraderChatManager.java @@ -23,10 +23,10 @@ import bisq.core.support.SupportManager; import bisq.core.support.SupportType; import bisq.core.support.messages.ChatMessage; import bisq.core.support.messages.SupportMessage; -import bisq.core.trade.Trade; +import bisq.core.trade.ClosedTradableManager; import bisq.core.trade.TradeManager; -import bisq.core.trade.closed.ClosedTradableManager; -import bisq.core.trade.failed.FailedTradesManager; +import bisq.core.trade.bisq_v1.FailedTradesManager; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.network.p2p.AckMessageSourceType; import bisq.network.p2p.NodeAddress; @@ -110,7 +110,8 @@ public class TraderChatManager extends SupportManager { @Override public List getAllChatMessages(String tradeId) { - return getTradeById(tradeId).map(trade -> trade.getChatMessages()).orElse(FXCollections.emptyObservableList()); + return getTradeById(tradeId).map(Trade::getChatMessages) + .orElse(FXCollections.emptyObservableList()); } @Override diff --git a/core/src/main/java/bisq/core/trade/ClosedTradableFormatter.java b/core/src/main/java/bisq/core/trade/ClosedTradableFormatter.java new file mode 100644 index 0000000000..cbe8e5b88a --- /dev/null +++ b/core/src/main/java/bisq/core/trade/ClosedTradableFormatter.java @@ -0,0 +1,225 @@ +/* + * 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.core.trade; + +import bisq.core.btc.wallet.BsqWalletService; +import bisq.core.locale.CurrencyUtil; +import bisq.core.locale.Res; +import bisq.core.monetary.Altcoin; +import bisq.core.monetary.Volume; +import bisq.core.offer.OpenOffer; +import bisq.core.trade.model.Tradable; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.util.FormattingUtils; +import bisq.core.util.coin.BsqFormatter; +import bisq.core.util.coin.CoinFormatter; + +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.Monetary; +import org.bitcoinj.core.TransactionConfidence; +import org.bitcoinj.utils.Fiat; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import lombok.extern.slf4j.Slf4j; + +import static bisq.core.trade.ClosedTradableUtil.*; +import static bisq.core.trade.model.bisq_v1.Trade.DisputeState.DISPUTE_CLOSED; +import static bisq.core.trade.model.bisq_v1.Trade.DisputeState.MEDIATION_CLOSED; +import static bisq.core.trade.model.bisq_v1.Trade.DisputeState.REFUND_REQUEST_CLOSED; +import static bisq.core.util.FormattingUtils.BTC_FORMATTER_KEY; +import static bisq.core.util.FormattingUtils.formatPercentagePrice; +import static bisq.core.util.FormattingUtils.formatToPercentWithSymbol; +import static bisq.core.util.VolumeUtil.formatVolume; +import static bisq.core.util.VolumeUtil.formatVolumeWithCode; + +@Slf4j +@Singleton +public class ClosedTradableFormatter { + // Resource bundle i18n keys with Desktop UI specific property names, + // having "generic-enough" property values to be referenced in the core layer. + private static final String I18N_KEY_TOTAL_AMOUNT = "closedTradesSummaryWindow.totalAmount.value"; + private static final String I18N_KEY_TOTAL_TX_FEE = "closedTradesSummaryWindow.totalMinerFee.value"; + private static final String I18N_KEY_TOTAL_TRADE_FEE_BTC = "closedTradesSummaryWindow.totalTradeFeeInBtc.value"; + private static final String I18N_KEY_TOTAL_TRADE_FEE_BSQ = "closedTradesSummaryWindow.totalTradeFeeInBsq.value"; + + private final BsqFormatter bsqFormatter; + private final CoinFormatter btcFormatter; + private final BsqWalletService bsqWalletService; + private final ClosedTradableManager closedTradableManager; + + @Inject + public ClosedTradableFormatter(ClosedTradableManager closedTradableManager, + BsqFormatter bsqFormatter, + @Named(BTC_FORMATTER_KEY) CoinFormatter btcFormatter, + BsqWalletService bsqWalletService) { + this.closedTradableManager = closedTradableManager; + this.bsqFormatter = bsqFormatter; + this.btcFormatter = btcFormatter; + this.bsqWalletService = bsqWalletService; + } + + public String getAmountAsString(Tradable tradable) { + return tradable.getOptionalAmount().map(btcFormatter::formatCoin).orElse(""); + } + + public String getTotalAmountWithVolumeAsString(Coin totalTradeAmount, Volume volume) { + return Res.get(I18N_KEY_TOTAL_AMOUNT, + btcFormatter.formatCoin(totalTradeAmount, true), + formatVolumeWithCode(volume)); + } + + public String getTxFeeAsString(Tradable tradable) { + return btcFormatter.formatCoin(getTxFee(tradable)); + } + + public String getTotalTxFeeAsString(Coin totalTradeAmount, Coin totalTxFee) { + double percentage = ((double) totalTxFee.value) / totalTradeAmount.value; + return Res.get(I18N_KEY_TOTAL_TX_FEE, + btcFormatter.formatCoin(totalTxFee, true), + formatToPercentWithSymbol(percentage)); + } + + public String getBuyerSecurityDepositAsString(Tradable tradable) { + return isBsqSwapTrade(tradable) ? Res.get("shared.na") : + btcFormatter.formatCoin(tradable.getOffer().getBuyerSecurityDeposit()); + } + + public String getSellerSecurityDepositAsString(Tradable tradable) { + return isBsqSwapTrade(tradable) ? Res.get("shared.na") : + btcFormatter.formatCoin(tradable.getOffer().getSellerSecurityDeposit()); + } + + public String getTotalTradeFeeInBsqAsString(Coin totalTradeFee, + Volume tradeAmountVolume, + Volume bsqVolumeInUsd) { + double percentage = ((double) bsqVolumeInUsd.getValue()) / tradeAmountVolume.getValue(); + return Res.get(I18N_KEY_TOTAL_TRADE_FEE_BSQ, + bsqFormatter.formatCoin(totalTradeFee, true), + formatToPercentWithSymbol(percentage)); + } + + public String getTradeFeeAsString(Tradable tradable, boolean appendCode) { + if (closedTradableManager.isBsqTradeFee(tradable)) { + return bsqFormatter.formatCoin(Coin.valueOf(closedTradableManager.getBsqTradeFee(tradable)), appendCode); + } else { + closedTradableManager.getBtcTradeFee(tradable); + return btcFormatter.formatCoin(Coin.valueOf(closedTradableManager.getBtcTradeFee(tradable)), appendCode); + } + } + + public String getTotalTradeFeeInBtcAsString(Coin totalTradeAmount, Coin totalTradeFee) { + double percentage = ((double) totalTradeFee.value) / totalTradeAmount.value; + return Res.get(I18N_KEY_TOTAL_TRADE_FEE_BTC, + btcFormatter.formatCoin(totalTradeFee, true), + formatToPercentWithSymbol(percentage)); + } + + public String getPriceDeviationAsString(Tradable tradable) { + if (tradable.getOffer().isUseMarketBasedPrice()) { + return formatPercentagePrice(tradable.getOffer().getMarketPriceMargin()); + } else { + return Res.get("shared.na"); + } + } + + public String getVolumeAsString(Tradable tradable, boolean appendCode) { + return tradable.getOptionalVolume().map(volume -> formatVolume(volume, appendCode)).orElse(""); + } + + public String getVolumeCurrencyAsString(Tradable tradable) { + return tradable.getOptionalVolume().map(Volume::getCurrencyCode).orElse(""); + } + + public String getPriceAsString(Tradable tradable) { + return tradable.getOptionalPrice().map(FormattingUtils::formatPrice).orElse(""); + } + + public Map getTotalVolumeByCurrencyAsString(List tradableList) { + return getTotalVolumeByCurrency(tradableList).entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, + entry -> { + String currencyCode = entry.getKey(); + Monetary monetary; + if (CurrencyUtil.isCryptoCurrency(currencyCode)) { + monetary = Altcoin.valueOf(currencyCode, entry.getValue()); + } else { + monetary = Fiat.valueOf(currencyCode, entry.getValue()); + } + return formatVolumeWithCode(new Volume(monetary)); + } + )); + } + + public String getStateAsString(Tradable tradable) { + if (tradable == null) { + return ""; + } + + if (isBisqV1Trade(tradable)) { + Trade trade = castToTrade(tradable); + if (trade.isWithdrawn() || trade.isPayoutPublished()) { + return Res.get("portfolio.closed.completed"); + } else if (trade.getDisputeState() == DISPUTE_CLOSED) { + return Res.get("portfolio.closed.ticketClosed"); + } else if (trade.getDisputeState() == MEDIATION_CLOSED) { + return Res.get("portfolio.closed.mediationTicketClosed"); + } else if (trade.getDisputeState() == REFUND_REQUEST_CLOSED) { + return Res.get("portfolio.closed.ticketClosed"); + } else { + log.error("That must not happen. We got a pending state but we are in" + + " the closed trades list. state={}", + trade.getTradeState().name()); + return Res.get("shared.na"); + } + } else if (isOpenOffer(tradable)) { + OpenOffer.State state = ((OpenOffer) tradable).getState(); + log.trace("OpenOffer state={}", state); + switch (state) { + case AVAILABLE: + case RESERVED: + case CLOSED: + case DEACTIVATED: + log.error("Invalid state {}", state); + return state.name(); + case CANCELED: + return Res.get("portfolio.closed.canceled"); + default: + log.error("Unhandled state {}", state); + return state.name(); + } + } else if (isBsqSwapTrade(tradable)) { + String txId = castToBsqSwapTrade(tradable).getTxId(); + TransactionConfidence confidence = bsqWalletService.getConfidenceForTxId(txId); + if (confidence != null && confidence.getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING) { + return Res.get("confidence.confirmed.short"); + } else { + log.warn("Unexpected confidence in a BSQ swap trade which has been moved to closed trades. " + + "This could happen at a wallet SPV resycn or a reorg. confidence={} tradeID={}", + confidence, tradable.getId()); + } + } + return Res.get("shared.na"); + } +} diff --git a/core/src/main/java/bisq/core/trade/ClosedTradableManager.java b/core/src/main/java/bisq/core/trade/ClosedTradableManager.java new file mode 100644 index 0000000000..1fe2dc8701 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/ClosedTradableManager.java @@ -0,0 +1,236 @@ +/* + * 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.core.trade; + +import bisq.core.btc.wallet.BsqWalletService; +import bisq.core.monetary.Price; +import bisq.core.monetary.Volume; +import bisq.core.offer.Offer; +import bisq.core.provider.price.PriceFeedService; +import bisq.core.trade.bisq_v1.CleanupMailboxMessagesService; +import bisq.core.trade.bisq_v1.DumpDelayedPayoutTx; +import bisq.core.trade.bsq_swap.BsqSwapTradeManager; +import bisq.core.trade.model.MakerTrade; +import bisq.core.trade.model.Tradable; +import bisq.core.trade.model.TradableList; +import bisq.core.trade.model.TradeModel; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.statistics.TradeStatisticsManager; +import bisq.core.user.Preferences; + +import bisq.network.p2p.NodeAddress; + +import bisq.common.crypto.KeyRing; +import bisq.common.persistence.PersistenceManager; +import bisq.common.proto.persistable.PersistedDataHost; +import bisq.common.util.Tuple2; + +import org.bitcoinj.core.Coin; +import org.bitcoinj.utils.Fiat; + +import com.google.inject.Inject; + +import com.google.common.collect.ImmutableList; + +import javafx.collections.ObservableList; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import lombok.extern.slf4j.Slf4j; + +import static bisq.core.trade.ClosedTradableUtil.castToTrade; +import static bisq.core.trade.ClosedTradableUtil.castToTradeModel; +import static bisq.core.trade.ClosedTradableUtil.isBsqSwapTrade; +import static bisq.core.trade.ClosedTradableUtil.isOpenOffer; +import static bisq.core.util.AveragePriceUtil.getAveragePriceTuple; + +/** + * Manages closed trades or offers. + * BsqSwap trades are once confirmed moved in the closed trades domain as well. + * We do not manage the persistence of BsqSwap trades here but in BsqSwapTradeManager. + */ +@Slf4j +public class ClosedTradableManager implements PersistedDataHost { + private final KeyRing keyRing; + private final PriceFeedService priceFeedService; + private final BsqSwapTradeManager bsqSwapTradeManager; + private final BsqWalletService bsqWalletService; + private final Preferences preferences; + private final TradeStatisticsManager tradeStatisticsManager; + private final PersistenceManager> persistenceManager; + private final CleanupMailboxMessagesService cleanupMailboxMessagesService; + private final DumpDelayedPayoutTx dumpDelayedPayoutTx; + + private final TradableList closedTradables = new TradableList<>(); + + @Inject + public ClosedTradableManager(KeyRing keyRing, + PriceFeedService priceFeedService, + BsqSwapTradeManager bsqSwapTradeManager, + BsqWalletService bsqWalletService, + Preferences preferences, + TradeStatisticsManager tradeStatisticsManager, + PersistenceManager> persistenceManager, + CleanupMailboxMessagesService cleanupMailboxMessagesService, + DumpDelayedPayoutTx dumpDelayedPayoutTx) { + this.keyRing = keyRing; + this.priceFeedService = priceFeedService; + this.bsqSwapTradeManager = bsqSwapTradeManager; + this.bsqWalletService = bsqWalletService; + this.preferences = preferences; + this.tradeStatisticsManager = tradeStatisticsManager; + this.cleanupMailboxMessagesService = cleanupMailboxMessagesService; + this.dumpDelayedPayoutTx = dumpDelayedPayoutTx; + this.persistenceManager = persistenceManager; + + this.persistenceManager.initialize(closedTradables, "ClosedTrades", PersistenceManager.Source.PRIVATE); + } + + @Override + public void readPersisted(Runnable completeHandler) { + persistenceManager.readPersisted(persisted -> { + closedTradables.setAll(persisted.getList()); + closedTradables.stream() + .filter(tradable -> tradable.getOffer() != null) + .forEach(tradable -> tradable.getOffer().setPriceFeedService(priceFeedService)); + dumpDelayedPayoutTx.maybeDumpDelayedPayoutTxs(closedTradables, "delayed_payout_txs_closed"); + completeHandler.run(); + }, + completeHandler); + } + + public void onAllServicesInitialized() { + cleanupMailboxMessagesService.handleTrades(getClosedTrades()); + } + + public void add(Tradable tradable) { + if (closedTradables.add(tradable)) { + requestPersistence(); + } + } + + public void remove(Tradable tradable) { + if (closedTradables.remove(tradable)) { + requestPersistence(); + } + } + + public boolean wasMyOffer(Offer offer) { + return offer.isMyOffer(keyRing); + } + + public ObservableList getObservableList() { + return closedTradables.getObservableList(); + } + + public List getClosedTrades() { + return ImmutableList.copyOf(getObservableList().stream() + .filter(e -> e instanceof Trade) + .map(e -> (Trade) e) + .collect(Collectors.toList())); + } + + public Optional getTradableById(String id) { + return closedTradables.stream().filter(e -> e.getId().equals(id)).findFirst(); + } + + public Stream getTradesStreamWithFundsLockedIn() { + return getClosedTrades().stream() + .filter(Trade::isFundsLockedIn); + } + + public Stream getTradeModelStream() { + return Stream.concat(bsqSwapTradeManager.getConfirmedBsqSwapTrades(), getClosedTrades().stream()); + } + + public int getNumPastTrades(Tradable tradable) { + if (isOpenOffer(tradable)) { + return 0; + } + NodeAddress addressInTrade = castToTradeModel(tradable).getTradingPeerNodeAddress(); + return (int) getTradeModelStream() + .map(TradeModel::getTradingPeerNodeAddress) + .filter(Objects::nonNull) + .filter(address -> address.equals(addressInTrade)) + .count(); + } + + public boolean isCurrencyForTradeFeeBtc(Tradable tradable) { + return !isBsqTradeFee(tradable); + } + + public Coin getTotalTradeFee(List tradableList, boolean expectBtcFee) { + return Coin.valueOf(tradableList.stream() + .mapToLong(tradable -> getTradeFee(tradable, expectBtcFee)) + .sum()); + } + + private long getTradeFee(Tradable tradable, boolean expectBtcFee) { + return expectBtcFee ? getBtcTradeFee(tradable) : getBsqTradeFee(tradable); + } + + public long getBtcTradeFee(Tradable tradable) { + if (isBsqSwapTrade(tradable) || isBsqTradeFee(tradable)) { + return 0L; + } + return isMaker(tradable) ? + tradable.getOptionalMakerFee().orElse(Coin.ZERO).value : + tradable.getOptionalTakerFee().orElse(Coin.ZERO).value; + } + + public long getBsqTradeFee(Tradable tradable) { + if (isBsqSwapTrade(tradable) || isBsqTradeFee(tradable)) { + return isMaker(tradable) ? + tradable.getOptionalMakerFee().orElse(Coin.ZERO).value : + tradable.getOptionalTakerFee().orElse(Coin.ZERO).value; + } + return 0L; + } + + public boolean isBsqTradeFee(Tradable tradable) { + if (isBsqSwapTrade(tradable)) { + return true; + } + + if (isMaker(tradable)) { + return !tradable.getOffer().isCurrencyForMakerFeeBtc(); + } + + String feeTxId = castToTrade(tradable).getTakerFeeTxId(); + return bsqWalletService.getTransaction(feeTxId) != null; + } + + public boolean isMaker(Tradable tradable) { + return tradable instanceof MakerTrade || tradable.getOffer().isMyOffer(keyRing); + } + + public Volume getBsqVolumeInUsdWithAveragePrice(Coin amount) { + Tuple2 tuple = getAveragePriceTuple(preferences, tradeStatisticsManager, 30); + Price usdPrice = tuple.first; + long value = Math.round(amount.value * usdPrice.getValue() / 100d); + return new Volume(Fiat.valueOf("USD", value)); + } + + private void requestPersistence() { + persistenceManager.requestPersistence(); + } +} diff --git a/core/src/main/java/bisq/core/trade/ClosedTradableUtil.java b/core/src/main/java/bisq/core/trade/ClosedTradableUtil.java new file mode 100644 index 0000000000..fd38d5204b --- /dev/null +++ b/core/src/main/java/bisq/core/trade/ClosedTradableUtil.java @@ -0,0 +1,85 @@ +/* + * 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.core.trade; + +import bisq.core.offer.OpenOffer; +import bisq.core.trade.model.Tradable; +import bisq.core.trade.model.TradeModel; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; + +import org.bitcoinj.core.Coin; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ClosedTradableUtil { + public static Coin getTotalAmount(List tradableList) { + return Coin.valueOf(tradableList.stream() + .flatMap(tradable -> tradable.getOptionalAmountAsLong().stream()) + .mapToLong(value -> value) + .sum()); + } + + public static Coin getTotalTxFee(List tradableList) { + return Coin.valueOf(tradableList.stream() + .mapToLong(tradable -> getTxFee(tradable).getValue()) + .sum()); + } + + public static Map getTotalVolumeByCurrency(List tradableList) { + Map map = new HashMap<>(); + tradableList.stream() + .flatMap(tradable -> tradable.getOptionalVolume().stream()) + .forEach(volume -> { + String currencyCode = volume.getCurrencyCode(); + map.putIfAbsent(currencyCode, 0L); + map.put(currencyCode, volume.getValue() + map.get(currencyCode)); + }); + return map; + } + + public static Coin getTxFee(Tradable tradable) { + return tradable.getOptionalTxFee().orElse(Coin.ZERO); + } + + public static boolean isOpenOffer(Tradable tradable) { + return tradable instanceof OpenOffer; + } + + public static boolean isBsqSwapTrade(Tradable tradable) { + return tradable instanceof BsqSwapTrade; + } + + public static boolean isBisqV1Trade(Tradable tradable) { + return tradable instanceof Trade; + } + + public static Trade castToTrade(Tradable tradable) { + return (Trade) tradable; + } + + public static TradeModel castToTradeModel(Tradable tradable) { + return (TradeModel) tradable; + } + + public static BsqSwapTrade castToBsqSwapTrade(Tradable tradable) { + return (BsqSwapTrade) tradable; + } +} diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index 6707fde8ea..89d924364c 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -23,23 +23,45 @@ import bisq.core.btc.wallet.BsqWalletService; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.locale.Res; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferDirection; import bisq.core.offer.OpenOffer; import bisq.core.offer.OpenOfferManager; import bisq.core.offer.availability.OfferAvailabilityModel; import bisq.core.provider.price.PriceFeedService; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import bisq.core.support.dispute.mediation.mediator.MediatorManager; -import bisq.core.trade.closed.ClosedTradableManager; -import bisq.core.trade.failed.FailedTradesManager; -import bisq.core.trade.handlers.TradeResultHandler; -import bisq.core.trade.messages.InputsForDepositTxRequest; -import bisq.core.trade.protocol.MakerProtocol; -import bisq.core.trade.protocol.ProcessModel; -import bisq.core.trade.protocol.ProcessModelServiceProvider; -import bisq.core.trade.protocol.TakerProtocol; +import bisq.core.trade.bisq_v1.DumpDelayedPayoutTx; +import bisq.core.trade.bisq_v1.FailedTradesManager; +import bisq.core.trade.bisq_v1.TradeResultHandler; +import bisq.core.trade.bisq_v1.TradeTxException; +import bisq.core.trade.bisq_v1.TradeUtil; +import bisq.core.trade.bsq_swap.BsqSwapTakeOfferRequestVerification; +import bisq.core.trade.bsq_swap.BsqSwapTradeManager; +import bisq.core.trade.model.Tradable; +import bisq.core.trade.model.TradableList; +import bisq.core.trade.model.TradeModel; +import bisq.core.trade.model.bisq_v1.BuyerAsMakerTrade; +import bisq.core.trade.model.bisq_v1.BuyerAsTakerTrade; +import bisq.core.trade.model.bisq_v1.SellerAsMakerTrade; +import bisq.core.trade.model.bisq_v1.SellerAsTakerTrade; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.model.bsq_swap.BsqSwapBuyerAsMakerTrade; +import bisq.core.trade.model.bsq_swap.BsqSwapBuyerAsTakerTrade; +import bisq.core.trade.model.bsq_swap.BsqSwapSellerAsMakerTrade; +import bisq.core.trade.model.bsq_swap.BsqSwapSellerAsTakerTrade; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.Provider; import bisq.core.trade.protocol.TradeProtocol; import bisq.core.trade.protocol.TradeProtocolFactory; +import bisq.core.trade.protocol.bisq_v1.MakerProtocol; +import bisq.core.trade.protocol.bisq_v1.TakerProtocol; +import bisq.core.trade.protocol.bisq_v1.messages.InputsForDepositTxRequest; +import bisq.core.trade.protocol.bisq_v1.model.ProcessModel; +import bisq.core.trade.protocol.bsq_swap.BsqSwapMakerProtocol; +import bisq.core.trade.protocol.bsq_swap.messages.BsqSwapRequest; +import bisq.core.trade.protocol.bsq_swap.messages.BuyersBsqSwapRequest; +import bisq.core.trade.protocol.bsq_swap.messages.SellersBsqSwapRequest; +import bisq.core.trade.protocol.bsq_swap.model.BsqSwapProtocolModel; import bisq.core.trade.statistics.ReferralIdService; import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.user.User; @@ -117,6 +139,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi private final BsqWalletService bsqWalletService; private final OpenOfferManager openOfferManager; private final ClosedTradableManager closedTradableManager; + private final BsqSwapTradeManager bsqSwapTradeManager; private final FailedTradesManager failedTradesManager; private final P2PService p2PService; private final PriceFeedService priceFeedService; @@ -125,7 +148,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi @Getter private final ArbitratorManager arbitratorManager; private final MediatorManager mediatorManager; - private final ProcessModelServiceProvider processModelServiceProvider; + private final Provider provider; private final ClockWatcher clockWatcher; private final Map tradeProtocolByTradeId = new HashMap<>(); @@ -155,6 +178,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi BsqWalletService bsqWalletService, OpenOfferManager openOfferManager, ClosedTradableManager closedTradableManager, + BsqSwapTradeManager bsqSwapTradeManager, FailedTradesManager failedTradesManager, P2PService p2PService, PriceFeedService priceFeedService, @@ -162,7 +186,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi TradeUtil tradeUtil, ArbitratorManager arbitratorManager, MediatorManager mediatorManager, - ProcessModelServiceProvider processModelServiceProvider, + Provider provider, ClockWatcher clockWatcher, PersistenceManager> persistenceManager, ReferralIdService referralIdService, @@ -174,6 +198,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi this.bsqWalletService = bsqWalletService; this.openOfferManager = openOfferManager; this.closedTradableManager = closedTradableManager; + this.bsqSwapTradeManager = bsqSwapTradeManager; this.failedTradesManager = failedTradesManager; this.p2PService = p2PService; this.priceFeedService = priceFeedService; @@ -181,7 +206,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi this.tradeUtil = tradeUtil; this.arbitratorManager = arbitratorManager; this.mediatorManager = mediatorManager; - this.processModelServiceProvider = processModelServiceProvider; + this.provider = provider; this.clockWatcher = clockWatcher; this.referralIdService = referralIdService; this.dumpDelayedPayoutTx = dumpDelayedPayoutTx; @@ -223,6 +248,8 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi NetworkEnvelope networkEnvelope = message.getNetworkEnvelope(); if (networkEnvelope instanceof InputsForDepositTxRequest) { handleTakeOfferRequest(peer, (InputsForDepositTxRequest) networkEnvelope); + } else if (networkEnvelope instanceof BsqSwapRequest) { + handleBsqSwapRequest(peer, (BsqSwapRequest) networkEnvelope); } } @@ -274,13 +301,9 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi getNewProcessModel(offer), UUID.randomUUID().toString()); } - TradeProtocol tradeProtocol = TradeProtocolFactory.getNewTradeProtocol(trade); - TradeProtocol prev = tradeProtocolByTradeId.put(trade.getUid(), tradeProtocol); - if (prev != null) { - log.error("We had already an entry with uid {}", trade.getUid()); - } - tradableList.add(trade); + TradeProtocol tradeProtocol = createTradeProtocol(trade); + initTradeAndProtocol(trade, tradeProtocol); ((MakerProtocol) tradeProtocol).handleTakeOfferRequest(inputsForDepositTxRequest, peer, errorMessage -> { @@ -292,6 +315,61 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi } + private void handleBsqSwapRequest(NodeAddress peer, BsqSwapRequest request) { + if (!BsqSwapTakeOfferRequestVerification.isValid(openOfferManager, provider.getFeeService(), keyRing, peer, request)) { + return; + } + + Optional openOfferOptional = openOfferManager.getOpenOfferById(request.getTradeId()); + checkArgument(openOfferOptional.isPresent()); + OpenOffer openOffer = openOfferOptional.get(); + Offer offer = openOffer.getOffer(); + openOfferManager.reserveOpenOffer(openOffer); + + BsqSwapTrade bsqSwapTrade; + Coin amount = Coin.valueOf(request.getTradeAmount()); + BsqSwapProtocolModel bsqSwapProtocolModel = new BsqSwapProtocolModel(keyRing.getPubKeyRing()); + if (request instanceof BuyersBsqSwapRequest) { + checkArgument(!offer.isBuyOffer(), + "offer is expected to be a sell offer at handleBsqSwapRequest"); + bsqSwapTrade = new BsqSwapSellerAsMakerTrade( + offer, + amount, + request.getTradeDate(), + request.getSenderNodeAddress(), + request.getTxFeePerVbyte(), + request.getMakerFee(), + request.getTakerFee(), + bsqSwapProtocolModel); + } else { + checkArgument(request instanceof SellersBsqSwapRequest); + checkArgument(offer.isBuyOffer(), + "offer is expected to be a buy offer at handleBsqSwapRequest"); + bsqSwapTrade = new BsqSwapBuyerAsMakerTrade( + offer, + amount, + request.getTradeDate(), + request.getSenderNodeAddress(), + request.getTxFeePerVbyte(), + request.getMakerFee(), + request.getTakerFee(), + bsqSwapProtocolModel); + } + + TradeProtocol tradeProtocol = createTradeProtocol(bsqSwapTrade); + initTradeAndProtocol(bsqSwapTrade, tradeProtocol); + + ((BsqSwapMakerProtocol) tradeProtocol).handleTakeOfferRequest(request, + peer, + errorMessage -> { + if (takeOfferRequestErrorMessageHandler != null) + takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); + }); + + requestPersistence(); + } + + /////////////////////////////////////////////////////////////////////////////////////////// // Lifecycle /////////////////////////////////////////////////////////////////////////////////////////// @@ -319,7 +397,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi }); } - public TradeProtocol getTradeProtocol(Trade trade) { + public TradeProtocol getTradeProtocol(TradeModel trade) { String uid = trade.getUid(); if (tradeProtocolByTradeId.containsKey(uid)) { return tradeProtocolByTradeId.get(uid); @@ -340,26 +418,48 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi /////////////////////////////////////////////////////////////////////////////////////////// private void initPersistedTrades() { - tradableList.forEach(this::initPersistedTrade); + Set toRemove = new HashSet<>(); + tradableList.forEach(tradeModel -> { + boolean valid = initPersistedTrade(tradeModel); + if (!valid) { + toRemove.add(tradeModel); + } + }); + toRemove.forEach(tradableList::remove); + if (!toRemove.isEmpty()) { + requestPersistence(); + } + persistedTradesInitialized.set(true); // We do not include failed trades as they should not be counted anyway in the trade statistics - Set allTrades = new HashSet<>(closedTradableManager.getClosedTrades()); + Set allTrades = new HashSet<>(closedTradableManager.getClosedTrades()); + allTrades.addAll(bsqSwapTradeManager.getBsqSwapTrades()); allTrades.addAll(tradableList.getList()); String referralId = referralIdService.getOptionalReferralId().orElse(null); boolean isTorNetworkNode = p2PService.getNetworkNode() instanceof TorNetworkNode; tradeStatisticsManager.maybeRepublishTradeStatistics(allTrades, referralId, isTorNetworkNode); } - private void initPersistedTrade(Trade trade) { - initTradeAndProtocol(trade, getTradeProtocol(trade)); - trade.updateDepositTxFromWallet(); + private boolean initPersistedTrade(TradeModel tradeModel) { + if (tradeModel instanceof BsqSwapTrade && !tradeModel.isCompleted()) { + // We do not keep pending or failed BsqSwap trades in our list and + // do not process them at restart. + // We remove the trade from the list after iterations in initPersistedTrades + return false; + } + initTradeAndProtocol(tradeModel, getTradeProtocol(tradeModel)); + + if (tradeModel instanceof Trade) { + ((Trade) tradeModel).updateDepositTxFromWallet(); + } requestPersistence(); + return true; } - private void initTradeAndProtocol(Trade trade, TradeProtocol tradeProtocol) { - tradeProtocol.initialize(processModelServiceProvider, this, trade.getOffer()); - trade.initialize(processModelServiceProvider); + private void initTradeAndProtocol(TradeModel tradeModel, TradeProtocol tradeProtocol) { + tradeProtocol.initialize(provider, this, tradeModel.getOffer()); + tradeModel.initialize(provider); requestPersistence(); } @@ -398,7 +498,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi String paymentAccountId, boolean useSavingsWallet, boolean isTakerApiUser, - TradeResultHandler tradeResultHandler, + TradeResultHandler tradeResultHandler, ErrorMessageHandler errorMessageHandler) { checkArgument(!wasOfferAlreadyUsedInTrade(offer.getId())); @@ -441,12 +541,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi trade.getProcessModel().setFundsNeededForTradeAsLong(fundsNeededForTrade.value); trade.setTakerPaymentAccountId(paymentAccountId); - TradeProtocol tradeProtocol = TradeProtocolFactory.getNewTradeProtocol(trade); - TradeProtocol prev = tradeProtocolByTradeId.put(trade.getUid(), tradeProtocol); - if (prev != null) { - log.error("We had already an entry with uid {}", trade.getUid()); - } - tradableList.add(trade); + TradeProtocol tradeProtocol = createTradeProtocol(trade); initTradeAndProtocol(trade, tradeProtocol); @@ -460,10 +555,77 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi requestPersistence(); } + public void onTakeBsqSwapOffer(Offer offer, + Coin amount, + long txFeePerVbyte, + long makerFee, + long takerFee, + boolean isTakerApiUser, + TradeResultHandler tradeResultHandler, + ErrorMessageHandler errorMessageHandler) { + + checkArgument(!wasOfferAlreadyUsedInTrade(offer.getId())); + + OfferAvailabilityModel model = getOfferAvailabilityModel(offer, isTakerApiUser); + offer.checkOfferAvailability(model, + () -> { + if (offer.getState() == Offer.State.AVAILABLE) { + BsqSwapTrade bsqSwapTrade; + NodeAddress peerNodeAddress = model.getPeerNodeAddress(); + BsqSwapProtocolModel bsqSwapProtocolModel = new BsqSwapProtocolModel(keyRing.getPubKeyRing()); + if (offer.isBuyOffer()) { + bsqSwapTrade = new BsqSwapSellerAsTakerTrade( + offer, + amount, + peerNodeAddress, + txFeePerVbyte, + makerFee, + takerFee, + bsqSwapProtocolModel); + } else { + bsqSwapTrade = new BsqSwapBuyerAsTakerTrade( + offer, + amount, + peerNodeAddress, + txFeePerVbyte, + makerFee, + takerFee, + bsqSwapProtocolModel); + } + + TradeProtocol tradeProtocol = createTradeProtocol(bsqSwapTrade); + + initTradeAndProtocol(bsqSwapTrade, tradeProtocol); + + ((TakerProtocol) tradeProtocol).onTakeOffer(); + tradeResultHandler.handleResult(bsqSwapTrade); + requestPersistence(); + } + }, + errorMessageHandler); + + requestPersistence(); + } + + private TradeProtocol createTradeProtocol(TradeModel tradeModel) { + TradeProtocol tradeProtocol = TradeProtocolFactory.getNewTradeProtocol(tradeModel); + TradeProtocol prev = tradeProtocolByTradeId.put(tradeModel.getUid(), tradeProtocol); + if (prev != null) { + log.error("We had already an entry with uid {}", tradeModel.getUid()); + } + if (tradeModel instanceof Trade) { + tradableList.add((Trade) tradeModel); + } + + // For BsqTrades we only store the trade at completion + + return tradeProtocol; + } + private ProcessModel getNewProcessModel(Offer offer) { return new ProcessModel(checkNotNull(offer).getId(), - processModelServiceProvider.getUser().getAccountId(), - processModelServiceProvider.getKeyRing().getPubKeyRing()); + provider.getUser().getAccountId(), + provider.getKeyRing().getPubKeyRing()); } private OfferAvailabilityModel getOfferAvailabilityModel(Offer offer, boolean isTakerApiUser) { @@ -538,14 +700,12 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi /////////////////////////////////////////////////////////////////////////////////////////// public void closeDisputedTrade(String tradeId, Trade.DisputeState disputeState) { - Optional tradeOptional = getTradeById(tradeId); - if (tradeOptional.isPresent()) { - Trade trade = tradeOptional.get(); + getTradeById(tradeId).ifPresent(trade -> { trade.setDisputeState(disputeState); onTradeCompleted(trade); btcWalletService.swapTradeEntryToAvailableEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT); requestPersistence(); - } + }); } @@ -647,6 +807,89 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi return tradesIdSet; } + + /////////////////////////////////////////////////////////////////////////////////////////// + // BsqSwapTradeManager delegates + /////////////////////////////////////////////////////////////////////////////////////////// + + public void onBsqSwapTradeCompleted(BsqSwapTrade bsqSwapTrade) { + bsqSwapTradeManager.onTradeCompleted(bsqSwapTrade); + } + + public Optional findBsqSwapTradeById(String tradeId) { + return bsqSwapTradeManager.findBsqSwapTradeById(tradeId); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Getters, Utils + /////////////////////////////////////////////////////////////////////////////////////////// + + public ObservableList getObservableList() { + return tradableList.getObservableList(); + } + + public BooleanProperty persistedTradesInitializedProperty() { + return persistedTradesInitialized; + } + + public boolean isMyOffer(Offer offer) { + return offer.isMyOffer(keyRing); + } + + public boolean wasOfferAlreadyUsedInTrade(String offerId) { + Stream combinedStream = Stream.concat(getPendingAndBsqSwapTrades(), + failedTradesManager.getObservableList().stream()); + + combinedStream = Stream.concat(combinedStream, + closedTradableManager.getObservableList().stream()); + + return combinedStream.anyMatch(t -> t.getOffer().getId().equals(offerId)); + } + + public boolean isBuyer(Offer offer) { + // If I am the maker, we use the OfferDirection, otherwise the mirrored direction + if (isMyOffer(offer)) + return offer.isBuyOffer(); + else + return offer.getDirection() == OfferDirection.SELL; + } + + public Optional getTradeModelById(String tradeId) { + return getPendingAndBsqSwapTrades() + .filter(tradeModel -> tradeModel.getId().equals(tradeId)) + .findFirst(); + } + + public Optional getTradeById(String tradeId) { + return getTradeModelById(tradeId) + .filter(tradeModel -> tradeModel instanceof Trade) + .map(tradeModel -> (Trade) tradeModel); + } + + private void removeTrade(Trade trade) { + if (tradableList.remove(trade)) { + requestPersistence(); + } + } + + private void addTrade(Trade trade) { + if (tradableList.add(trade)) { + requestPersistence(); + } + } + + private Stream getPendingAndBsqSwapTrades() { + return Stream.concat(tradableList.stream(), + bsqSwapTradeManager.getObservableList().stream()); + } + + // TODO Remove once tradableList is refactored to a final field + // (part of the persistence refactor PR) + private void onTradesChanged() { + this.numPendingTrades.set(getObservableList().size()); + } + // If trade still has funds locked up it might come back from failed trades // Aborts unfailing if the address entries needed are not available private boolean unFailTrade(Trade trade) { @@ -677,57 +920,4 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi AddressEntry.Context.TRADE_PAYOUT); return true; } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Getters, Utils - /////////////////////////////////////////////////////////////////////////////////////////// - - public ObservableList getObservableList() { - return tradableList.getObservableList(); - } - - public BooleanProperty persistedTradesInitializedProperty() { - return persistedTradesInitialized; - } - - public boolean isMyOffer(Offer offer) { - return offer.isMyOffer(keyRing); - } - - public boolean wasOfferAlreadyUsedInTrade(String offerId) { - return getTradeById(offerId).isPresent() || - failedTradesManager.getTradeById(offerId).isPresent() || - closedTradableManager.getTradableById(offerId).isPresent(); - } - - public boolean isBuyer(Offer offer) { - // If I am the maker, we use the OfferPayload.Direction, otherwise the mirrored direction - if (isMyOffer(offer)) - return offer.isBuyOffer(); - else - return offer.getDirection() == OfferPayload.Direction.SELL; - } - - public Optional getTradeById(String tradeId) { - return tradableList.stream().filter(e -> e.getId().equals(tradeId)).findFirst(); - } - - private void removeTrade(Trade trade) { - if (tradableList.remove(trade)) { - requestPersistence(); - } - } - - private void addTrade(Trade trade) { - if (tradableList.add(trade)) { - requestPersistence(); - } - } - - // TODO Remove once tradableList is refactored to a final field - // (part of the persistence refactor PR) - private void onTradesChanged() { - this.numPendingTrades.set(getObservableList().size()); - } } diff --git a/core/src/main/java/bisq/core/trade/TradeModule.java b/core/src/main/java/bisq/core/trade/TradeModule.java index 751bd5a0eb..88c6f4ac18 100644 --- a/core/src/main/java/bisq/core/trade/TradeModule.java +++ b/core/src/main/java/bisq/core/trade/TradeModule.java @@ -21,8 +21,7 @@ import bisq.core.account.sign.SignedWitnessService; import bisq.core.account.sign.SignedWitnessStorageService; import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.account.witness.AccountAgeWitnessStorageService; -import bisq.core.trade.closed.ClosedTradableManager; -import bisq.core.trade.failed.FailedTradesManager; +import bisq.core.trade.bisq_v1.FailedTradesManager; import bisq.core.trade.statistics.ReferralIdService; import bisq.common.app.AppModule; diff --git a/core/src/main/java/bisq/core/trade/closed/CleanupMailboxMessages.java b/core/src/main/java/bisq/core/trade/bisq_v1/CleanupMailboxMessagesService.java similarity index 95% rename from core/src/main/java/bisq/core/trade/closed/CleanupMailboxMessages.java rename to core/src/main/java/bisq/core/trade/bisq_v1/CleanupMailboxMessagesService.java index 5e3e40810b..c27d200408 100644 --- a/core/src/main/java/bisq/core/trade/closed/CleanupMailboxMessages.java +++ b/core/src/main/java/bisq/core/trade/bisq_v1/CleanupMailboxMessagesService.java @@ -15,10 +15,10 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.closed; +package bisq.core.trade.bisq_v1; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.TradeMessage; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.TradeMessage; import bisq.network.p2p.AckMessage; import bisq.network.p2p.AckMessageSourceType; @@ -50,12 +50,12 @@ import lombok.extern.slf4j.Slf4j; * This class must not be injected as a singleton! */ @Slf4j -public class CleanupMailboxMessages { +public class CleanupMailboxMessagesService { private final P2PService p2PService; private final MailboxMessageService mailboxMessageService; @Inject - public CleanupMailboxMessages(P2PService p2PService, MailboxMessageService mailboxMessageService) { + public CleanupMailboxMessagesService(P2PService p2PService, MailboxMessageService mailboxMessageService) { this.p2PService = p2PService; this.mailboxMessageService = mailboxMessageService; } @@ -123,7 +123,7 @@ public class CleanupMailboxMessages { private boolean isPubKeyValid(DecryptedMessageWithPubKey decryptedMessageWithPubKey, Trade trade) { // We can only validate the peers pubKey if we have it already. If we are the taker we get it from the offer // Otherwise it depends on the state of the trade protocol if we have received the peers pubKeyRing already. - PubKeyRing peersPubKeyRing = trade.getProcessModel().getTradingPeer().getPubKeyRing(); + PubKeyRing peersPubKeyRing = trade.getProcessModel().getTradePeer().getPubKeyRing(); boolean isValid = true; if (peersPubKeyRing != null && !decryptedMessageWithPubKey.getSignaturePubKey().equals(peersPubKeyRing.getSignaturePubKey())) { diff --git a/core/src/main/java/bisq/core/trade/DumpDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/bisq_v1/DumpDelayedPayoutTx.java similarity index 87% rename from core/src/main/java/bisq/core/trade/DumpDelayedPayoutTx.java rename to core/src/main/java/bisq/core/trade/bisq_v1/DumpDelayedPayoutTx.java index 3d2785c01e..1589a4afb3 100644 --- a/core/src/main/java/bisq/core/trade/DumpDelayedPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/bisq_v1/DumpDelayedPayoutTx.java @@ -15,7 +15,12 @@ * along with Bisq. If not, see . */ -package bisq.core.trade; +package bisq.core.trade.bisq_v1; + +import bisq.core.trade.model.Tradable; +import bisq.core.trade.model.TradableList; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.util.JsonUtil; import bisq.common.config.Config; import bisq.common.file.JsonFileManager; @@ -58,7 +63,7 @@ public class DumpDelayedPayoutTx { .map(trade -> new DelayedPayoutHash(trade.getId(), Utilities.bytesAsHexString(((Trade) trade).getDelayedPayoutTxBytes()))) .collect(Collectors.toList()); - jsonFileManager.writeToDiscThreaded(Utilities.objectToJson(delayedPayoutHashes), fileName); + jsonFileManager.writeToDiscThreaded(JsonUtil.objectToJson(delayedPayoutHashes), fileName); } } diff --git a/core/src/main/java/bisq/core/trade/failed/FailedTradesManager.java b/core/src/main/java/bisq/core/trade/bisq_v1/FailedTradesManager.java similarity index 91% rename from core/src/main/java/bisq/core/trade/failed/FailedTradesManager.java rename to core/src/main/java/bisq/core/trade/bisq_v1/FailedTradesManager.java index 84746a2128..295342511a 100644 --- a/core/src/main/java/bisq/core/trade/failed/FailedTradesManager.java +++ b/core/src/main/java/bisq/core/trade/bisq_v1/FailedTradesManager.java @@ -15,17 +15,14 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.failed; +package bisq.core.trade.bisq_v1; import bisq.core.btc.model.AddressEntry; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.offer.Offer; import bisq.core.provider.price.PriceFeedService; -import bisq.core.trade.DumpDelayedPayoutTx; -import bisq.core.trade.TradableList; -import bisq.core.trade.Trade; -import bisq.core.trade.TradeUtil; -import bisq.core.trade.closed.CleanupMailboxMessages; +import bisq.core.trade.model.TradableList; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.common.crypto.KeyRing; import bisq.common.persistence.PersistenceManager; @@ -50,7 +47,7 @@ public class FailedTradesManager implements PersistedDataHost { private final KeyRing keyRing; private final PriceFeedService priceFeedService; private final BtcWalletService btcWalletService; - private final CleanupMailboxMessages cleanupMailboxMessages; + private final CleanupMailboxMessagesService cleanupMailboxMessagesService; private final PersistenceManager> persistenceManager; private final TradeUtil tradeUtil; private final DumpDelayedPayoutTx dumpDelayedPayoutTx; @@ -63,12 +60,12 @@ public class FailedTradesManager implements PersistedDataHost { BtcWalletService btcWalletService, PersistenceManager> persistenceManager, TradeUtil tradeUtil, - CleanupMailboxMessages cleanupMailboxMessages, + CleanupMailboxMessagesService cleanupMailboxMessagesService, DumpDelayedPayoutTx dumpDelayedPayoutTx) { this.keyRing = keyRing; this.priceFeedService = priceFeedService; this.btcWalletService = btcWalletService; - this.cleanupMailboxMessages = cleanupMailboxMessages; + this.cleanupMailboxMessagesService = cleanupMailboxMessagesService; this.dumpDelayedPayoutTx = dumpDelayedPayoutTx; this.persistenceManager = persistenceManager; this.tradeUtil = tradeUtil; @@ -90,7 +87,7 @@ public class FailedTradesManager implements PersistedDataHost { } public void onAllServicesInitialized() { - cleanupMailboxMessages.handleTrades(failedTrades.getList()); + cleanupMailboxMessagesService.handleTrades(failedTrades.getList()); } public void add(Trade trade) { diff --git a/core/src/main/java/bisq/core/trade/TradeDataValidation.java b/core/src/main/java/bisq/core/trade/bisq_v1/TradeDataValidation.java similarity index 99% rename from core/src/main/java/bisq/core/trade/TradeDataValidation.java rename to core/src/main/java/bisq/core/trade/bisq_v1/TradeDataValidation.java index 013dcdb42a..e1a2eb2ecb 100644 --- a/core/src/main/java/bisq/core/trade/TradeDataValidation.java +++ b/core/src/main/java/bisq/core/trade/bisq_v1/TradeDataValidation.java @@ -15,13 +15,14 @@ * along with Bisq. If not, see . */ -package bisq.core.trade; +package bisq.core.trade.bisq_v1; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.dao.DaoFacade; import bisq.core.offer.Offer; import bisq.core.support.SupportType; import bisq.core.support.dispute.Dispute; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.util.validation.RegexValidatorFactory; import bisq.network.p2p.NodeAddress; @@ -304,7 +305,7 @@ public class TradeDataValidation { Offer offer = checkNotNull(trade.getOffer()); Coin msOutputAmount = offer.getBuyerSecurityDeposit() .add(offer.getSellerSecurityDeposit()) - .add(checkNotNull(trade.getTradeAmount())); + .add(checkNotNull(trade.getAmount())); if (!output.getValue().equals(msOutputAmount)) { errorMsg = "Output value of deposit tx and delayed payout tx is not matching. Output: " + output + " / msOutputAmount: " + msOutputAmount; diff --git a/core/src/main/java/bisq/core/trade/handlers/TradeResultHandler.java b/core/src/main/java/bisq/core/trade/bisq_v1/TradeResultHandler.java similarity index 83% rename from core/src/main/java/bisq/core/trade/handlers/TradeResultHandler.java rename to core/src/main/java/bisq/core/trade/bisq_v1/TradeResultHandler.java index aab6dc4e46..3ae8a13fba 100644 --- a/core/src/main/java/bisq/core/trade/handlers/TradeResultHandler.java +++ b/core/src/main/java/bisq/core/trade/bisq_v1/TradeResultHandler.java @@ -15,10 +15,8 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.handlers; +package bisq.core.trade.bisq_v1; -import bisq.core.trade.Trade; - -public interface TradeResultHandler { - void handleResult(Trade trade); +public interface TradeResultHandler { + void handleResult(T trade); } diff --git a/core/src/main/java/bisq/core/trade/TradeTxException.java b/core/src/main/java/bisq/core/trade/bisq_v1/TradeTxException.java similarity index 96% rename from core/src/main/java/bisq/core/trade/TradeTxException.java rename to core/src/main/java/bisq/core/trade/bisq_v1/TradeTxException.java index cbc7966067..947257aa89 100644 --- a/core/src/main/java/bisq/core/trade/TradeTxException.java +++ b/core/src/main/java/bisq/core/trade/bisq_v1/TradeTxException.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.trade; +package bisq.core.trade.bisq_v1; public class TradeTxException extends Exception { public TradeTxException(String message) { diff --git a/core/src/main/java/bisq/core/trade/TradeUtil.java b/core/src/main/java/bisq/core/trade/bisq_v1/TradeUtil.java similarity index 77% rename from core/src/main/java/bisq/core/trade/TradeUtil.java rename to core/src/main/java/bisq/core/trade/bisq_v1/TradeUtil.java index a026f6ab98..3f99e7dea9 100644 --- a/core/src/main/java/bisq/core/trade/TradeUtil.java +++ b/core/src/main/java/bisq/core/trade/bisq_v1/TradeUtil.java @@ -15,13 +15,22 @@ * along with Bisq. If not, see . */ -package bisq.core.trade; +package bisq.core.trade.bisq_v1; import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.filter.FilterManager; import bisq.core.locale.Res; import bisq.core.offer.Offer; +import bisq.core.payment.payload.PaymentAccountPayload; +import bisq.core.trade.model.TradeModel; +import bisq.core.trade.model.bisq_v1.Contract; +import bisq.core.trade.model.bisq_v1.Trade; + +import bisq.network.p2p.NodeAddress; import bisq.common.crypto.KeyRing; +import bisq.common.handlers.ErrorMessageHandler; +import bisq.common.handlers.ResultHandler; import bisq.common.util.Tuple2; import bisq.common.util.Utilities; @@ -224,4 +233,33 @@ public class TradeUtil { : Res.get("formatter.asTaker", currencyCode, Res.get("shared.seller")); } } + + public static void applyFilter(TradeModel tradeModel, + FilterManager filterManager, + NodeAddress nodeAddress, + @Nullable PaymentAccountPayload paymentAccountPayload, + ResultHandler complete, + ErrorMessageHandler failed) { + if (filterManager.isNodeAddressBanned(nodeAddress)) { + failed.handleErrorMessage("Other trader is banned by their node address.\n" + + "tradingPeerNodeAddress=" + nodeAddress); + } else if (filterManager.isOfferIdBanned(tradeModel.getId())) { + failed.handleErrorMessage("Offer ID is banned.\n" + "Offer ID=" + tradeModel.getId()); + } else if (tradeModel.getOffer() != null && + filterManager.isCurrencyBanned(tradeModel.getOffer().getCurrencyCode())) { + failed.handleErrorMessage("Currency is banned.\n" + + "Currency code=" + tradeModel.getOffer().getCurrencyCode()); + } else if (filterManager.isPaymentMethodBanned(checkNotNull(tradeModel.getOffer()).getPaymentMethod())) { + failed.handleErrorMessage("Payment method is banned.\n" + + "Payment method=" + tradeModel.getOffer().getPaymentMethod().getId()); + } else if (paymentAccountPayload != null && filterManager.arePeersPaymentAccountDataBanned(paymentAccountPayload)) { + failed.handleErrorMessage("Other trader is banned by their trading account data.\n" + + "paymentAccountPayload=" + paymentAccountPayload.getPaymentDetails()); + } else if (filterManager.requireUpdateToNewVersionForTrading()) { + failed.handleErrorMessage("Your version of Bisq is not compatible for trading anymore. " + + "Please update to the latest Bisq version at https://bisq.network/downloads."); + } else { + complete.handleResult(); + } + } } diff --git a/core/src/main/java/bisq/core/trade/handlers/TransactionResultHandler.java b/core/src/main/java/bisq/core/trade/bisq_v1/TransactionResultHandler.java similarity index 96% rename from core/src/main/java/bisq/core/trade/handlers/TransactionResultHandler.java rename to core/src/main/java/bisq/core/trade/bisq_v1/TransactionResultHandler.java index ca3531226b..3a9d2b42f0 100644 --- a/core/src/main/java/bisq/core/trade/handlers/TransactionResultHandler.java +++ b/core/src/main/java/bisq/core/trade/bisq_v1/TransactionResultHandler.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.handlers; +package bisq.core.trade.bisq_v1; import org.bitcoinj.core.Transaction; diff --git a/core/src/main/java/bisq/core/trade/bsq_swap/BsqSwapCalculation.java b/core/src/main/java/bisq/core/trade/bsq_swap/BsqSwapCalculation.java new file mode 100644 index 0000000000..b65f19e19b --- /dev/null +++ b/core/src/main/java/bisq/core/trade/bsq_swap/BsqSwapCalculation.java @@ -0,0 +1,245 @@ +/* + * 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.core.trade.bsq_swap; + +import bisq.core.btc.exceptions.InsufficientBsqException; +import bisq.core.btc.model.RawTransactionInput; +import bisq.core.btc.wallet.BsqWalletService; +import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.Restrictions; +import bisq.core.monetary.Volume; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; + +import bisq.common.util.MathUtils; +import bisq.common.util.Tuple2; + +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.InsufficientMoneyException; + +import java.util.List; + +import lombok.extern.slf4j.Slf4j; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * The fees can be paid either by adding them to the inputs or by reducing them from the outputs. As we want to avoid + * extra inputs only needed for the fees (tx fee in case of buyer and trade fee in case of seller) we let + * the buyer add the trade fee to the BSQ input and reduce the tx fee from the BTC output. For the seller its the + * other way round. + * + * + * The example numbers are: + * BTC trade amount 100000000 sat (1 BTC) + * BSQ trade amount: 5000000 sat (50000.00 BSQ) + * Buyer trade fee: 50 sat (0.5 BSQ) + * Seller trade fee: 150 sat (1.5 BSQ) + * Buyer tx fee: 1950 sat (total tx fee would be 2000 but we subtract the 50 sat trade fee) + * Seller tx fee: 1850 sat (total tx fee would be 2000 but we subtract the 150 sat trade fee) + * + * Input buyer: BSQ trade amount + buyer trade fee 5000000 + 50 + * Input seller: BTC trade amount + seller tx fee 100000000 + 1850 + * Output seller: BSQ trade amount - sellers trade fee 5000000 - 150 + * Output buyer: BSQ change 0 + * Output buyer: BTC trade amount - buyers tx fee 100000000 - 1950 + * Output seller: BTC change 0 + * Tx fee: Buyer tx fee + seller tx fee + buyer trade fee + seller trade fee 1950 + 1850 + 50 + 150 + */ +@Slf4j +public class BsqSwapCalculation { + private static final int MIN_SELLERS_TX_SIZE = 104; + + // Estimated size in case we do not have enough funds to calculate it from wallet inputs. + // We use 3 non segwit inputs. 5 + 3*149 + 62 = 514 + public static final int ESTIMATED_V_BYTES = 514; + + // Buyer + public static Coin getBuyersBsqInputValue(BsqSwapTrade trade, long buyersTradeFee) { + return getBuyersBsqInputValue(trade.getBsqTradeAmount(), buyersTradeFee); + } + + public static Coin getBuyersBsqInputValue(long bsqTradeAmount, long buyersTradeFee) { + return Coin.valueOf(bsqTradeAmount + buyersTradeFee); + } + + public static Coin getBuyersBtcPayoutValue(BsqSwapTrade trade, int buyersVBytesSize, long buyerTradeFee) { + return getBuyersBtcPayoutValue(trade.getAmountAsLong(), trade.getTxFeePerVbyte(), buyersVBytesSize, buyerTradeFee); + } + + public static Coin getBuyersBtcPayoutValue(long btcTradeAmount, + long txFeePerVbyte, + int buyersVBytesSize, + long buyerTradeFee) { + long buyersTxFee = getAdjustedTxFee(txFeePerVbyte, buyersVBytesSize, buyerTradeFee); + return getBuyersBtcPayoutValue(btcTradeAmount, buyersTxFee); + } + + public static Coin getBuyersBtcPayoutValue(BsqWalletService bsqWalletService, + Coin bsqTradeAmount, + Coin btcTradeAmount, + long txFeePerVbyte, + long buyerTradeFee) throws InsufficientBsqException { + Tuple2, Coin> inputsAndChange = getBuyersBsqInputsAndChange(bsqWalletService, bsqTradeAmount.getValue(), buyerTradeFee); + int buyersVBytesSize = BsqSwapCalculation.getVBytesSize(inputsAndChange.first, inputsAndChange.second.getValue()); + long buyersTxFee = getAdjustedTxFee(txFeePerVbyte, buyersVBytesSize, buyerTradeFee); + return getBuyersBtcPayoutValue(btcTradeAmount.getValue(), buyersTxFee); + } + + public static Tuple2, Coin> getBuyersBsqInputsAndChange(BsqWalletService bsqWalletService, + long amount, + long buyersTradeFee) + throws InsufficientBsqException { + Coin required = getBuyersBsqInputValue(amount, buyersTradeFee); + return bsqWalletService.getBuyersBsqInputsForBsqSwapTx(required); + } + + public static Coin getEstimatedBuyersBtcPayoutValue(Coin btcTradeAmount, + long txFeePerVbyte, + long buyerTradeFee) { + // Use estimated size. This is used in case the wallet has not enough fund so we cannot calculate the exact + // amount but we still want to provide some estimated value. + long buyersTxFee = getAdjustedTxFee(txFeePerVbyte, ESTIMATED_V_BYTES, buyerTradeFee); + return getBuyersBtcPayoutValue(btcTradeAmount.getValue(), buyersTxFee); + } + + private static Coin getBuyersBtcPayoutValue(long btcTradeAmount, long buyerTxFee) { + return Coin.valueOf(btcTradeAmount - buyerTxFee); + } + + // Seller + public static Coin getSellersBtcInputValue(BsqSwapTrade trade, int sellersTxSize, long sellersTradeFee) { + return getSellersBtcInputValue(trade.getAmountAsLong(), trade.getTxFeePerVbyte(), sellersTxSize, sellersTradeFee); + } + + public static Coin getSellersBtcInputValue(long btcTradeAmount, + long txFeePerVbyte, + int sellersVBytesSize, + long sellersTradeFee) { + long sellersTxFee = getAdjustedTxFee(txFeePerVbyte, sellersVBytesSize, sellersTradeFee); + return getSellersBtcInputValue(btcTradeAmount, sellersTxFee); + } + + public static Coin getSellersBtcInputValue(BtcWalletService btcWalletService, + Coin btcTradeAmount, + long txFeePerVbyte, + long sellersTradeFee) throws InsufficientMoneyException { + Tuple2, Coin> inputsAndChange = getSellersBtcInputsAndChange(btcWalletService, + btcTradeAmount.getValue(), + txFeePerVbyte, + sellersTradeFee); + int sellersVBytesSize = getVBytesSize(inputsAndChange.first, inputsAndChange.second.getValue()); + long sellersTxFee = getAdjustedTxFee(txFeePerVbyte, sellersVBytesSize, sellersTradeFee); + return getSellersBtcInputValue(btcTradeAmount.getValue(), sellersTxFee); + } + + public static Coin getEstimatedSellersBtcInputValue(Coin btcTradeAmount, + long txFeePerVbyte, + long sellersTradeFee) { + // Use estimated size. This is used in case the wallet has not enough fund so we cannot calculate the exact + // amount but we still want to provide some estimated value. + long sellersTxFee = getAdjustedTxFee(txFeePerVbyte, ESTIMATED_V_BYTES, sellersTradeFee); + return getSellersBtcInputValue(btcTradeAmount.getValue(), sellersTxFee); + } + + public static Coin getSellersBtcInputValue(long btcTradeAmount, long sellerTxFee) { + return Coin.valueOf(btcTradeAmount + sellerTxFee); + } + + public static Coin getSellersBsqPayoutValue(BsqSwapTrade trade, long sellerTradeFee) { + return getSellersBsqPayoutValue(trade.getBsqTradeAmount(), sellerTradeFee); + } + + public static Coin getSellersBsqPayoutValue(long bsqTradeAmount, long sellerTradeFee) { + return Coin.valueOf(bsqTradeAmount - sellerTradeFee); + } + + // Tx fee estimation + public static Tuple2, Coin> getSellersBtcInputsAndChange(BtcWalletService btcWalletService, + long amount, + long txFeePerVbyte, + long sellersTradeFee) + throws InsufficientMoneyException { + // Figure out how large out tx will be + int iterations = 0; + Tuple2, Coin> inputsAndChange; + Coin previous = null; + + // At first we try with min. tx size + int sellersTxSize = MIN_SELLERS_TX_SIZE; + Coin change = Coin.ZERO; + Coin required = getSellersBtcInputValue(amount, txFeePerVbyte, sellersTxSize, sellersTradeFee); + + // We do a first calculation here to get the size of the inputs (segwit or not) and we adjust the sellersTxSize + // so that we avoid to get into dangling states. + inputsAndChange = btcWalletService.getInputsAndChange(required); + sellersTxSize = getVBytesSize(inputsAndChange.first, 0); + required = getSellersBtcInputValue(amount, txFeePerVbyte, sellersTxSize, sellersTradeFee); + + // As fee calculation is not deterministic it could be that we toggle between a too small and too large + // inputs. We would take the latest result before we break iteration. Worst case is that we under- or + // overpay a bit. As fee rate is anyway an estimation we ignore that imperfection. + while (iterations < 10 && !required.equals(previous)) { + inputsAndChange = btcWalletService.getInputsAndChange(required); + previous = required; + + // We calculate more exact tx size based on resulted inputs and change + change = inputsAndChange.second; + if (Restrictions.isDust(change)) { + log.warn("We got a change below dust. We ignore that and use it as miner fee."); + change = Coin.ZERO; + } + + sellersTxSize = getVBytesSize(inputsAndChange.first, change.getValue()); + required = getSellersBtcInputValue(amount, txFeePerVbyte, sellersTxSize, sellersTradeFee); + + iterations++; + } + + checkNotNull(inputsAndChange); + + return new Tuple2<>(inputsAndChange.first, change); + } + + // Tx fee + + // See https://bitcoin.stackexchange.com/questions/87275/how-to-calculate-segwit-transaction-fee-in-bytes + public static int getVBytesSize(List inputs, long change) { + int size = 5; // Half of base tx size (10) + size += inputs.stream() + .mapToLong(input -> input.isSegwit() ? 68 : 149) + .sum(); + size += change > 0 ? 62 : 31; + return size; + } + + public static long getAdjustedTxFee(BsqSwapTrade trade, int vBytes, long tradeFee) { + return getAdjustedTxFee(trade.getTxFeePerVbyte(), vBytes, tradeFee); + } + + public static long getAdjustedTxFee(long txFeePerVbyte, int vBytes, long tradeFee) { + return txFeePerVbyte * vBytes - tradeFee; + } + + // Convert BTC trade amount to BSQ amount + public static Coin getBsqTradeAmount(Volume volume) { + // We treat BSQ as altcoin with smallest unit exponent 8 but we use 2 instead. + // To avoid a larger refactoring of the monetary domain we just hack in the conversion here + // by removing the last 6 digits. + return Coin.valueOf(MathUtils.roundDoubleToLong(MathUtils.scaleDownByPowerOf10(volume.getValue(), 6))); + } +} diff --git a/core/src/main/java/bisq/core/trade/bsq_swap/BsqSwapTakeOfferRequestVerification.java b/core/src/main/java/bisq/core/trade/bsq_swap/BsqSwapTakeOfferRequestVerification.java new file mode 100644 index 0000000000..6918116728 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/bsq_swap/BsqSwapTakeOfferRequestVerification.java @@ -0,0 +1,106 @@ +/* + * 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.core.trade.bsq_swap; + +import bisq.core.offer.Offer; +import bisq.core.offer.OpenOffer; +import bisq.core.offer.OpenOfferManager; +import bisq.core.provider.fee.FeeService; +import bisq.core.trade.protocol.bsq_swap.messages.BsqSwapRequest; +import bisq.core.util.Validator; +import bisq.core.util.coin.CoinUtil; + +import bisq.network.p2p.NodeAddress; + +import bisq.common.crypto.KeyRing; + +import org.bitcoinj.core.Coin; + +import java.util.Date; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +import lombok.extern.slf4j.Slf4j; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static java.lang.Math.abs; + +@Slf4j +public class BsqSwapTakeOfferRequestVerification { + + public static boolean isValid(OpenOfferManager openOfferManager, + FeeService feeService, + KeyRing keyRing, + NodeAddress peer, + BsqSwapRequest request) { + try { + log.info("Received {} from {} with tradeId {} and uid {}", + request.getClass().getSimpleName(), peer, request.getTradeId(), request.getUid()); + + checkNotNull(request); + Validator.nonEmptyStringOf(request.getTradeId()); + + checkArgument(request.getSenderNodeAddress().equals(peer), "Node address not matching"); + + Optional openOfferOptional = openOfferManager.getOpenOfferById(request.getTradeId()); + checkArgument(openOfferOptional.isPresent(), "Offer not found in open offers"); + + OpenOffer openOffer = openOfferOptional.get(); + checkArgument(openOffer.getState() == OpenOffer.State.AVAILABLE, "Offer not available"); + + Offer offer = openOffer.getOffer(); + Validator.checkTradeId(offer.getId(), request); + checkArgument(offer.isMyOffer(keyRing), "Offer must be mine"); + + long tradeAmount = request.getTradeAmount(); + Coin amountAsCoin = Coin.valueOf(request.getTradeAmount()); + + checkArgument(tradeAmount >= offer.getMinAmount().getValue() && + tradeAmount <= offer.getAmount().getValue(), "TradeAmount not within offers amount range"); + checkArgument(isDateInTolerance(request), "Trade date is out of tolerance"); + checkArgument(isTxFeeInTolerance(request, feeService), "Miner fee from taker not in tolerance"); + checkArgument(request.getMakerFee() == Objects.requireNonNull(CoinUtil.getMakerFee(false, amountAsCoin)).value); + checkArgument(request.getTakerFee() == CoinUtil.getTakerFee(false, amountAsCoin).value); + } catch (Exception e) { + log.error("BsqSwapTakeOfferRequestVerification failed. Request={}, peer={}, error={}", request, peer, e.toString()); + return false; + } + + return true; + } + + private static boolean isDateInTolerance(BsqSwapRequest request) { + return abs(request.getTradeDate() - new Date().getTime()) < TimeUnit.MINUTES.toMillis(10); + } + + private static boolean isTxFeeInTolerance(BsqSwapRequest request, FeeService feeService) { + double myFee = (double) feeService.getTxFeePerVbyte().getValue(); + double peersFee = (double) Coin.valueOf(request.getTxFeePerVbyte()).getValue(); + // Allow for 10% diff in mining fee, ie, maker will accept taker fee that's 10% + // off their own fee from service. Both parties will use the same fee while + // creating the bsq swap tx + double diff = abs(1 - myFee / peersFee); + boolean isInTolerance = diff < 0.5; + if (!isInTolerance) { + log.warn("Miner fee from taker not in tolerance. myFee={}, peersFee={}, diff={}", myFee, peersFee, diff); + } + return isInTolerance; + } +} diff --git a/core/src/main/java/bisq/core/trade/bsq_swap/BsqSwapTradeManager.java b/core/src/main/java/bisq/core/trade/bsq_swap/BsqSwapTradeManager.java new file mode 100644 index 0000000000..f5b9a9f680 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/bsq_swap/BsqSwapTradeManager.java @@ -0,0 +1,148 @@ +/* + * 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.core.trade.bsq_swap; + +import bisq.core.btc.wallet.BsqWalletService; +import bisq.core.offer.Offer; +import bisq.core.provider.price.PriceFeedService; +import bisq.core.trade.model.TradableList; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; + +import bisq.common.crypto.KeyRing; +import bisq.common.persistence.PersistenceManager; +import bisq.common.proto.persistable.PersistedDataHost; + +import org.bitcoinj.core.TransactionConfidence; + +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import com.google.common.collect.ImmutableList; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; + +import javafx.collections.ObservableList; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Singleton +public class BsqSwapTradeManager implements PersistedDataHost { + private final BsqWalletService bsqWalletService; + private final PersistenceManager> persistenceManager; + private final TradableList bsqSwapTrades = new TradableList<>(); + private final KeyRing keyRing; + private final PriceFeedService priceFeedService; + + // Used for listening for notifications in the UI + @Getter + private final ObjectProperty completedBsqSwapTrade = new SimpleObjectProperty<>(); + + @Inject + public BsqSwapTradeManager(KeyRing keyRing, + PriceFeedService priceFeedService, + BsqWalletService bsqWalletService, + PersistenceManager> persistenceManager) { + this.keyRing = keyRing; + this.priceFeedService = priceFeedService; + this.bsqWalletService = bsqWalletService; + this.persistenceManager = persistenceManager; + + this.persistenceManager.initialize(bsqSwapTrades, "BsqSwapTrades", PersistenceManager.Source.PRIVATE); + } + + @Override + public void readPersisted(Runnable completeHandler) { + persistenceManager.readPersisted(persisted -> { + bsqSwapTrades.setAll(persisted.getList()); + bsqSwapTrades.stream() + .filter(bsqSwapTrade -> bsqSwapTrade.getOffer() != null) + .forEach(bsqSwapTrade -> bsqSwapTrade.getOffer().setPriceFeedService(priceFeedService)); + completeHandler.run(); + }, + completeHandler); + } + + public void onAllServicesInitialized() { + } + + public void onTradeCompleted(BsqSwapTrade bsqSwapTrade) { + if (findBsqSwapTradeById(bsqSwapTrade.getId()).isPresent()) { + return; + } + + if (bsqSwapTrades.add(bsqSwapTrade)) { + requestPersistence(); + + completedBsqSwapTrade.set(bsqSwapTrade); + } + } + + public void resetCompletedBsqSwapTrade() { + completedBsqSwapTrade.set(null); + } + + public boolean wasMyOffer(Offer offer) { + return offer.isMyOffer(keyRing); + } + + public ObservableList getObservableList() { + return bsqSwapTrades.getObservableList(); + } + + public List getBsqSwapTrades() { + return ImmutableList.copyOf(new ArrayList<>(getObservableList())); + } + + public Optional findBsqSwapTradeById(String id) { + return bsqSwapTrades.stream().filter(e -> e.getId().equals(id)).findFirst(); + } + + public Stream getUnconfirmedBsqSwapTrades() { + return getObservableList().stream().filter(this::isUnconfirmed); + } + + public Stream getConfirmedBsqSwapTrades() { + return getObservableList().stream().filter(this::isConfirmed); + } + + private boolean isUnconfirmed(BsqSwapTrade bsqSwapTrade) { + return matchesConfidence(bsqSwapTrade, TransactionConfidence.ConfidenceType.PENDING); + } + + private boolean isConfirmed(BsqSwapTrade bsqSwapTrade) { + return matchesConfidence(bsqSwapTrade, TransactionConfidence.ConfidenceType.BUILDING); + } + + private boolean matchesConfidence(BsqSwapTrade bsqSwapTrade, TransactionConfidence.ConfidenceType confidenceTyp) { + TransactionConfidence confidenceForTxId = bsqWalletService.getConfidenceForTxId(bsqSwapTrade.getTxId()); + return confidenceForTxId != null && + confidenceForTxId.getConfidenceType() == confidenceTyp; + } + + private void requestPersistence() { + persistenceManager.requestPersistence(); + } +} diff --git a/core/src/main/java/bisq/core/trade/closed/ClosedTradableManager.java b/core/src/main/java/bisq/core/trade/closed/ClosedTradableManager.java deleted file mode 100644 index 01c36beb4d..0000000000 --- a/core/src/main/java/bisq/core/trade/closed/ClosedTradableManager.java +++ /dev/null @@ -1,124 +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.core.trade.closed; - -import bisq.core.offer.Offer; -import bisq.core.provider.price.PriceFeedService; -import bisq.core.trade.DumpDelayedPayoutTx; -import bisq.core.trade.Tradable; -import bisq.core.trade.TradableList; -import bisq.core.trade.Trade; - -import bisq.common.crypto.KeyRing; -import bisq.common.persistence.PersistenceManager; -import bisq.common.proto.persistable.PersistedDataHost; - -import com.google.inject.Inject; - -import com.google.common.collect.ImmutableList; - -import javafx.collections.ObservableList; - -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import lombok.extern.slf4j.Slf4j; - -@Slf4j -public class ClosedTradableManager implements PersistedDataHost { - private final PersistenceManager> persistenceManager; - private final TradableList closedTradables = new TradableList<>(); - private final KeyRing keyRing; - private final PriceFeedService priceFeedService; - private final CleanupMailboxMessages cleanupMailboxMessages; - private final DumpDelayedPayoutTx dumpDelayedPayoutTx; - - @Inject - public ClosedTradableManager(KeyRing keyRing, - PriceFeedService priceFeedService, - PersistenceManager> persistenceManager, - CleanupMailboxMessages cleanupMailboxMessages, - DumpDelayedPayoutTx dumpDelayedPayoutTx) { - this.keyRing = keyRing; - this.priceFeedService = priceFeedService; - this.cleanupMailboxMessages = cleanupMailboxMessages; - this.dumpDelayedPayoutTx = dumpDelayedPayoutTx; - this.persistenceManager = persistenceManager; - - this.persistenceManager.initialize(closedTradables, "ClosedTrades", PersistenceManager.Source.PRIVATE); - } - - @Override - public void readPersisted(Runnable completeHandler) { - persistenceManager.readPersisted(persisted -> { - closedTradables.setAll(persisted.getList()); - closedTradables.stream() - .filter(tradable -> tradable.getOffer() != null) - .forEach(tradable -> tradable.getOffer().setPriceFeedService(priceFeedService)); - dumpDelayedPayoutTx.maybeDumpDelayedPayoutTxs(closedTradables, "delayed_payout_txs_closed"); - completeHandler.run(); - }, - completeHandler); - } - - public void onAllServicesInitialized() { - cleanupMailboxMessages.handleTrades(getClosedTrades()); - } - - public void add(Tradable tradable) { - if (closedTradables.add(tradable)) { - requestPersistence(); - } - } - - public void remove(Tradable tradable) { - if (closedTradables.remove(tradable)) { - requestPersistence(); - } - } - - public boolean wasMyOffer(Offer offer) { - return offer.isMyOffer(keyRing); - } - - public ObservableList getObservableList() { - return closedTradables.getObservableList(); - } - - public List getClosedTrades() { - return ImmutableList.copyOf(getObservableList().stream() - .filter(e -> e instanceof Trade) - .map(e -> (Trade) e) - .collect(Collectors.toList())); - } - - public Optional getTradableById(String id) { - return closedTradables.stream().filter(e -> e.getId().equals(id)).findFirst(); - } - - public Stream getTradesStreamWithFundsLockedIn() { - return getClosedTrades().stream() - .filter(Trade::isFundsLockedIn); - } - - private void requestPersistence() { - persistenceManager.requestPersistence(); - } -} diff --git a/core/src/main/java/bisq/core/trade/closed/ClosedTradeUtil.java b/core/src/main/java/bisq/core/trade/closed/ClosedTradeUtil.java deleted file mode 100644 index 50fab443df..0000000000 --- a/core/src/main/java/bisq/core/trade/closed/ClosedTradeUtil.java +++ /dev/null @@ -1,390 +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.core.trade.closed; - -import bisq.core.btc.wallet.BsqWalletService; -import bisq.core.locale.CurrencyUtil; -import bisq.core.locale.Res; -import bisq.core.monetary.Altcoin; -import bisq.core.monetary.Price; -import bisq.core.monetary.Volume; -import bisq.core.offer.Offer; -import bisq.core.offer.OpenOffer; -import bisq.core.trade.Tradable; -import bisq.core.trade.Trade; -import bisq.core.trade.statistics.TradeStatisticsManager; -import bisq.core.user.Preferences; -import bisq.core.util.coin.BsqFormatter; -import bisq.core.util.coin.CoinFormatter; - -import bisq.network.p2p.NodeAddress; - -import bisq.common.util.Tuple2; - -import org.bitcoinj.core.Coin; -import org.bitcoinj.core.Monetary; -import org.bitcoinj.utils.Fiat; - -import javax.inject.Inject; -import javax.inject.Named; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; - -import lombok.extern.slf4j.Slf4j; - -import static bisq.core.trade.Trade.DisputeState.DISPUTE_CLOSED; -import static bisq.core.trade.Trade.DisputeState.MEDIATION_CLOSED; -import static bisq.core.trade.Trade.DisputeState.REFUND_REQUEST_CLOSED; -import static bisq.core.util.AveragePriceUtil.getAveragePriceTuple; -import static bisq.core.util.FormattingUtils.BTC_FORMATTER_KEY; -import static bisq.core.util.FormattingUtils.formatPercentagePrice; -import static bisq.core.util.FormattingUtils.formatPrice; -import static bisq.core.util.FormattingUtils.formatToPercentWithSymbol; -import static bisq.core.util.VolumeUtil.formatVolume; -import static bisq.core.util.VolumeUtil.formatVolumeWithCode; - -@Slf4j -public class ClosedTradeUtil { - - // Resource bundle i18n keys with Desktop UI specific property names, - // having "generic-enough" property values to be referenced in the core layer. - private static final String I18N_KEY_TOTAL_AMOUNT = "closedTradesSummaryWindow.totalAmount.value"; - private static final String I18N_KEY_TOTAL_TX_FEE = "closedTradesSummaryWindow.totalMinerFee.value"; - private static final String I18N_KEY_TOTAL_TRADE_FEE_BTC = "closedTradesSummaryWindow.totalTradeFeeInBtc.value"; - private static final String I18N_KEY_TOTAL_TRADE_FEE_BSQ = "closedTradesSummaryWindow.totalTradeFeeInBsq.value"; - - private final ClosedTradableManager closedTradableManager; - private final BsqWalletService bsqWalletService; - private final BsqFormatter bsqFormatter; - private final CoinFormatter btcFormatter; - private final Preferences preferences; - private final TradeStatisticsManager tradeStatisticsManager; - - @Inject - public ClosedTradeUtil(ClosedTradableManager closedTradableManager, - BsqWalletService bsqWalletService, - BsqFormatter bsqFormatter, - @Named(BTC_FORMATTER_KEY) CoinFormatter btcFormatter, - Preferences preferences, - TradeStatisticsManager tradeStatisticsManager) { - this.closedTradableManager = closedTradableManager; - this.bsqWalletService = bsqWalletService; - this.bsqFormatter = bsqFormatter; - this.btcFormatter = btcFormatter; - this.preferences = preferences; - this.tradeStatisticsManager = tradeStatisticsManager; - } - - public boolean wasMyOffer(Tradable tradable) { - return closedTradableManager.wasMyOffer(tradable.getOffer()); - } - - public Coin getTotalAmount(List tradableList) { - return Coin.valueOf(tradableList.stream() - .filter(e -> e instanceof Trade) - .map(e -> (Trade) e) - .mapToLong(Trade::getTradeAmountAsLong) - .sum()); - } - - public String getAmountAsString(Tradable tradable) { - if (tradable instanceof Trade) - return btcFormatter.formatCoin(((Trade) tradable).getTradeAmount()); - else - return ""; - } - - public String getTotalAmountWithVolumeAsString(Coin totalTradeAmount, Volume volume) { - return Res.get(I18N_KEY_TOTAL_AMOUNT, - btcFormatter.formatCoin(totalTradeAmount, true), - formatVolumeWithCode(volume)); - } - - public String getPriceAsString(Tradable tradable) { - if (tradable instanceof Trade) - return formatPrice(((Trade) tradable).getTradePrice()); - else - return formatPrice(tradable.getOffer().getPrice()); - } - - public String getPriceDeviationAsString(Tradable tradable) { - if (tradable.getOffer().isUseMarketBasedPrice()) { - return formatPercentagePrice(tradable.getOffer().getMarketPriceMargin()); - } else { - return Res.get("shared.na"); - } - } - - public String getVolumeAsString(Tradable tradable, boolean appendCode) { - if (tradable instanceof OpenOffer) { - return ""; - } - - Trade trade = (Trade) tradable; - return formatVolume(trade.getTradeVolume(), appendCode); - } - - public String getVolumeCurrencyAsString(Tradable tradable) { - Volume volume; - if (tradable instanceof OpenOffer) { - OpenOffer openOffer = (OpenOffer) tradable; - volume = openOffer.getOffer().getVolume(); - } else { - Trade trade = (Trade) tradable; - volume = trade.getTradeVolume(); - } - return volume != null ? volume.getCurrencyCode() : ""; - } - - public Map getTotalVolumeByCurrency(List tradableList) { - Map map = new HashMap<>(); - tradableList.stream() - .filter(e -> e instanceof Trade) - .map(e -> (Trade) e) - .map(Trade::getTradeVolume) - .filter(Objects::nonNull) - .forEach(volume -> { - String currencyCode = volume.getCurrencyCode(); - map.putIfAbsent(currencyCode, 0L); - map.put(currencyCode, volume.getValue() + map.get(currencyCode)); - }); - return map; - } - - public Map getTotalVolumeByCurrencyAsString(List tradableList) { - return getTotalVolumeByCurrency(tradableList).entrySet().stream() - .collect(Collectors.toMap(Map.Entry::getKey, - entry -> { - String currencyCode = entry.getKey(); - Monetary monetary; - if (CurrencyUtil.isCryptoCurrency(currencyCode)) { - monetary = Altcoin.valueOf(currencyCode, entry.getValue()); - } else { - monetary = Fiat.valueOf(currencyCode, entry.getValue()); - } - return formatVolumeWithCode(new Volume(monetary)); - } - )); - } - - public Volume getBsqVolumeInUsdWithAveragePrice(Coin amount) { - Tuple2 tuple = getAveragePriceTuple(preferences, tradeStatisticsManager, 30); - Price usdPrice = tuple.first; - long value = Math.round(amount.value * usdPrice.getValue() / 100d); - return new Volume(Fiat.valueOf("USD", value)); - } - - public Coin getTotalTxFee(List tradableList) { - return Coin.valueOf(tradableList.stream() - .mapToLong(tradable -> { - if (wasMyOffer(tradable) || tradable instanceof OpenOffer) { - return tradable.getOffer().getTxFee().value; - } else { - // taker pays for 3 transactions - return ((Trade) tradable).getTxFee().multiply(3).value; - } - }) - .sum()); - } - - public String getTxFeeAsString(Tradable tradable) { - if (!wasMyOffer(tradable) && (tradable instanceof Trade)) { - // taker pays for 3 transactions - return btcFormatter.formatCoin(((Trade) tradable).getTxFee().multiply(3)); - } else { - return btcFormatter.formatCoin(tradable.getOffer().getTxFee()); - } - } - - public String getTotalTxFeeAsString(Coin totalTradeAmount, Coin totalTxFee) { - double percentage = ((double) totalTxFee.value) / totalTradeAmount.value; - return Res.get(I18N_KEY_TOTAL_TX_FEE, - btcFormatter.formatCoin(totalTxFee, true), - formatToPercentWithSymbol(percentage)); - } - - public boolean isCurrencyForTradeFeeBtc(Tradable tradable) { - Offer offer = tradable.getOffer(); - if (wasMyOffer(tradable) || tradable instanceof OpenOffer) { - // I was maker so we use offer - return offer.isCurrencyForMakerFeeBtc(); - } else { - Trade trade = (Trade) tradable; - String takerFeeTxId = trade.getTakerFeeTxId(); - // If we find our tx in the bsq wallet it's a BSQ trade fee tx. - return bsqWalletService.getTransaction(takerFeeTxId) == null; - } - } - - public Coin getTotalTradeFee(List tradableList, boolean expectBtcFee) { - return Coin.valueOf(tradableList.stream() - .mapToLong(tradable -> getTradeFee(tradable, expectBtcFee)) - .sum()); - } - - public String getTradeFeeAsString(Tradable tradable, boolean appendCode) { - Offer offer = tradable.getOffer(); - if (wasMyOffer(tradable) || tradable instanceof OpenOffer) { - CoinFormatter formatter = offer.isCurrencyForMakerFeeBtc() ? btcFormatter : bsqFormatter; - return formatter.formatCoin(offer.getMakerFee(), appendCode); - } else { - Trade trade = (Trade) tradable; - String takerFeeTxId = trade.getTakerFeeTxId(); - if (bsqWalletService.getTransaction(takerFeeTxId) == null) { - // Was BTC fee - return btcFormatter.formatCoin(trade.getTakerFee(), appendCode); - } else { - // BSQ fee - return bsqFormatter.formatCoin(trade.getTakerFee(), appendCode); - } - } - } - - public String getTotalTradeFeeInBtcAsString(Coin totalTradeAmount, Coin totalTradeFee) { - double percentage = ((double) totalTradeFee.value) / totalTradeAmount.value; - return Res.get(I18N_KEY_TOTAL_TRADE_FEE_BTC, - btcFormatter.formatCoin(totalTradeFee, true), - formatToPercentWithSymbol(percentage)); - } - - public String getBuyerSecurityDepositAsString(Tradable tradable) { - if (tradable.getOffer() != null) - return btcFormatter.formatCoin(tradable.getOffer().getBuyerSecurityDeposit()); - else - return ""; - } - - public String getSellerSecurityDepositAsString(Tradable tradable) { - if (tradable.getOffer() != null) - return btcFormatter.formatCoin(tradable.getOffer().getSellerSecurityDeposit()); - else - return ""; - } - - public String getMarketLabel(Tradable tradable) { - return CurrencyUtil.getCurrencyPair(tradable.getOffer().getCurrencyCode()); - } - - public int getNumPastTrades(Tradable tradable) { - if (!(tradable instanceof Trade)) - return 0; - - return closedTradableManager.getClosedTrades().stream() - .filter(candidate -> { - NodeAddress candidateAddress = candidate.getTradingPeerNodeAddress(); - NodeAddress tradableAddress = ((Trade) tradable).getTradingPeerNodeAddress(); - return candidateAddress != null - && tradableAddress != null - && candidateAddress.getFullAddress().equals(tradableAddress.getFullAddress()); - }) - .collect(Collectors.toSet()) - .size(); - } - - public String getTotalTradeFeeInBsqAsString(Coin totalTradeFee, - Volume tradeAmountVolume, - Volume bsqVolumeInUsd) { - double percentage = ((double) bsqVolumeInUsd.getValue()) / tradeAmountVolume.getValue(); - return Res.get(I18N_KEY_TOTAL_TRADE_FEE_BSQ, - bsqFormatter.formatCoin(totalTradeFee, true), - formatToPercentWithSymbol(percentage)); - } - - - public String getStateAsString(Tradable tradable) { - if (tradable != null) { - if (tradable instanceof Trade) { - Trade trade = (Trade) tradable; - - if (trade.isWithdrawn() || trade.isPayoutPublished()) { - return Res.get("portfolio.closed.completed"); - } else if (trade.getDisputeState() == DISPUTE_CLOSED) { - return Res.get("portfolio.closed.ticketClosed"); - } else if (trade.getDisputeState() == MEDIATION_CLOSED) { - return Res.get("portfolio.closed.mediationTicketClosed"); - } else if (trade.getDisputeState() == REFUND_REQUEST_CLOSED) { - return Res.get("portfolio.closed.ticketClosed"); - } else { - log.error("That must not happen. We got a pending state but we are in" - + " the closed trades list. state={}", - trade.getState().name()); - return Res.get("shared.na"); - } - } else if (tradable instanceof OpenOffer) { - OpenOffer.State state = ((OpenOffer) tradable).getState(); - log.trace("OpenOffer state={}", state); - switch (state) { - case AVAILABLE: - case RESERVED: - case CLOSED: - case DEACTIVATED: - log.error("Invalid state {}", state); - return state.name(); - case CANCELED: - return Res.get("portfolio.closed.canceled"); - default: - log.error("Unhandled state {}", state); - return state.name(); - } - } - } - return ""; - } - - protected long getTradeFee(Tradable tradable, boolean expectBtcFee) { - Offer offer = tradable.getOffer(); - if (wasMyOffer(tradable) || tradable instanceof OpenOffer) { - String makerFeeTxId = offer.getOfferFeePaymentTxId(); - boolean notInBsqWallet = bsqWalletService.getTransaction(makerFeeTxId) == null; - if (expectBtcFee) { - if (notInBsqWallet) { - return offer.getMakerFee().value; - } else { - return 0; - } - } else { - if (notInBsqWallet) { - return 0; - } else { - return offer.getMakerFee().value; - } - } - } else { - Trade trade = (Trade) tradable; - String takerFeeTxId = trade.getTakerFeeTxId(); - boolean notInBsqWallet = bsqWalletService.getTransaction(takerFeeTxId) == null; - if (expectBtcFee) { - if (notInBsqWallet) { - return trade.getTakerFee().value; - } else { - return 0; - } - } else { - if (notInBsqWallet) { - return 0; - } else { - return trade.getTakerFee().value; - } - } - } - } -} diff --git a/core/src/main/java/bisq/core/trade/MakerTrade.java b/core/src/main/java/bisq/core/trade/model/MakerTrade.java similarity index 95% rename from core/src/main/java/bisq/core/trade/MakerTrade.java rename to core/src/main/java/bisq/core/trade/model/MakerTrade.java index 2e0a41182d..b911758da8 100644 --- a/core/src/main/java/bisq/core/trade/MakerTrade.java +++ b/core/src/main/java/bisq/core/trade/model/MakerTrade.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.trade; +package bisq.core.trade.model; public interface MakerTrade { } diff --git a/core/src/main/java/bisq/core/trade/TakerTrade.java b/core/src/main/java/bisq/core/trade/model/TakerTrade.java similarity index 95% rename from core/src/main/java/bisq/core/trade/TakerTrade.java rename to core/src/main/java/bisq/core/trade/model/TakerTrade.java index a6d82ac840..bb163a6051 100644 --- a/core/src/main/java/bisq/core/trade/TakerTrade.java +++ b/core/src/main/java/bisq/core/trade/model/TakerTrade.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.trade; +package bisq.core.trade.model; public interface TakerTrade { } diff --git a/core/src/main/java/bisq/core/trade/model/Tradable.java b/core/src/main/java/bisq/core/trade/model/Tradable.java new file mode 100644 index 0000000000..96739a4aa1 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/model/Tradable.java @@ -0,0 +1,75 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.model; + +import bisq.core.monetary.Price; +import bisq.core.monetary.Volume; +import bisq.core.offer.Offer; + +import bisq.common.proto.persistable.PersistablePayload; + +import org.bitcoinj.core.Coin; + +import java.util.Date; +import java.util.Optional; + +public interface Tradable extends PersistablePayload { + Offer getOffer(); + + Date getDate(); + + String getId(); + + String getShortId(); + + default Optional asTradeModel() { + if (this instanceof TradeModel) { + return Optional.of(((TradeModel) this)); + } else { + return Optional.empty(); + } + } + + default Optional getOptionalVolume() { + return asTradeModel().map(TradeModel::getVolume).or(() -> Optional.ofNullable(getOffer().getVolume())); + } + + default Optional getOptionalPrice() { + return asTradeModel().map(TradeModel::getPrice).or(() -> Optional.ofNullable(getOffer().getPrice())); + } + + default Optional getOptionalAmount() { + return asTradeModel().map(TradeModel::getAmount); + } + + default Optional getOptionalAmountAsLong() { + return asTradeModel().map(TradeModel::getAmountAsLong); + } + + default Optional getOptionalTxFee() { + return asTradeModel().map(TradeModel::getTxFee); + } + + default Optional getOptionalTakerFee() { + return asTradeModel().map(TradeModel::getTakerFee); + } + + default Optional getOptionalMakerFee() { + return asTradeModel().map(TradeModel::getMakerFee).or(() -> Optional.ofNullable(getOffer().getMakerFee())); + } +} diff --git a/core/src/main/java/bisq/core/trade/TradableList.java b/core/src/main/java/bisq/core/trade/model/TradableList.java similarity index 76% rename from core/src/main/java/bisq/core/trade/TradableList.java rename to core/src/main/java/bisq/core/trade/model/TradableList.java index c3a668708a..345766bc40 100644 --- a/core/src/main/java/bisq/core/trade/TradableList.java +++ b/core/src/main/java/bisq/core/trade/model/TradableList.java @@ -15,11 +15,19 @@ * along with Bisq. If not, see . */ -package bisq.core.trade; +package bisq.core.trade.model; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.offer.OpenOffer; import bisq.core.proto.CoreProtoResolver; +import bisq.core.trade.model.bisq_v1.BuyerAsMakerTrade; +import bisq.core.trade.model.bisq_v1.BuyerAsTakerTrade; +import bisq.core.trade.model.bisq_v1.SellerAsMakerTrade; +import bisq.core.trade.model.bisq_v1.SellerAsTakerTrade; +import bisq.core.trade.model.bsq_swap.BsqSwapBuyerAsMakerTrade; +import bisq.core.trade.model.bsq_swap.BsqSwapBuyerAsTakerTrade; +import bisq.core.trade.model.bsq_swap.BsqSwapSellerAsMakerTrade; +import bisq.core.trade.model.bsq_swap.BsqSwapSellerAsTakerTrade; import bisq.common.proto.ProtoUtil; import bisq.common.proto.ProtobufferRuntimeException; @@ -76,6 +84,14 @@ public final class TradableList extends PersistableListAsObs return SellerAsMakerTrade.fromProto(tradable.getSellerAsMakerTrade(), btcWalletService, coreProtoResolver); case SELLER_AS_TAKER_TRADE: return SellerAsTakerTrade.fromProto(tradable.getSellerAsTakerTrade(), btcWalletService, coreProtoResolver); + case BSQ_SWAP_BUYER_AS_MAKER_TRADE: + return BsqSwapBuyerAsMakerTrade.fromProto(tradable.getBsqSwapBuyerAsMakerTrade()); + case BSQ_SWAP_BUYER_AS_TAKER_TRADE: + return BsqSwapBuyerAsTakerTrade.fromProto(tradable.getBsqSwapBuyerAsTakerTrade()); + case BSQ_SWAP_SELLER_AS_MAKER_TRADE: + return BsqSwapSellerAsMakerTrade.fromProto(tradable.getBsqSwapSellerAsMakerTrade()); + case BSQ_SWAP_SELLER_AS_TAKER_TRADE: + return BsqSwapSellerAsTakerTrade.fromProto(tradable.getBsqSwapSellerAsTakerTrade()); default: log.error("Unknown messageCase. tradable.getMessageCase() = " + tradable.getMessageCase()); throw new ProtobufferRuntimeException("Unknown messageCase. tradable.getMessageCase() = " + diff --git a/core/src/main/java/bisq/core/trade/model/TradeModel.java b/core/src/main/java/bisq/core/trade/model/TradeModel.java new file mode 100644 index 0000000000..33361873ee --- /dev/null +++ b/core/src/main/java/bisq/core/trade/model/TradeModel.java @@ -0,0 +1,130 @@ +package bisq.core.trade.model; + +import bisq.core.monetary.Price; +import bisq.core.monetary.Volume; +import bisq.core.offer.Offer; +import bisq.core.trade.protocol.ProtocolModel; +import bisq.core.trade.protocol.Provider; +import bisq.core.trade.protocol.TradePeer; + +import bisq.network.p2p.NodeAddress; + +import bisq.common.taskrunner.Model; +import bisq.common.util.Utilities; + +import org.bitcoinj.core.Coin; + +import javafx.beans.property.ReadOnlyStringProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; + +import java.util.Date; + +import lombok.Getter; +import lombok.Setter; + +import javax.annotation.Nullable; + +public abstract class TradeModel implements Tradable, Model { + @Getter + protected final String uid; + protected final Offer offer; + @Getter + @Setter + @Nullable + protected NodeAddress tradingPeerNodeAddress; + @Getter + @Setter + protected long takeOfferDate; + @Nullable + @Getter + protected String errorMessage; + transient final private StringProperty errorMessageProperty = new SimpleStringProperty(); + + + public TradeModel(String uid, Offer offer) { + this(uid, offer, new Date().getTime(), null, null); + } + + public TradeModel(String uid, + Offer offer, + long takeOfferDate, + @Nullable NodeAddress tradingPeerNodeAddress, + @Nullable String errorMessage) { + this.uid = uid; + this.offer = offer; + this.tradingPeerNodeAddress = tradingPeerNodeAddress; + this.takeOfferDate = takeOfferDate; + setErrorMessage(errorMessage); + } + + public void initialize(Provider serviceProvider) { + } + + public abstract boolean isCompleted(); + + public abstract ProtocolModel getTradeProtocolModel(); + + public abstract TradeState getTradeState(); + + public abstract TradePhase getTradePhase(); + + public abstract long getAmountAsLong(); + + public abstract Coin getAmount(); + + @Nullable + public abstract Volume getVolume(); + + public abstract Price getPrice(); + + public abstract Coin getTxFee(); + + public abstract Coin getTakerFee(); + + public abstract Coin getMakerFee(); + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Tradable implementation + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public Offer getOffer() { + return offer; + } + + @Override + public Date getDate() { + return new Date(takeOfferDate); + } + + @Override + public String getId() { + return offer.getId(); + } + + @Override + public String getShortId() { + return Utilities.getShortId(getId()); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Setters + /////////////////////////////////////////////////////////////////////////////////////////// + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + errorMessageProperty.set(errorMessage); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Getters + /////////////////////////////////////////////////////////////////////////////////////////// + + public ReadOnlyStringProperty errorMessageProperty() { + return errorMessageProperty; + } +} diff --git a/core/src/main/java/bisq/core/trade/model/TradePhase.java b/core/src/main/java/bisq/core/trade/model/TradePhase.java new file mode 100644 index 0000000000..fdc1b54f38 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/model/TradePhase.java @@ -0,0 +1,28 @@ +/* + * 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.core.trade.model; + +public interface TradePhase { + enum Phase implements TradePhase { + DEFAULT + } + + int ordinal(); + + String name(); +} diff --git a/core/src/main/java/bisq/core/trade/model/TradeState.java b/core/src/main/java/bisq/core/trade/model/TradeState.java new file mode 100644 index 0000000000..f1e777bae1 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/model/TradeState.java @@ -0,0 +1,28 @@ +/* + * 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.core.trade.model; + +public interface TradeState { + default TradePhase getTradePhase() { + return TradePhase.Phase.DEFAULT; + } + + int ordinal(); + + String name(); +} diff --git a/core/src/main/java/bisq/core/trade/BuyerAsMakerTrade.java b/core/src/main/java/bisq/core/trade/model/bisq_v1/BuyerAsMakerTrade.java similarity index 91% rename from core/src/main/java/bisq/core/trade/BuyerAsMakerTrade.java rename to core/src/main/java/bisq/core/trade/model/bisq_v1/BuyerAsMakerTrade.java index 3a0005f824..12a4417e4c 100644 --- a/core/src/main/java/bisq/core/trade/BuyerAsMakerTrade.java +++ b/core/src/main/java/bisq/core/trade/model/bisq_v1/BuyerAsMakerTrade.java @@ -15,12 +15,14 @@ * along with Bisq. If not, see . */ -package bisq.core.trade; +package bisq.core.trade.model.bisq_v1; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.offer.Offer; import bisq.core.proto.CoreProtoResolver; -import bisq.core.trade.protocol.ProcessModel; +import bisq.core.trade.model.MakerTrade; +import bisq.core.trade.model.Tradable; +import bisq.core.trade.protocol.bisq_v1.model.ProcessModel; import bisq.network.p2p.NodeAddress; @@ -96,12 +98,17 @@ public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade { processModel, uid); - trade.setTradeAmountAsLong(proto.getTradeAmountAsLong()); - trade.setTradePrice(proto.getTradePrice()); + trade.setAmountAsLong(proto.getTradeAmountAsLong()); + trade.setPriceAsLong(proto.getTradePrice()); trade.setTradingPeerNodeAddress(proto.hasTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTradingPeerNodeAddress()) : null); return fromProto(trade, proto, coreProtoResolver); } + + @Override + public Coin getTxFee() { + return offer.getTxFee(); + } } diff --git a/core/src/main/java/bisq/core/trade/BuyerAsTakerTrade.java b/core/src/main/java/bisq/core/trade/model/bisq_v1/BuyerAsTakerTrade.java similarity index 91% rename from core/src/main/java/bisq/core/trade/BuyerAsTakerTrade.java rename to core/src/main/java/bisq/core/trade/model/bisq_v1/BuyerAsTakerTrade.java index 2ccf0fb3cb..9757be2ded 100644 --- a/core/src/main/java/bisq/core/trade/BuyerAsTakerTrade.java +++ b/core/src/main/java/bisq/core/trade/model/bisq_v1/BuyerAsTakerTrade.java @@ -15,12 +15,14 @@ * along with Bisq. If not, see . */ -package bisq.core.trade; +package bisq.core.trade.model.bisq_v1; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.offer.Offer; import bisq.core.proto.CoreProtoResolver; -import bisq.core.trade.protocol.ProcessModel; +import bisq.core.trade.model.TakerTrade; +import bisq.core.trade.model.Tradable; +import bisq.core.trade.protocol.bisq_v1.model.ProcessModel; import bisq.network.p2p.NodeAddress; @@ -108,4 +110,11 @@ public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade { proto, coreProtoResolver); } + + // The tx fee the user has paid. Not to be confused to the tradeTxFee which is the takers txFee and used for + // all trade txs + @Override + public Coin getTxFee() { + return tradeTxFee.multiply(3); + } } diff --git a/core/src/main/java/bisq/core/trade/BuyerTrade.java b/core/src/main/java/bisq/core/trade/model/bisq_v1/BuyerTrade.java similarity index 93% rename from core/src/main/java/bisq/core/trade/BuyerTrade.java rename to core/src/main/java/bisq/core/trade/model/bisq_v1/BuyerTrade.java index 82f38cf9c1..5e0d916a69 100644 --- a/core/src/main/java/bisq/core/trade/BuyerTrade.java +++ b/core/src/main/java/bisq/core/trade/model/bisq_v1/BuyerTrade.java @@ -15,11 +15,11 @@ * along with Bisq. If not, see . */ -package bisq.core.trade; +package bisq.core.trade.model.bisq_v1; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.offer.Offer; -import bisq.core.trade.protocol.ProcessModel; +import bisq.core.trade.protocol.bisq_v1.model.ProcessModel; import bisq.network.p2p.NodeAddress; @@ -85,8 +85,8 @@ public abstract class BuyerTrade extends Trade { @Override public Coin getPayoutAmount() { - checkNotNull(getTradeAmount(), "Invalid state: getTradeAmount() = null"); - return checkNotNull(getOffer()).getBuyerSecurityDeposit().add(getTradeAmount()); + checkNotNull(getAmount(), "Invalid state: getTradeAmount() = null"); + return checkNotNull(getOffer()).getBuyerSecurityDeposit().add(getAmount()); } @Override diff --git a/core/src/main/java/bisq/core/trade/Contract.java b/core/src/main/java/bisq/core/trade/model/bisq_v1/Contract.java similarity index 99% rename from core/src/main/java/bisq/core/trade/Contract.java rename to core/src/main/java/bisq/core/trade/model/bisq_v1/Contract.java index d32b1c8372..473d90af0d 100644 --- a/core/src/main/java/bisq/core/trade/Contract.java +++ b/core/src/main/java/bisq/core/trade/model/bisq_v1/Contract.java @@ -15,15 +15,16 @@ * along with Bisq. If not, see . */ -package bisq.core.trade; +package bisq.core.trade.model.bisq_v1; import bisq.core.locale.CurrencyUtil; import bisq.core.monetary.Price; import bisq.core.monetary.Volume; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.bisq_v1.OfferPayload; import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.payment.payload.PaymentMethod; import bisq.core.proto.CoreProtoResolver; +import bisq.core.util.JsonUtil; import bisq.core.util.VolumeUtil; import bisq.network.p2p.NodeAddress; @@ -350,7 +351,7 @@ public final class Contract implements NetworkPayload { } public void printDiff(@Nullable String peersContractAsJson) { - String json = Utilities.objectToJson(this); + String json = JsonUtil.objectToJson(this); String diff = StringUtils.difference(json, peersContractAsJson); if (!diff.isEmpty()) { log.warn("Diff of both contracts: \n" + diff); diff --git a/core/src/main/java/bisq/core/trade/SellerAsMakerTrade.java b/core/src/main/java/bisq/core/trade/model/bisq_v1/SellerAsMakerTrade.java similarity index 91% rename from core/src/main/java/bisq/core/trade/SellerAsMakerTrade.java rename to core/src/main/java/bisq/core/trade/model/bisq_v1/SellerAsMakerTrade.java index ccbf66a0f6..0cfadb8f5c 100644 --- a/core/src/main/java/bisq/core/trade/SellerAsMakerTrade.java +++ b/core/src/main/java/bisq/core/trade/model/bisq_v1/SellerAsMakerTrade.java @@ -15,12 +15,14 @@ * along with Bisq. If not, see . */ -package bisq.core.trade; +package bisq.core.trade.model.bisq_v1; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.offer.Offer; import bisq.core.proto.CoreProtoResolver; -import bisq.core.trade.protocol.ProcessModel; +import bisq.core.trade.model.MakerTrade; +import bisq.core.trade.model.Tradable; +import bisq.core.trade.protocol.bisq_v1.model.ProcessModel; import bisq.network.p2p.NodeAddress; @@ -97,12 +99,17 @@ public final class SellerAsMakerTrade extends SellerTrade implements MakerTrade processModel, uid); - trade.setTradeAmountAsLong(proto.getTradeAmountAsLong()); - trade.setTradePrice(proto.getTradePrice()); + trade.setAmountAsLong(proto.getTradeAmountAsLong()); + trade.setPriceAsLong(proto.getTradePrice()); trade.setTradingPeerNodeAddress(proto.hasTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTradingPeerNodeAddress()) : null); return fromProto(trade, proto, coreProtoResolver); } + + @Override + public Coin getTxFee() { + return offer.getTxFee(); + } } diff --git a/core/src/main/java/bisq/core/trade/SellerAsTakerTrade.java b/core/src/main/java/bisq/core/trade/model/bisq_v1/SellerAsTakerTrade.java similarity index 92% rename from core/src/main/java/bisq/core/trade/SellerAsTakerTrade.java rename to core/src/main/java/bisq/core/trade/model/bisq_v1/SellerAsTakerTrade.java index 11fb6c281d..c6b9f4114d 100644 --- a/core/src/main/java/bisq/core/trade/SellerAsTakerTrade.java +++ b/core/src/main/java/bisq/core/trade/model/bisq_v1/SellerAsTakerTrade.java @@ -15,12 +15,14 @@ * along with Bisq. If not, see . */ -package bisq.core.trade; +package bisq.core.trade.model.bisq_v1; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.offer.Offer; import bisq.core.proto.CoreProtoResolver; -import bisq.core.trade.protocol.ProcessModel; +import bisq.core.trade.model.TakerTrade; +import bisq.core.trade.model.Tradable; +import bisq.core.trade.protocol.bisq_v1.model.ProcessModel; import bisq.network.p2p.NodeAddress; @@ -108,4 +110,11 @@ public final class SellerAsTakerTrade extends SellerTrade implements TakerTrade proto, coreProtoResolver); } + + // The tx fee the user has paid. Not to be confused to the tradeTxFee which is the takers txFee and used for + // all trade txs + @Override + public Coin getTxFee() { + return tradeTxFee.multiply(3); + } } diff --git a/core/src/main/java/bisq/core/trade/SellerTrade.java b/core/src/main/java/bisq/core/trade/model/bisq_v1/SellerTrade.java similarity index 97% rename from core/src/main/java/bisq/core/trade/SellerTrade.java rename to core/src/main/java/bisq/core/trade/model/bisq_v1/SellerTrade.java index a87c18ddee..2284b2baaa 100644 --- a/core/src/main/java/bisq/core/trade/SellerTrade.java +++ b/core/src/main/java/bisq/core/trade/model/bisq_v1/SellerTrade.java @@ -15,12 +15,12 @@ * along with Bisq. If not, see . */ -package bisq.core.trade; +package bisq.core.trade.model.bisq_v1; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.locale.CurrencyUtil; import bisq.core.offer.Offer; -import bisq.core.trade.protocol.ProcessModel; +import bisq.core.trade.protocol.bisq_v1.model.ProcessModel; import bisq.network.p2p.NodeAddress; diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/model/bisq_v1/Trade.java similarity index 87% rename from core/src/main/java/bisq/core/trade/Trade.java rename to core/src/main/java/bisq/core/trade/model/bisq_v1/Trade.java index 447f8b52ea..95ac3a7849 100644 --- a/core/src/main/java/bisq/core/trade/Trade.java +++ b/core/src/main/java/bisq/core/trade/model/bisq_v1/Trade.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.trade; +package bisq.core.trade.model.bisq_v1; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.locale.CurrencyUtil; @@ -28,8 +28,13 @@ import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator; import bisq.core.support.dispute.mediation.MediationResultState; import bisq.core.support.dispute.refund.RefundResultState; import bisq.core.support.messages.ChatMessage; -import bisq.core.trade.protocol.ProcessModel; -import bisq.core.trade.protocol.ProcessModelServiceProvider; +import bisq.core.trade.model.TradeModel; +import bisq.core.trade.model.TradePhase; +import bisq.core.trade.model.TradeState; +import bisq.core.trade.protocol.ProtocolModel; +import bisq.core.trade.protocol.Provider; +import bisq.core.trade.protocol.bisq_v1.model.ProcessModel; +import bisq.core.trade.protocol.bisq_v1.model.TradingPeer; import bisq.core.trade.txproof.AssetTxProofResult; import bisq.core.util.VolumeUtil; @@ -37,7 +42,6 @@ import bisq.network.p2p.NodeAddress; import bisq.common.crypto.PubKeyRing; import bisq.common.proto.ProtoUtil; -import bisq.common.taskrunner.Model; import bisq.common.util.Utilities; import com.google.protobuf.ByteString; @@ -55,11 +59,8 @@ import com.google.common.util.concurrent.MoreExecutors; import javafx.beans.property.IntegerProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty; -import javafx.beans.property.ReadOnlyStringProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleObjectProperty; -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.property.StringProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -83,13 +84,13 @@ import static com.google.common.base.Preconditions.checkNotNull; * stored in the task model. */ @Slf4j -public abstract class Trade implements Tradable, Model { +public abstract class Trade extends TradeModel { /////////////////////////////////////////////////////////////////////////////////////////// // Enums /////////////////////////////////////////////////////////////////////////////////////////// - public enum State { + public enum State implements TradeState { // #################### Phase PREPARATION // When trade protocol starts no funds are on stake PREPARATION(Phase.INIT), @@ -160,19 +161,16 @@ public abstract class Trade implements Tradable, Model { // Alternatively the maker could have seen the payout tx earlier before he received the PAYOUT_TX_PUBLISHED_MSG BUYER_SAW_PAYOUT_TX_IN_NETWORK(Phase.PAYOUT_PUBLISHED), - // #################### Phase WITHDRAWN WITHDRAW_COMPLETED(Phase.WITHDRAWN); - @NotNull - public Phase getPhase() { + public Phase getTradePhase() { return phase; } - @NotNull private final Phase phase; - State(@NotNull Phase phase) { + State(Phase phase) { this.phase = phase; } @@ -188,13 +186,13 @@ public abstract class Trade implements Tradable, Model { // We allow a state change only if the phase is the next phase or if we do not change the phase by the // state change (e.g. detail change inside the same phase) public boolean isValidTransitionTo(State newState) { - Phase newPhase = newState.getPhase(); - Phase currentPhase = this.getPhase(); + Phase newPhase = newState.getTradePhase(); + Phase currentPhase = this.getTradePhase(); return currentPhase.isValidTransitionTo(newPhase) || newPhase.equals(currentPhase); } } - public enum Phase { + public enum Phase implements TradePhase { INIT, TAKER_FEE_PUBLISHED, DEPOSIT_PUBLISHED, @@ -208,13 +206,13 @@ public abstract class Trade implements Tradable, Model { return ProtoUtil.enumFromProto(Trade.Phase.class, phase.name()); } - public static protobuf.Trade.Phase toProtoMessage(Trade.Phase phase) { + public static protobuf.Trade.Phase toProtoMessage(Phase phase) { return protobuf.Trade.Phase.valueOf(phase.name()); } // We allow a phase change only if the phase a future phase (we cannot limit it to next phase as we have cases where // we skip a phase as it is only relevant to one role -> states and phases need a redesign ;-( ) - public boolean isValidTransitionTo(Phase newPhase) { + public boolean isValidTransitionTo(Trade.Phase newPhase) { // this is current phase return newPhase.ordinal() > this.ordinal(); } @@ -289,21 +287,12 @@ public abstract class Trade implements Tradable, Model { @Getter private final ProcessModel processModel; @Getter - private final Offer offer; - @Getter private final boolean isCurrencyForTakerFeeBtc; @Getter - private final long txFeeAsLong; + private final long tradeTxFeeAsLong; @Getter private final long takerFeeAsLong; - // Added in 1.5.1 - @Getter - private final String uid; - - @Setter - private long takeOfferDate; - // Mutable @Nullable @Getter @@ -317,15 +306,10 @@ public abstract class Trade implements Tradable, Model { @Getter @Setter private String payoutTxId; - @Getter @Setter - private long tradeAmountAsLong; + private long amountAsLong; @Setter - private long tradePrice; - @Nullable - @Getter - private NodeAddress tradingPeerNodeAddress; - @Getter + private long priceAsLong; private State state = State.PREPARATION; @Getter private DisputeState disputeState = DisputeState.NO_DISPUTE; @@ -374,8 +358,7 @@ public abstract class Trade implements Tradable, Model { @Getter @Setter private String takerPaymentAccountId; - @Nullable - private String errorMessage; + @Getter @Setter @Nullable @@ -386,8 +369,7 @@ public abstract class Trade implements Tradable, Model { // Transient // Immutable @Getter - transient final private Coin txFee; - @Getter + transient final protected Coin tradeTxFee; // is takers tx fee and the tx fee used for all the trade txs. transient final private Coin takerFee; @Getter transient final private BtcWalletService btcWalletService; @@ -396,7 +378,6 @@ public abstract class Trade implements Tradable, Model { transient final private ObjectProperty statePhaseProperty = new SimpleObjectProperty<>(state.phase); transient final private ObjectProperty disputeStateProperty = new SimpleObjectProperty<>(disputeState); transient final private ObjectProperty tradePeriodStateProperty = new SimpleObjectProperty<>(tradePeriodState); - transient final private StringProperty errorMessageProperty = new SimpleStringProperty(); // Mutable @Nullable @@ -411,10 +392,10 @@ public abstract class Trade implements Tradable, Model { @Nullable transient private Transaction payoutTx; @Nullable - transient private Coin tradeAmount; + transient private Coin amount; - transient private ObjectProperty tradeAmountProperty; - transient private ObjectProperty tradeVolumeProperty; + transient private ObjectProperty amountProperty; + transient private ObjectProperty volumeProperty; // Added in v1.1.6 @Getter @@ -468,7 +449,7 @@ public abstract class Trade implements Tradable, Model { // maker protected Trade(Offer offer, - Coin txFee, + Coin tradeTxFee, Coin takerFee, boolean isCurrencyForTakerFeeBtc, @Nullable NodeAddress arbitratorNodeAddress, @@ -477,8 +458,8 @@ public abstract class Trade implements Tradable, Model { BtcWalletService btcWalletService, ProcessModel processModel, String uid) { - this.offer = offer; - this.txFee = txFee; + super(uid, offer); + this.tradeTxFee = tradeTxFee; this.takerFee = takerFee; this.isCurrencyForTakerFeeBtc = isCurrencyForTakerFeeBtc; this.arbitratorNodeAddress = arbitratorNodeAddress; @@ -486,22 +467,20 @@ public abstract class Trade implements Tradable, Model { this.refundAgentNodeAddress = refundAgentNodeAddress; this.btcWalletService = btcWalletService; this.processModel = processModel; - this.uid = uid; - txFeeAsLong = txFee.value; + tradeTxFeeAsLong = tradeTxFee.value; takerFeeAsLong = takerFee.value; - takeOfferDate = new Date().getTime(); } // taker @SuppressWarnings("NullableProblems") protected Trade(Offer offer, - Coin tradeAmount, + Coin amount, Coin txFee, Coin takerFee, boolean isCurrencyForTakerFeeBtc, - long tradePrice, + long priceAsLong, NodeAddress tradingPeerNodeAddress, @Nullable NodeAddress arbitratorNodeAddress, @Nullable NodeAddress mediatorNodeAddress, @@ -520,10 +499,10 @@ public abstract class Trade implements Tradable, Model { btcWalletService, processModel, uid); - this.tradePrice = tradePrice; - this.tradingPeerNodeAddress = tradingPeerNodeAddress; + this.priceAsLong = priceAsLong; - setTradeAmount(tradeAmount); + setTradingPeerNodeAddress(tradingPeerNodeAddress); + setAmount(amount); } @@ -536,12 +515,12 @@ public abstract class Trade implements Tradable, Model { protobuf.Trade.Builder builder = protobuf.Trade.newBuilder() .setOffer(offer.toProtoMessage()) .setIsCurrencyForTakerFeeBtc(isCurrencyForTakerFeeBtc) - .setTxFeeAsLong(txFeeAsLong) + .setTxFeeAsLong(tradeTxFeeAsLong) .setTakerFeeAsLong(takerFeeAsLong) .setTakeOfferDate(takeOfferDate) .setProcessModel(processModel.toProtoMessage()) - .setTradeAmountAsLong(tradeAmountAsLong) - .setTradePrice(tradePrice) + .setTradeAmountAsLong(amountAsLong) + .setTradePrice(priceAsLong) .setState(Trade.State.toProtoMessage(state)) .setDisputeState(Trade.DisputeState.toProtoMessage(disputeState)) .setTradePeriodState(Trade.TradePeriodState.toProtoMessage(tradePeriodState)) @@ -627,7 +606,7 @@ public abstract class Trade implements Tradable, Model { // API /////////////////////////////////////////////////////////////////////////////////////////// - public void initialize(ProcessModelServiceProvider serviceProvider) { + public void initialize(Provider serviceProvider) { serviceProvider.getArbitratorManager().getDisputeAgentByNodeAddress(arbitratorNodeAddress).ifPresent(arbitrator -> { arbitratorBtcPubKey = arbitrator.getBtcPubKey(); arbitratorPubKeyRing = arbitrator.getPubKeyRing(); @@ -648,7 +627,7 @@ public abstract class Trade implements Tradable, Model { /////////////////////////////////////////////////////////////////////////////////////////// // The deserialized tx has not actual confidence data, so we need to get the fresh one from the wallet. - void updateDepositTxFromWallet() { + public void updateDepositTxFromWallet() { if (getDepositTx() != null) applyDepositTx(processModel.getTradeWalletService().getWalletTx(getDepositTx().getTxId())); } @@ -721,13 +700,84 @@ public abstract class Trade implements Tradable, Model { /////////////////////////////////////////////////////////////////////////////////////////// - // Model implementation + // TradeModel implementation /////////////////////////////////////////////////////////////////////////////////////////// @Override public void onComplete() { } + @Override + public State getTradeState() { + return state; + } + + @Override + public Phase getTradePhase() { + return state.getTradePhase(); + } + + @Override + public ProtocolModel getTradeProtocolModel() { + return processModel; + } + + @Override + public boolean isCompleted() { + return isWithdrawn(); + } + + @Override + public long getAmountAsLong() { + return amountAsLong; + } + + @Override + public Coin getAmount() { + if (amount == null) + amount = Coin.valueOf(amountAsLong); + return amount; + } + + @Nullable + @Override + public Volume getVolume() { + try { + if (getAmount() != null && getPrice() != null) { + Volume volumeByAmount = getPrice().getVolumeByAmount(getAmount()); + if (offer != null) { + if (offer.getPaymentMethod().getId().equals(PaymentMethod.HAL_CASH_ID)) + volumeByAmount = VolumeUtil.getAdjustedVolumeForHalCash(volumeByAmount); + else if (CurrencyUtil.isFiatCurrency(offer.getCurrencyCode())) + volumeByAmount = VolumeUtil.getRoundedFiatVolume(volumeByAmount); + } + return volumeByAmount; + } else { + return null; + } + } catch (Throwable ignore) { + return null; + } + } + + @Override + public Price getPrice() { + return Price.valueOf(offer.getCurrencyCode(), priceAsLong); + } + + // getTxFee() is implemented in concrete classes + // Maker use fee from offer, taker use this.txFee + + @Override + public Coin getTakerFee() { + return takerFee; + } + + @Override + public Coin getMakerFee() { + return offer.getMakerFee(); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Abstract @@ -755,7 +805,7 @@ public abstract class Trade implements Tradable, Model { // We don't want to log at startup the setState calls from all persisted trades log.info("Set new state at {} (id={}): {}", this.getClass().getSimpleName(), getShortId(), state); } - if (state.getPhase().ordinal() < this.state.getPhase().ordinal()) { + if (state.getTradePhase().ordinal() < this.state.getTradePhase().ordinal()) { String message = "We got a state change to a previous phase.\n" + "Old state is: " + this.state + ". New state is: " + state; log.warn(message); @@ -763,7 +813,7 @@ public abstract class Trade implements Tradable, Model { this.state = state; stateProperty.set(state); - statePhaseProperty.set(state.getPhase()); + statePhaseProperty.set(state.getTradePhase()); } public void setDisputeState(DisputeState disputeState) { @@ -786,18 +836,11 @@ public abstract class Trade implements Tradable, Model { tradePeriodStateProperty.set(tradePeriodState); } - public void setTradingPeerNodeAddress(NodeAddress tradingPeerNodeAddress) { - if (tradingPeerNodeAddress == null) - log.error("tradingPeerAddress=null"); - else - this.tradingPeerNodeAddress = tradingPeerNodeAddress; - } - - public void setTradeAmount(Coin tradeAmount) { - this.tradeAmount = tradeAmount; - tradeAmountAsLong = tradeAmount.value; - getTradeAmountProperty().set(tradeAmount); - getTradeVolumeProperty().set(getTradeVolume()); + public void setAmount(Coin amount) { + this.amount = amount; + amountAsLong = amount.value; + getAmountProperty().set(amount); + getVolumeProperty().set(getVolume()); } public void setPayoutTx(Transaction payoutTx) { @@ -805,11 +848,6 @@ public abstract class Trade implements Tradable, Model { payoutTxId = payoutTx.getTxId().toString(); } - public void setErrorMessage(String errorMessage) { - this.errorMessage = errorMessage; - errorMessageProperty.set(errorMessage); - } - public void setAssetTxProofResult(@Nullable AssetTxProofResult assetTxProofResult) { this.assetTxProofResult = assetTxProofResult; assetTxProofResultUpdateProperty.set(assetTxProofResultUpdateProperty.get() + 1); @@ -820,33 +858,6 @@ public abstract class Trade implements Tradable, Model { // Getter /////////////////////////////////////////////////////////////////////////////////////////// - public Date getTakeOfferDate() { - return new Date(takeOfferDate); - } - - public Phase getPhase() { - return state.getPhase(); - } - - @Nullable - public Volume getTradeVolume() { - try { - if (getTradeAmount() != null && getTradePrice() != null) { - Volume volumeByAmount = getTradePrice().getVolumeByAmount(getTradeAmount()); - if (offer != null) { - if (offer.getPaymentMethod().getId().equals(PaymentMethod.HAL_CASH_ID)) - volumeByAmount = VolumeUtil.getAdjustedVolumeForHalCash(volumeByAmount); - else if (CurrencyUtil.isFiatCurrency(offer.getCurrencyCode())) - volumeByAmount = VolumeUtil.getRoundedFiatVolume(volumeByAmount); - } - return volumeByAmount; - } else { - return null; - } - } catch (Throwable ignore) { - return null; - } - } public Date getHalfTradePeriodDate() { return new Date(getTradeStartTime() + getMaxTradePeriod() / 2); @@ -857,16 +868,16 @@ public abstract class Trade implements Tradable, Model { } private long getMaxTradePeriod() { - return getOffer().getPaymentMethod().getMaxTradePeriod(); + return offer.getPaymentMethod().getMaxTradePeriod(); } private long getTradeStartTime() { long now = System.currentTimeMillis(); long startTime; Transaction depositTx = getDepositTx(); - if (depositTx != null && getTakeOfferDate() != null) { + if (depositTx != null && getDate() != null) { if (depositTx.getConfidence().getDepthInBlocks() > 0) { - final long tradeTime = getTakeOfferDate().getTime(); + final long tradeTime = getDate().getTime(); // Use tx.getIncludedInBestChainAt() when available, otherwise use tx.getUpdateTime() long blockTime = depositTx.getIncludedInBestChainAt() != null ? depositTx.getIncludedInBestChainAt().getTime() @@ -896,15 +907,15 @@ public abstract class Trade implements Tradable, Model { } public boolean isInPreparation() { - return getState().getPhase().ordinal() == Phase.INIT.ordinal(); + return getTradePhase().ordinal() == Phase.INIT.ordinal(); } public boolean isTakerFeePublished() { - return getState().getPhase().ordinal() >= Phase.TAKER_FEE_PUBLISHED.ordinal(); + return getTradePhase().ordinal() >= Phase.TAKER_FEE_PUBLISHED.ordinal(); } public boolean isDepositPublished() { - return getState().getPhase().ordinal() >= Phase.DEPOSIT_PUBLISHED.ordinal(); + return getTradePhase().ordinal() >= Phase.DEPOSIT_PUBLISHED.ordinal(); } public boolean isFundsLockedIn() { @@ -938,23 +949,23 @@ public abstract class Trade implements Tradable, Model { } public boolean isDepositConfirmed() { - return getState().getPhase().ordinal() >= Phase.DEPOSIT_CONFIRMED.ordinal(); + return getTradePhase().ordinal() >= Phase.DEPOSIT_CONFIRMED.ordinal(); } public boolean isFiatSent() { - return getState().getPhase().ordinal() >= Phase.FIAT_SENT.ordinal(); + return getTradePhase().ordinal() >= Phase.FIAT_SENT.ordinal(); } public boolean isFiatReceived() { - return getState().getPhase().ordinal() >= Phase.FIAT_RECEIVED.ordinal(); + return getTradePhase().ordinal() >= Phase.FIAT_RECEIVED.ordinal(); } public boolean isPayoutPublished() { - return getState().getPhase().ordinal() >= Phase.PAYOUT_PUBLISHED.ordinal() || isWithdrawn(); + return getTradePhase().ordinal() >= Phase.PAYOUT_PUBLISHED.ordinal() || isWithdrawn(); } public boolean isWithdrawn() { - return getState().getPhase().ordinal() == Phase.WITHDRAWN.ordinal(); + return getTradePhase().ordinal() == Phase.WITHDRAWN.ordinal(); } public ReadOnlyObjectProperty stateProperty() { @@ -981,43 +992,14 @@ public abstract class Trade implements Tradable, Model { return tradePeriodStateProperty; } - public ReadOnlyObjectProperty tradeAmountProperty() { - return tradeAmountProperty; + public ReadOnlyObjectProperty amountProperty() { + return amountProperty; } - public ReadOnlyObjectProperty tradeVolumeProperty() { - return tradeVolumeProperty; + public ReadOnlyObjectProperty volumeProperty() { + return volumeProperty; } - public ReadOnlyStringProperty errorMessageProperty() { - return errorMessageProperty; - } - - @Override - public Date getDate() { - return getTakeOfferDate(); - } - - @Override - public String getId() { - return offer.getId(); - } - - @Override - public String getShortId() { - return offer.getShortId(); - } - - public Price getTradePrice() { - return Price.valueOf(offer.getCurrencyCode(), tradePrice); - } - - @Nullable - public Coin getTradeAmount() { - if (tradeAmount == null) - tradeAmount = Coin.valueOf(tradeAmountAsLong); - return tradeAmount; - } @Nullable public Transaction getPayoutTx() { @@ -1030,11 +1012,6 @@ public abstract class Trade implements Tradable, Model { return getErrorMessage() != null && !getErrorMessage().isEmpty(); } - @Nullable - public String getErrorMessage() { - return errorMessageProperty.get(); - } - public boolean isTxChainInvalid() { return offer.getOfferFeePaymentTxId() == null || getTakerFeeTxId() == null || @@ -1056,24 +1033,28 @@ public abstract class Trade implements Tradable, Model { return arbitratorBtcPubKey; } + public boolean isBsqSwap() { + return offer != null && offer.isBsqSwapOffer(); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Private /////////////////////////////////////////////////////////////////////////////////////////// // lazy initialization - private ObjectProperty getTradeAmountProperty() { - if (tradeAmountProperty == null) - tradeAmountProperty = getTradeAmount() != null ? new SimpleObjectProperty<>(getTradeAmount()) : new SimpleObjectProperty<>(); + private ObjectProperty getAmountProperty() { + if (amountProperty == null) + amountProperty = getAmount() != null ? new SimpleObjectProperty<>(getAmount()) : new SimpleObjectProperty<>(); - return tradeAmountProperty; + return amountProperty; } // lazy initialization - private ObjectProperty getTradeVolumeProperty() { - if (tradeVolumeProperty == null) - tradeVolumeProperty = getTradeVolume() != null ? new SimpleObjectProperty<>(getTradeVolume()) : new SimpleObjectProperty<>(); - return tradeVolumeProperty; + private ObjectProperty getVolumeProperty() { + if (volumeProperty == null) + volumeProperty = getVolume() != null ? new SimpleObjectProperty<>(getVolume()) : new SimpleObjectProperty<>(); + return volumeProperty; } private void setupConfidenceListener() { @@ -1117,15 +1098,15 @@ public abstract class Trade implements Tradable, Model { return "Trade{" + "\n offer=" + offer + ",\n isCurrencyForTakerFeeBtc=" + isCurrencyForTakerFeeBtc + - ",\n txFeeAsLong=" + txFeeAsLong + + ",\n tradeTxFeeAsLong=" + tradeTxFeeAsLong + ",\n takerFeeAsLong=" + takerFeeAsLong + ",\n takeOfferDate=" + takeOfferDate + ",\n processModel=" + processModel + ",\n takerFeeTxId='" + takerFeeTxId + '\'' + ",\n depositTxId='" + depositTxId + '\'' + ",\n payoutTxId='" + payoutTxId + '\'' + - ",\n tradeAmountAsLong=" + tradeAmountAsLong + - ",\n tradePrice=" + tradePrice + + ",\n tradeAmountAsLong=" + amountAsLong + + ",\n tradePrice=" + priceAsLong + ",\n tradingPeerNodeAddress=" + tradingPeerNodeAddress + ",\n state=" + state + ",\n disputeState=" + disputeState + @@ -1146,20 +1127,19 @@ public abstract class Trade implements Tradable, Model { ",\n counterCurrencyExtraData='" + counterCurrencyExtraData + '\'' + ",\n assetTxProofResult='" + assetTxProofResult + '\'' + ",\n chatMessages=" + chatMessages + - ",\n txFee=" + txFee + + ",\n tradeTxFee=" + tradeTxFee + ",\n takerFee=" + takerFee + ",\n btcWalletService=" + btcWalletService + ",\n stateProperty=" + stateProperty + ",\n statePhaseProperty=" + statePhaseProperty + ",\n disputeStateProperty=" + disputeStateProperty + ",\n tradePeriodStateProperty=" + tradePeriodStateProperty + - ",\n errorMessageProperty=" + errorMessageProperty + ",\n depositTx=" + depositTx + ",\n delayedPayoutTx=" + delayedPayoutTx + ",\n payoutTx=" + payoutTx + - ",\n tradeAmount=" + tradeAmount + - ",\n tradeAmountProperty=" + tradeAmountProperty + - ",\n tradeVolumeProperty=" + tradeVolumeProperty + + ",\n tradeAmount=" + amount + + ",\n tradeAmountProperty=" + amountProperty + + ",\n tradeVolumeProperty=" + volumeProperty + ",\n mediationResultState=" + mediationResultState + ",\n mediationResultStateProperty=" + mediationResultStateProperty + ",\n lockTime=" + lockTime + diff --git a/core/src/main/java/bisq/core/trade/model/bsq_swap/BsqSwapBuyerAsMakerTrade.java b/core/src/main/java/bisq/core/trade/model/bsq_swap/BsqSwapBuyerAsMakerTrade.java new file mode 100644 index 0000000000..7fbf26eb47 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/model/bsq_swap/BsqSwapBuyerAsMakerTrade.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.core.trade.model.bsq_swap; + +import bisq.core.offer.Offer; +import bisq.core.trade.model.TakerTrade; +import bisq.core.trade.model.Tradable; +import bisq.core.trade.protocol.bsq_swap.model.BsqSwapProtocolModel; + +import bisq.network.p2p.NodeAddress; + +import bisq.common.proto.ProtoUtil; + +import org.bitcoinj.core.Coin; + +import java.util.UUID; + +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.Nullable; + +@Slf4j +public final class BsqSwapBuyerAsMakerTrade extends BsqSwapBuyerTrade implements TakerTrade { + public BsqSwapBuyerAsMakerTrade(Offer offer, + Coin amount, + long takeOfferDate, + NodeAddress peerNodeAddress, + long txFeePerVbyte, + long makerFee, + long takerFee, + BsqSwapProtocolModel bsqSwapProtocolModel) { + + + super(UUID.randomUUID().toString(), + offer, + amount, + takeOfferDate, + peerNodeAddress, + txFeePerVbyte, + makerFee, + takerFee, + bsqSwapProtocolModel, + null, + BsqSwapTrade.State.PREPARATION, + null); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + private BsqSwapBuyerAsMakerTrade(String uid, + Offer offer, + Coin amount, + long takeOfferDate, + NodeAddress peerNodeAddress, + long txFeePerVbyte, + long makerFee, + long takerFee, + BsqSwapProtocolModel bsqSwapProtocolModel, + @Nullable String errorMessage, + State state, + @Nullable String txId) { + super(uid, + offer, + amount, + takeOfferDate, + peerNodeAddress, + txFeePerVbyte, + makerFee, + takerFee, + bsqSwapProtocolModel, + errorMessage, + state, + txId); + } + + @Override + public protobuf.Tradable toProtoMessage() { + return protobuf.Tradable.newBuilder() + .setBsqSwapBuyerAsMakerTrade(protobuf.BsqSwapBuyerAsMakerTrade.newBuilder() + .setBsqSwapTrade((protobuf.BsqSwapTrade) super.toProtoMessage())) + .build(); + } + + public static Tradable fromProto(protobuf.BsqSwapBuyerAsMakerTrade bsqSwapTrade) { + var proto = bsqSwapTrade.getBsqSwapTrade(); + var uid = ProtoUtil.stringOrNullFromProto(proto.getUid()); + if (uid == null) { + uid = UUID.randomUUID().toString(); + } + return new BsqSwapBuyerAsMakerTrade( + uid, + Offer.fromProto(proto.getOffer()), + Coin.valueOf(proto.getAmount()), + proto.getTakeOfferDate(), + proto.hasPeerNodeAddress() ? NodeAddress.fromProto(proto.getPeerNodeAddress()) : null, + proto.getMiningFeePerByte(), + proto.getMakerFee(), + proto.getTakerFee(), + BsqSwapProtocolModel.fromProto(proto.getBsqSwapProtocolModel()), + ProtoUtil.stringOrNullFromProto(proto.getErrorMessage()), + State.fromProto(proto.getState()), + ProtoUtil.stringOrNullFromProto(proto.getTxId())); + } +} diff --git a/core/src/main/java/bisq/core/trade/model/bsq_swap/BsqSwapBuyerAsTakerTrade.java b/core/src/main/java/bisq/core/trade/model/bsq_swap/BsqSwapBuyerAsTakerTrade.java new file mode 100644 index 0000000000..9c374bd34c --- /dev/null +++ b/core/src/main/java/bisq/core/trade/model/bsq_swap/BsqSwapBuyerAsTakerTrade.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.core.trade.model.bsq_swap; + +import bisq.core.offer.Offer; +import bisq.core.trade.model.TakerTrade; +import bisq.core.trade.model.Tradable; +import bisq.core.trade.protocol.bsq_swap.model.BsqSwapProtocolModel; + +import bisq.network.p2p.NodeAddress; + +import bisq.common.proto.ProtoUtil; + +import org.bitcoinj.core.Coin; + +import java.util.Date; +import java.util.UUID; + +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.Nullable; + +@Slf4j +public final class BsqSwapBuyerAsTakerTrade extends BsqSwapBuyerTrade implements TakerTrade { + public BsqSwapBuyerAsTakerTrade(Offer offer, + Coin amount, + NodeAddress peerNodeAddress, + long txFeePerVbyte, + long makerFee, + long takerFee, + BsqSwapProtocolModel bsqSwapProtocolModel) { + + + super(UUID.randomUUID().toString(), + offer, + amount, + new Date().getTime(), + peerNodeAddress, + txFeePerVbyte, + makerFee, + takerFee, + bsqSwapProtocolModel, + null, + BsqSwapTrade.State.PREPARATION, + null); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + private BsqSwapBuyerAsTakerTrade(String uid, + Offer offer, + Coin amount, + long takeOfferDate, + NodeAddress peerNodeAddress, + long txFeePerVbyte, + long makerFee, + long takerFee, + BsqSwapProtocolModel bsqSwapProtocolModel, + @Nullable String errorMessage, + State state, + @Nullable String txId) { + super(uid, + offer, + amount, + takeOfferDate, + peerNodeAddress, + txFeePerVbyte, + makerFee, + takerFee, + bsqSwapProtocolModel, + errorMessage, + state, + txId); + } + + @Override + public protobuf.Tradable toProtoMessage() { + return protobuf.Tradable.newBuilder() + .setBsqSwapBuyerAsTakerTrade(protobuf.BsqSwapBuyerAsTakerTrade.newBuilder() + .setBsqSwapTrade((protobuf.BsqSwapTrade) super.toProtoMessage())) + .build(); + } + + public static Tradable fromProto(protobuf.BsqSwapBuyerAsTakerTrade bsqSwapTrade) { + var proto = bsqSwapTrade.getBsqSwapTrade(); + var uid = ProtoUtil.stringOrNullFromProto(proto.getUid()); + if (uid == null) { + uid = UUID.randomUUID().toString(); + } + return new BsqSwapBuyerAsTakerTrade( + uid, + Offer.fromProto(proto.getOffer()), + Coin.valueOf(proto.getAmount()), + proto.getTakeOfferDate(), + proto.hasPeerNodeAddress() ? NodeAddress.fromProto(proto.getPeerNodeAddress()) : null, + proto.getMiningFeePerByte(), + proto.getMakerFee(), + proto.getTakerFee(), + BsqSwapProtocolModel.fromProto(proto.getBsqSwapProtocolModel()), + ProtoUtil.stringOrNullFromProto(proto.getErrorMessage()), + State.fromProto(proto.getState()), + ProtoUtil.stringOrNullFromProto(proto.getTxId())); + } +} diff --git a/core/src/main/java/bisq/core/trade/model/bsq_swap/BsqSwapBuyerTrade.java b/core/src/main/java/bisq/core/trade/model/bsq_swap/BsqSwapBuyerTrade.java new file mode 100644 index 0000000000..d71aace432 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/model/bsq_swap/BsqSwapBuyerTrade.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.core.trade.model.bsq_swap; + +import bisq.core.offer.Offer; +import bisq.core.trade.protocol.bsq_swap.model.BsqSwapProtocolModel; + +import bisq.network.p2p.NodeAddress; + +import org.bitcoinj.core.Coin; + +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.Nullable; + +@Slf4j +public abstract class BsqSwapBuyerTrade extends BsqSwapTrade { + + public BsqSwapBuyerTrade(String uid, + Offer offer, + Coin amount, + long takeOfferDate, + NodeAddress peerNodeAddress, + long txFeePerVbyte, + long makerFee, + long takerFee, + BsqSwapProtocolModel bsqSwapProtocolModel, + @Nullable String errorMessage, + State state, + @Nullable String txId) { + super(uid, + offer, + amount, + takeOfferDate, + peerNodeAddress, + txFeePerVbyte, + makerFee, + takerFee, + bsqSwapProtocolModel, + errorMessage, + state, + txId); + } +} diff --git a/core/src/main/java/bisq/core/trade/model/bsq_swap/BsqSwapSellerAsMakerTrade.java b/core/src/main/java/bisq/core/trade/model/bsq_swap/BsqSwapSellerAsMakerTrade.java new file mode 100644 index 0000000000..4148978ffe --- /dev/null +++ b/core/src/main/java/bisq/core/trade/model/bsq_swap/BsqSwapSellerAsMakerTrade.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.core.trade.model.bsq_swap; + +import bisq.core.offer.Offer; +import bisq.core.trade.model.MakerTrade; +import bisq.core.trade.model.Tradable; +import bisq.core.trade.protocol.bsq_swap.model.BsqSwapProtocolModel; + +import bisq.network.p2p.NodeAddress; + +import bisq.common.proto.ProtoUtil; + +import org.bitcoinj.core.Coin; + +import java.util.UUID; + +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.Nullable; + + +@Slf4j +public final class BsqSwapSellerAsMakerTrade extends BsqSwapSellerTrade implements MakerTrade { + public BsqSwapSellerAsMakerTrade(Offer offer, + Coin amount, + long takeOfferDate, + NodeAddress peerNodeAddress, + long txFeePerVbyte, + long makerFee, + long takerFee, + BsqSwapProtocolModel bsqSwapProtocolModel) { + + super(UUID.randomUUID().toString(), + offer, + amount, + takeOfferDate, + peerNodeAddress, + txFeePerVbyte, + makerFee, + takerFee, + bsqSwapProtocolModel, + null, + BsqSwapTrade.State.PREPARATION, + null); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + private BsqSwapSellerAsMakerTrade(String uid, + Offer offer, + Coin amount, + long takeOfferDate, + NodeAddress peerNodeAddress, + long txFeePerVbyte, + long makerFee, + long takerFee, + BsqSwapProtocolModel bsqSwapProtocolModel, + @Nullable String errorMessage, + State state, + @Nullable String txId) { + super(uid, + offer, + amount, + takeOfferDate, + peerNodeAddress, + txFeePerVbyte, + makerFee, + takerFee, + bsqSwapProtocolModel, + errorMessage, + state, + txId); + } + + @Override + public protobuf.Tradable toProtoMessage() { + return protobuf.Tradable.newBuilder() + .setBsqSwapSellerAsMakerTrade(protobuf.BsqSwapSellerAsMakerTrade.newBuilder() + .setBsqSwapTrade((protobuf.BsqSwapTrade) super.toProtoMessage())) + .build(); + } + + public static Tradable fromProto(protobuf.BsqSwapSellerAsMakerTrade bsqSwapSellerAsMakerTrade) { + var proto = bsqSwapSellerAsMakerTrade.getBsqSwapTrade(); + var uid = ProtoUtil.stringOrNullFromProto(proto.getUid()); + if (uid == null) { + uid = UUID.randomUUID().toString(); + } + return new BsqSwapSellerAsMakerTrade( + uid, + Offer.fromProto(proto.getOffer()), + Coin.valueOf(proto.getAmount()), + proto.getTakeOfferDate(), + proto.hasPeerNodeAddress() ? NodeAddress.fromProto(proto.getPeerNodeAddress()) : null, + proto.getMiningFeePerByte(), + proto.getMakerFee(), + proto.getTakerFee(), + BsqSwapProtocolModel.fromProto(proto.getBsqSwapProtocolModel()), + ProtoUtil.stringOrNullFromProto(proto.getErrorMessage()), + State.fromProto(proto.getState()), + ProtoUtil.stringOrNullFromProto(proto.getTxId())); + } +} diff --git a/core/src/main/java/bisq/core/trade/model/bsq_swap/BsqSwapSellerAsTakerTrade.java b/core/src/main/java/bisq/core/trade/model/bsq_swap/BsqSwapSellerAsTakerTrade.java new file mode 100644 index 0000000000..665f7d76b3 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/model/bsq_swap/BsqSwapSellerAsTakerTrade.java @@ -0,0 +1,123 @@ +/* + * 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.core.trade.model.bsq_swap; + +import bisq.core.offer.Offer; +import bisq.core.trade.model.TakerTrade; +import bisq.core.trade.model.Tradable; +import bisq.core.trade.protocol.bsq_swap.model.BsqSwapProtocolModel; + +import bisq.network.p2p.NodeAddress; + +import bisq.common.proto.ProtoUtil; + +import org.bitcoinj.core.Coin; + +import java.util.Date; +import java.util.UUID; + +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.Nullable; + + +@Slf4j +public final class BsqSwapSellerAsTakerTrade extends BsqSwapSellerTrade implements TakerTrade { + public BsqSwapSellerAsTakerTrade(Offer offer, + Coin amount, + NodeAddress peerNodeAddress, + long txFeePerVbyte, + long makerFee, + long takerFee, + BsqSwapProtocolModel bsqSwapProtocolModel) { + + + super(UUID.randomUUID().toString(), + offer, + amount, + new Date().getTime(), + peerNodeAddress, + txFeePerVbyte, + makerFee, + takerFee, + bsqSwapProtocolModel, + null, + BsqSwapTrade.State.PREPARATION, + null); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + private BsqSwapSellerAsTakerTrade(String uid, + Offer offer, + Coin amount, + long takeOfferDate, + NodeAddress peerNodeAddress, + long txFeePerVbyte, + long makerFee, + long takerFee, + BsqSwapProtocolModel bsqSwapProtocolModel, + @Nullable String errorMessage, + State state, + @Nullable String txId) { + super(uid, + offer, + amount, + takeOfferDate, + peerNodeAddress, + txFeePerVbyte, + makerFee, + takerFee, + bsqSwapProtocolModel, + errorMessage, + state, + txId); + } + + @Override + public protobuf.Tradable toProtoMessage() { + return protobuf.Tradable.newBuilder() + .setBsqSwapSellerAsTakerTrade(protobuf.BsqSwapSellerAsTakerTrade.newBuilder() + .setBsqSwapTrade((protobuf.BsqSwapTrade) super.toProtoMessage())) + .build(); + } + + public static Tradable fromProto(protobuf.BsqSwapSellerAsTakerTrade bsqSwapSellerAsTakerTrade) { + var proto = bsqSwapSellerAsTakerTrade.getBsqSwapTrade(); + var uid = ProtoUtil.stringOrNullFromProto(proto.getUid()); + if (uid == null) { + uid = UUID.randomUUID().toString(); + } + return new BsqSwapSellerAsTakerTrade( + uid, + Offer.fromProto(proto.getOffer()), + Coin.valueOf(proto.getAmount()), + proto.getTakeOfferDate(), + proto.hasPeerNodeAddress() ? NodeAddress.fromProto(proto.getPeerNodeAddress()) : null, + proto.getMiningFeePerByte(), + proto.getMakerFee(), + proto.getTakerFee(), + BsqSwapProtocolModel.fromProto(proto.getBsqSwapProtocolModel()), + ProtoUtil.stringOrNullFromProto(proto.getErrorMessage()), + State.fromProto(proto.getState()), + ProtoUtil.stringOrNullFromProto(proto.getTxId())); + } +} diff --git a/core/src/main/java/bisq/core/trade/model/bsq_swap/BsqSwapSellerTrade.java b/core/src/main/java/bisq/core/trade/model/bsq_swap/BsqSwapSellerTrade.java new file mode 100644 index 0000000000..6f62bfc669 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/model/bsq_swap/BsqSwapSellerTrade.java @@ -0,0 +1,56 @@ +/* + * 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.core.trade.model.bsq_swap; + +import bisq.core.offer.Offer; +import bisq.core.trade.protocol.bsq_swap.model.BsqSwapProtocolModel; + +import bisq.network.p2p.NodeAddress; + +import org.bitcoinj.core.Coin; + +import javax.annotation.Nullable; + +public abstract class BsqSwapSellerTrade extends BsqSwapTrade { + + public BsqSwapSellerTrade(String uid, + Offer offer, + Coin amount, + long takeOfferDate, + NodeAddress peerNodeAddress, + long txFeePerVbyte, + long makerFee, + long takerFee, + BsqSwapProtocolModel bsqSwapProtocolModel, + @Nullable String errorMessage, + State state, + @Nullable String txId) { + super(uid, + offer, + amount, + takeOfferDate, + peerNodeAddress, + txFeePerVbyte, + makerFee, + takerFee, + bsqSwapProtocolModel, + errorMessage, + state, + txId); + } +} diff --git a/core/src/main/java/bisq/core/trade/model/bsq_swap/BsqSwapTrade.java b/core/src/main/java/bisq/core/trade/model/bsq_swap/BsqSwapTrade.java new file mode 100644 index 0000000000..01766ccb6b --- /dev/null +++ b/core/src/main/java/bisq/core/trade/model/bsq_swap/BsqSwapTrade.java @@ -0,0 +1,282 @@ +/* + * 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.core.trade.model.bsq_swap; + +import bisq.core.btc.wallet.BsqWalletService; +import bisq.core.monetary.Price; +import bisq.core.monetary.Volume; +import bisq.core.offer.Offer; +import bisq.core.trade.bsq_swap.BsqSwapCalculation; +import bisq.core.trade.model.TradeModel; +import bisq.core.trade.model.TradePhase; +import bisq.core.trade.model.TradeState; +import bisq.core.trade.protocol.ProtocolModel; +import bisq.core.trade.protocol.bsq_swap.model.BsqSwapProtocolModel; +import bisq.core.trade.protocol.bsq_swap.model.BsqSwapTradePeer; + +import bisq.network.p2p.NodeAddress; + +import bisq.common.proto.ProtoUtil; + +import com.google.protobuf.Message; + +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.Transaction; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.SimpleObjectProperty; + +import java.util.Optional; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.Nullable; + +@Slf4j +public abstract class BsqSwapTrade extends TradeModel { + + /////////////////////////////////////////////////////////////////////////////////////////// + // Enums + /////////////////////////////////////////////////////////////////////////////////////////// + + public enum State implements TradeState { + PREPARATION, + COMPLETED, + FAILED; + + public static State fromProto(protobuf.BsqSwapTrade.State state) { + return ProtoUtil.enumFromProto(State.class, state.name()); + } + + public static protobuf.BsqSwapTrade.State toProtoMessage(State state) { + return protobuf.BsqSwapTrade.State.valueOf(state.name()); + } + } + + + private final long amountAsLong; + @Getter + private final long txFeePerVbyte; + private final long makerFeeAsLong; + @Getter + private final long takerFeeAsLong; + @Getter + private final BsqSwapProtocolModel bsqSwapProtocolModel; + + @Getter + private State state; + + @Getter + @Nullable + private String txId; + + @Nullable + transient private Volume volume; + @Nullable + transient private Transaction transaction; + transient final private ObjectProperty stateProperty = new SimpleObjectProperty<>(state); + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, initialization + /////////////////////////////////////////////////////////////////////////////////////////// + + protected BsqSwapTrade(String uid, + Offer offer, + Coin amount, + long takeOfferDate, + NodeAddress tradingPeerNodeAddress, + long txFeePerVbyte, + long makerFeeAsLong, + long takerFeeAsLong, + BsqSwapProtocolModel bsqSwapProtocolModel, + @Nullable String errorMessage, + State state, + @Nullable String txId) { + super(uid, offer, takeOfferDate, tradingPeerNodeAddress, errorMessage); + this.amountAsLong = amount.value; + this.txFeePerVbyte = txFeePerVbyte; + this.makerFeeAsLong = makerFeeAsLong; + this.takerFeeAsLong = takerFeeAsLong; + this.bsqSwapProtocolModel = bsqSwapProtocolModel; + this.state = state; + this.txId = txId; + + stateProperty.set(state); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public Message toProtoMessage() { + protobuf.BsqSwapTrade.Builder builder = protobuf.BsqSwapTrade.newBuilder() + .setUid(uid) + .setOffer(offer.toProtoMessage()) + .setAmount(amountAsLong) + .setTakeOfferDate(takeOfferDate) + .setMiningFeePerByte(txFeePerVbyte) + .setMakerFee(makerFeeAsLong) + .setTakerFee(takerFeeAsLong) + .setBsqSwapProtocolModel(bsqSwapProtocolModel.toProtoMessage()) + .setState(State.toProtoMessage(state)) + .setPeerNodeAddress(tradingPeerNodeAddress.toProtoMessage()); + Optional.ofNullable(errorMessage).ifPresent(builder::setErrorMessage); + Optional.ofNullable(txId).ifPresent(builder::setTxId); + return builder.build(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Model implementation + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void onComplete() { + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // TradeModel implementation + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public ProtocolModel getTradeProtocolModel() { + return bsqSwapProtocolModel; + } + + @Override + public boolean isCompleted() { + return state == State.COMPLETED; + } + + @Override + public BsqSwapTrade.State getTradeState() { + return state; + } + + @Override + public TradePhase getTradePhase() { + return state.getTradePhase(); + } + + @Override + public long getAmountAsLong() { + return amountAsLong; + } + + @Override + public Coin getAmount() { + return Coin.valueOf(amountAsLong); + } + + @Override + @Nullable + public Volume getVolume() { + if (volume == null) { + try { + volume = getPrice().getVolumeByAmount(Coin.valueOf(amountAsLong)); + } catch (Throwable e) { + log.error(e.toString()); + return null; + } + } + return volume; + } + + @Override + public Price getPrice() { + return Price.valueOf(offer.getCurrencyCode(), offer.getFixedPrice()); + } + + @Override + public Coin getTxFee() { + return Coin.valueOf(bsqSwapProtocolModel.getTxFee()); + } + + public long getMakerFeeAsLong() { + return makerFeeAsLong; + } + + @Override + public Coin getTakerFee() { + return Coin.valueOf(takerFeeAsLong); + } + + @Override + public Coin getMakerFee() { + return Coin.valueOf(makerFeeAsLong); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Setters + /////////////////////////////////////////////////////////////////////////////////////////// + + public void setState(State state) { + if (state.ordinal() < this.state.ordinal()) { + String message = "Unexpected state change to a previous state.\n" + + "Old state is: " + this.state + ". New state is: " + state; + log.warn(message); + } + + this.state = state; + stateProperty.set(state); + } + + public void applyTransaction(Transaction transaction) { + this.transaction = transaction; + txId = transaction.getTxId().toString(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Getters + /////////////////////////////////////////////////////////////////////////////////////////// + + public ReadOnlyObjectProperty stateProperty() { + return stateProperty; + } + + public boolean hasFailed() { + return errorMessageProperty().get() != null; + } + + public long getBsqTradeAmount() { + Volume volume = getVolume(); + if (volume == null) { + return 0L; + } + return BsqSwapCalculation.getBsqTradeAmount(volume).getValue(); + } + + @Nullable + public Transaction getTransaction(BsqWalletService bsqWalletService) { + if (txId == null) { + return null; + } + if (transaction == null) { + transaction = bsqWalletService.getTransaction(txId); + } + return transaction; + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/FluentProtocol.java b/core/src/main/java/bisq/core/trade/protocol/FluentProtocol.java index 2d79f9ed7e..b1ca476ba6 100644 --- a/core/src/main/java/bisq/core/trade/protocol/FluentProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/FluentProtocol.java @@ -17,8 +17,9 @@ package bisq.core.trade.protocol; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.TradeMessage; +import bisq.core.trade.model.TradeModel; +import bisq.core.trade.model.TradePhase; +import bisq.core.trade.model.TradeState; import bisq.network.p2p.NodeAddress; @@ -42,8 +43,7 @@ import static com.google.common.base.Preconditions.checkArgument; // taskRunner and the optional runnable. public class FluentProtocol { - - interface Event { + public interface Event { String name(); } @@ -61,7 +61,7 @@ public class FluentProtocol { return this; } - protected FluentProtocol setup(Setup setup) { + public FluentProtocol setup(Setup setup) { this.setup = setup; return this; } @@ -98,14 +98,14 @@ public class FluentProtocol { NodeAddress peer = condition.getPeer(); if (peer != null) { - tradeProtocol.processModel.setTempTradingPeerNodeAddress(peer); - tradeProtocol.processModel.getTradeManager().requestPersistence(); + tradeProtocol.protocolModel.setTempTradingPeerNodeAddress(peer); + tradeProtocol.protocolModel.getTradeManager().requestPersistence(); } TradeMessage message = condition.getMessage(); if (message != null) { - tradeProtocol.processModel.setTradeMessage(message); - tradeProtocol.processModel.getTradeManager().requestPersistence(); + tradeProtocol.protocolModel.setTradeMessage(message); + tradeProtocol.protocolModel.getTradeManager().requestPersistence(); } TradeTaskRunner taskRunner = setup.getTaskRunner(message, condition.getEvent()); @@ -146,10 +146,10 @@ public class FluentProtocol { } } - private final Set expectedPhases = new HashSet<>(); - private final Set expectedStates = new HashSet<>(); + private final Set expectedPhases = new HashSet<>(); + private final Set expectedStates = new HashSet<>(); private final Set preConditions = new HashSet<>(); - private final Trade trade; + private final TradeModel tradeModel; @Nullable private Result result; @@ -166,29 +166,29 @@ public class FluentProtocol { private Runnable preConditionFailedHandler; - public Condition(Trade trade) { - this.trade = trade; + public Condition(TradeModel tradeModel) { + this.tradeModel = tradeModel; } - public Condition phase(Trade.Phase expectedPhase) { + public Condition phase(TradePhase expectedPhase) { checkArgument(result == null); this.expectedPhases.add(expectedPhase); return this; } - public Condition anyPhase(Trade.Phase... expectedPhases) { + public Condition anyPhase(TradePhase... expectedPhases) { checkArgument(result == null); this.expectedPhases.addAll(Set.of(expectedPhases)); return this; } - public Condition state(Trade.State state) { + public Condition state(TradeState state) { checkArgument(result == null); this.expectedStates.add(state); return this; } - public Condition anyState(Trade.State... states) { + public Condition anyState(TradeState... states) { checkArgument(result == null); this.expectedStates.addAll(Set.of(states)); return this; @@ -228,10 +228,10 @@ public class FluentProtocol { public Result getResult() { if (result == null) { - boolean isTradeIdValid = message == null || isTradeIdValid(trade.getId(), message); + boolean isTradeIdValid = message == null || isTradeIdValid(tradeModel.getId(), message); if (!isTradeIdValid) { String info = MessageFormat.format("TradeId does not match tradeId in message, TradeId={0}, tradeId in message={1}", - trade.getId(), message.getTradeId()); + tradeModel.getId(), message.getTradeId()); result = Result.INVALID_TRADE_ID.info(info); return result; } @@ -252,7 +252,7 @@ public class FluentProtocol { boolean allPreConditionsMet = preConditions.stream().allMatch(e -> e); if (!allPreConditionsMet) { String info = MessageFormat.format("PreConditions not met. preConditions={0}, this={1}, tradeId={2}", - preConditions, this, trade.getId()); + preConditions, this, tradeModel.getId()); result = Result.INVALID_PRE_CONDITION.info(info); if (preConditionFailedHandler != null) { @@ -271,7 +271,7 @@ public class FluentProtocol { return Result.VALID; } - boolean isPhaseValid = expectedPhases.stream().anyMatch(e -> e == trade.getPhase()); + boolean isPhaseValid = expectedPhases.stream().anyMatch(e -> e == tradeModel.getTradePhase()); String trigger = message != null ? message.getClass().getSimpleName() : event != null ? @@ -280,9 +280,9 @@ public class FluentProtocol { if (isPhaseValid) { String info = MessageFormat.format("We received a {0} at phase {1} and state {2}, tradeId={3}", trigger, - trade.getPhase(), - trade.getState(), - trade.getId()); + tradeModel.getTradePhase(), + tradeModel.getTradeState(), + tradeModel.getId()); log.info(info); return Result.VALID.info(info); } else { @@ -292,9 +292,9 @@ public class FluentProtocol { "Expected phases={1},\nTrade phase={2},\nTrade state= {3},\ntradeId={4}", trigger, expectedPhases, - trade.getPhase(), - trade.getState(), - trade.getId()); + tradeModel.getTradePhase(), + tradeModel.getTradeState(), + tradeModel.getId()); return Result.INVALID_PHASE.info(info); } } @@ -304,7 +304,7 @@ public class FluentProtocol { return Result.VALID; } - boolean isStateValid = expectedStates.stream().anyMatch(e -> e == trade.getState()); + boolean isStateValid = expectedStates.stream().anyMatch(e -> e == tradeModel.getTradeState()); String trigger = message != null ? message.getClass().getSimpleName() : event != null ? @@ -313,8 +313,8 @@ public class FluentProtocol { if (isStateValid) { String info = MessageFormat.format("We received a {0} at state {1}, tradeId={2}", trigger, - trade.getState(), - trade.getId()); + tradeModel.getTradeState(), + tradeModel.getId()); log.info(info); return Result.VALID.info(info); } else { @@ -322,8 +322,8 @@ public class FluentProtocol { "Expected states={1}, Trade state= {2}, tradeId={3}", trigger, expectedStates, - trade.getState(), - trade.getId()); + tradeModel.getTradeState(), + tradeModel.getId()); return Result.INVALID_STATE.info(info); } } @@ -337,21 +337,21 @@ public class FluentProtocol { @Slf4j public static class Setup { private final TradeProtocol tradeProtocol; - private final Trade trade; + private final TradeModel tradeModel; @Getter - private Class>[] tasks; + private Class>[] tasks; @Getter private int timeoutSec; @Nullable private TradeTaskRunner taskRunner; - public Setup(TradeProtocol tradeProtocol, Trade trade) { + public Setup(TradeProtocol tradeProtocol, TradeModel tradeModel) { this.tradeProtocol = tradeProtocol; - this.trade = trade; + this.tradeModel = tradeModel; } @SafeVarargs - public final Setup tasks(Class>... tasks) { + public final Setup tasks(Class>... tasks) { this.tasks = tasks; return this; } @@ -369,11 +369,11 @@ public class FluentProtocol { public TradeTaskRunner getTaskRunner(@Nullable TradeMessage message, @Nullable Event event) { if (taskRunner == null) { if (message != null) { - taskRunner = new TradeTaskRunner(trade, + taskRunner = new TradeTaskRunner(tradeModel, () -> tradeProtocol.handleTaskRunnerSuccess(message), errorMessage -> tradeProtocol.handleTaskRunnerFault(message, errorMessage)); } else if (event != null) { - taskRunner = new TradeTaskRunner(trade, + taskRunner = new TradeTaskRunner(tradeModel, () -> tradeProtocol.handleTaskRunnerSuccess(event), errorMessage -> tradeProtocol.handleTaskRunnerFault(event, errorMessage)); } else { diff --git a/core/src/main/java/bisq/core/trade/protocol/ProtocolModel.java b/core/src/main/java/bisq/core/trade/protocol/ProtocolModel.java new file mode 100644 index 0000000000..7c6f6b23ab --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/ProtocolModel.java @@ -0,0 +1,28 @@ +package bisq.core.trade.protocol; + +import bisq.core.offer.Offer; +import bisq.core.trade.TradeManager; + +import bisq.network.p2p.NodeAddress; +import bisq.network.p2p.P2PService; + +import bisq.common.proto.persistable.PersistablePayload; +import bisq.common.taskrunner.Model; + +public interface ProtocolModel extends Model, PersistablePayload { + void applyTransient(Provider provider, TradeManager tradeManager, Offer offer); + + P2PService getP2PService(); + + T getTradePeer(); + + void setTempTradingPeerNodeAddress(NodeAddress nodeAddress); + + NodeAddress getTempTradingPeerNodeAddress(); + + TradeManager getTradeManager(); + + void setTradeMessage(TradeMessage tradeMessage); + + NodeAddress getMyNodeAddress(); +} diff --git a/core/src/main/java/bisq/core/trade/protocol/ProcessModelServiceProvider.java b/core/src/main/java/bisq/core/trade/protocol/Provider.java similarity index 72% rename from core/src/main/java/bisq/core/trade/protocol/ProcessModelServiceProvider.java rename to core/src/main/java/bisq/core/trade/protocol/Provider.java index 37600eccaf..273285efe1 100644 --- a/core/src/main/java/bisq/core/trade/protocol/ProcessModelServiceProvider.java +++ b/core/src/main/java/bisq/core/trade/protocol/Provider.java @@ -21,9 +21,11 @@ import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.btc.wallet.BsqWalletService; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.TradeWalletService; +import bisq.core.btc.wallet.WalletsManager; import bisq.core.dao.DaoFacade; import bisq.core.filter.FilterManager; import bisq.core.offer.OpenOfferManager; +import bisq.core.provider.fee.FeeService; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import bisq.core.support.dispute.mediation.mediator.MediatorManager; import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; @@ -40,12 +42,13 @@ import javax.inject.Inject; import lombok.Getter; @Getter -public class ProcessModelServiceProvider { +public class Provider { private final OpenOfferManager openOfferManager; private final P2PService p2PService; private final BtcWalletService btcWalletService; private final BsqWalletService bsqWalletService; private final TradeWalletService tradeWalletService; + private final WalletsManager walletsManager; private final DaoFacade daoFacade; private final ReferralIdService referralIdService; private final User user; @@ -56,29 +59,33 @@ public class ProcessModelServiceProvider { private final MediatorManager mediatorManager; private final RefundAgentManager refundAgentManager; private final KeyRing keyRing; + private final FeeService feeService; @Inject - public ProcessModelServiceProvider(OpenOfferManager openOfferManager, - P2PService p2PService, - BtcWalletService btcWalletService, - BsqWalletService bsqWalletService, - TradeWalletService tradeWalletService, - DaoFacade daoFacade, - ReferralIdService referralIdService, - User user, - FilterManager filterManager, - AccountAgeWitnessService accountAgeWitnessService, - TradeStatisticsManager tradeStatisticsManager, - ArbitratorManager arbitratorManager, - MediatorManager mediatorManager, - RefundAgentManager refundAgentManager, - KeyRing keyRing) { + public Provider(OpenOfferManager openOfferManager, + P2PService p2PService, + BtcWalletService btcWalletService, + BsqWalletService bsqWalletService, + TradeWalletService tradeWalletService, + WalletsManager walletsManager, + DaoFacade daoFacade, + ReferralIdService referralIdService, + User user, + FilterManager filterManager, + AccountAgeWitnessService accountAgeWitnessService, + TradeStatisticsManager tradeStatisticsManager, + ArbitratorManager arbitratorManager, + MediatorManager mediatorManager, + RefundAgentManager refundAgentManager, + KeyRing keyRing, + FeeService feeService) { this.openOfferManager = openOfferManager; this.p2PService = p2PService; this.btcWalletService = btcWalletService; this.bsqWalletService = bsqWalletService; this.tradeWalletService = tradeWalletService; + this.walletsManager = walletsManager; this.daoFacade = daoFacade; this.referralIdService = referralIdService; this.user = user; @@ -89,5 +96,6 @@ public class ProcessModelServiceProvider { this.mediatorManager = mediatorManager; this.refundAgentManager = refundAgentManager; this.keyRing = keyRing; + this.feeService = feeService; } } diff --git a/core/src/main/java/bisq/core/trade/messages/TradeMessage.java b/core/src/main/java/bisq/core/trade/protocol/TradeMessage.java similarity index 97% rename from core/src/main/java/bisq/core/trade/messages/TradeMessage.java rename to core/src/main/java/bisq/core/trade/protocol/TradeMessage.java index e90cbb0265..2854365099 100644 --- a/core/src/main/java/bisq/core/trade/messages/TradeMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/TradeMessage.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.messages; +package bisq.core.trade.protocol; import bisq.network.p2p.UidMessage; diff --git a/core/src/main/java/bisq/core/trade/Tradable.java b/core/src/main/java/bisq/core/trade/protocol/TradePeer.java similarity index 76% rename from core/src/main/java/bisq/core/trade/Tradable.java rename to core/src/main/java/bisq/core/trade/protocol/TradePeer.java index e9b3f33193..81b134fbb6 100644 --- a/core/src/main/java/bisq/core/trade/Tradable.java +++ b/core/src/main/java/bisq/core/trade/protocol/TradePeer.java @@ -15,20 +15,13 @@ * along with Bisq. If not, see . */ -package bisq.core.trade; - -import bisq.core.offer.Offer; +package bisq.core.trade.protocol; +import bisq.common.crypto.PubKeyRing; import bisq.common.proto.persistable.PersistablePayload; -import java.util.Date; +public interface TradePeer extends PersistablePayload { + PubKeyRing getPubKeyRing(); -public interface Tradable extends PersistablePayload { - Offer getOffer(); - - Date getDate(); - - String getId(); - - String getShortId(); + void setPubKeyRing(PubKeyRing pubKeyRing); } diff --git a/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java b/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java index 2135aecec8..549439e61b 100644 --- a/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java @@ -18,11 +18,9 @@ package bisq.core.trade.protocol; import bisq.core.offer.Offer; -import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; -import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage; -import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage; -import bisq.core.trade.messages.TradeMessage; +import bisq.core.trade.model.TradeModel; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.network.p2p.AckMessage; import bisq.network.p2p.AckMessageSourceType; @@ -44,6 +42,7 @@ import java.util.Collection; import java.util.Collections; import java.util.concurrent.TimeUnit; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import javax.annotation.Nullable; @@ -51,8 +50,9 @@ import javax.annotation.Nullable; @Slf4j public abstract class TradeProtocol implements DecryptedDirectMessageListener, DecryptedMailboxListener { - protected final ProcessModel processModel; - protected final Trade trade; + @Getter + protected final ProtocolModel protocolModel; + protected final TradeModel tradeModel; private Timer timeoutTimer; @@ -60,9 +60,9 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D // Constructor /////////////////////////////////////////////////////////////////////////////////////////// - public TradeProtocol(Trade trade) { - this.trade = trade; - this.processModel = trade.getProcessModel(); + public TradeProtocol(TradeModel tradeModel) { + this.tradeModel = tradeModel; + this.protocolModel = tradeModel.getTradeProtocolModel(); } @@ -70,18 +70,18 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D // API /////////////////////////////////////////////////////////////////////////////////////////// - public void initialize(ProcessModelServiceProvider serviceProvider, TradeManager tradeManager, Offer offer) { - processModel.applyTransient(serviceProvider, tradeManager, offer); + public void initialize(Provider serviceProvider, TradeManager tradeManager, Offer offer) { + protocolModel.applyTransient(serviceProvider, tradeManager, offer); onInitialized(); } protected void onInitialized() { - if (!trade.isWithdrawn()) { - processModel.getP2PService().addDecryptedDirectMessageListener(this); + if (!tradeModel.isCompleted()) { + protocolModel.getP2PService().addDecryptedDirectMessageListener(this); } - MailboxMessageService mailboxMessageService = processModel.getP2PService().getMailboxMessageService(); - // We delay a bit here as the trade gets updated from the wallet to update the trade + MailboxMessageService mailboxMessageService = protocolModel.getP2PService().getMailboxMessageService(); + // We delay a bit here as the tradeModel gets updated from the wallet to update the tradeModel // state (deposit confirmed) and that happens after our method is called. // TODO To fix that in a better way we would need to change the order of some routines // from the TradeManager, but as we are close to a release I dont want to risk a bigger @@ -145,32 +145,33 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D } private void handleMailboxMessage(MailboxMessage mailboxMessage) { + ProtocolModel protocolModel = tradeModel.getTradeProtocolModel(); if (mailboxMessage instanceof TradeMessage) { TradeMessage tradeMessage = (TradeMessage) mailboxMessage; - // We only remove here if we have already completed the trade. + // We only remove here if we have already completed the tradeModel. // Otherwise removal is done after successfully applied the task runner. - if (trade.isWithdrawn()) { - processModel.getP2PService().getMailboxMessageService().removeMailboxMsg(mailboxMessage); - log.info("Remove {} from the P2P network as trade is already completed.", + if (tradeModel.isCompleted()) { + protocolModel.getP2PService().getMailboxMessageService().removeMailboxMsg(mailboxMessage); + log.info("Remove {} from the P2P network as tradeModel is already completed.", tradeMessage.getClass().getSimpleName()); return; } onMailboxMessage(tradeMessage, mailboxMessage.getSenderNodeAddress()); } else if (mailboxMessage instanceof AckMessage) { AckMessage ackMessage = (AckMessage) mailboxMessage; - if (!trade.isWithdrawn()) { - // We only apply the msg if we have not already completed the trade + if (!tradeModel.isCompleted()) { + // We only apply the msg if we have not already completed the tradeModel onAckMessage(ackMessage, mailboxMessage.getSenderNodeAddress()); } // In any case we remove the msg - processModel.getP2PService().getMailboxMessageService().removeMailboxMsg(ackMessage); + protocolModel.getP2PService().getMailboxMessageService().removeMailboxMsg(ackMessage); log.info("Remove {} from the P2P network.", ackMessage.getClass().getSimpleName()); } } public void removeMailboxMessageAfterProcessing(TradeMessage tradeMessage) { if (tradeMessage instanceof MailboxMessage) { - processModel.getP2PService().getMailboxMessageService().removeMailboxMsg((MailboxMessage) tradeMessage); + protocolModel.getP2PService().getMailboxMessageService().removeMailboxMsg((MailboxMessage) tradeMessage); log.info("Remove {} from the P2P network.", tradeMessage.getClass().getSimpleName()); } } @@ -208,16 +209,20 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D } protected FluentProtocol.Condition phase(Trade.Phase expectedPhase) { - return new FluentProtocol.Condition(trade).phase(expectedPhase); + return new FluentProtocol.Condition(tradeModel).phase(expectedPhase); } protected FluentProtocol.Condition anyPhase(Trade.Phase... expectedPhases) { - return new FluentProtocol.Condition(trade).anyPhase(expectedPhases); + return new FluentProtocol.Condition(tradeModel).anyPhase(expectedPhases); + } + + protected FluentProtocol.Condition preCondition(boolean preCondition) { + return new FluentProtocol.Condition(tradeModel).preCondition(preCondition); } @SafeVarargs - public final FluentProtocol.Setup tasks(Class>... tasks) { - return new FluentProtocol.Setup(this, trade).tasks(tasks); + public final FluentProtocol.Setup tasks(Class>... tasks) { + return new FluentProtocol.Setup(this, tradeModel).tasks(tasks); } @@ -225,26 +230,10 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D // ACK msg /////////////////////////////////////////////////////////////////////////////////////////// - private void onAckMessage(AckMessage ackMessage, NodeAddress peer) { - // We handle the ack for CounterCurrencyTransferStartedMessage and DepositTxAndDelayedPayoutTxMessage - // as we support automatic re-send of the msg in case it was not ACKed after a certain time - if (ackMessage.getSourceMsgClassName().equals(CounterCurrencyTransferStartedMessage.class.getSimpleName())) { - processModel.setPaymentStartedAckMessage(ackMessage); - } else if (ackMessage.getSourceMsgClassName().equals(DepositTxAndDelayedPayoutTxMessage.class.getSimpleName())) { - processModel.setDepositTxSentAckMessage(ackMessage); - } - - if (ackMessage.isSuccess()) { - log.info("Received AckMessage for {} from {} with tradeId {} and uid {}", - ackMessage.getSourceMsgClassName(), peer, trade.getId(), ackMessage.getSourceUid()); - } else { - log.warn("Received AckMessage with error state for {} from {} with tradeId {} and errorMessage={}", - ackMessage.getSourceMsgClassName(), peer, trade.getId(), ackMessage.getErrorMessage()); - } - } + abstract protected void onAckMessage(AckMessage ackMessage, NodeAddress peer); protected void sendAckMessage(TradeMessage message, boolean result, @Nullable String errorMessage) { - PubKeyRing peersPubKeyRing = processModel.getTradingPeer().getPubKeyRing(); + PubKeyRing peersPubKeyRing = protocolModel.getTradePeer().getPubKeyRing(); if (peersPubKeyRing == null) { log.error("We cannot send the ACK message as peersPubKeyRing is null"); return; @@ -252,21 +241,21 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D String tradeId = message.getTradeId(); String sourceUid = message.getUid(); - AckMessage ackMessage = new AckMessage(processModel.getMyNodeAddress(), + AckMessage ackMessage = new AckMessage(protocolModel.getMyNodeAddress(), AckMessageSourceType.TRADE_MESSAGE, message.getClass().getSimpleName(), sourceUid, tradeId, result, errorMessage); - // If there was an error during offer verification, the tradingPeerNodeAddress of the trade might not be set yet. - // We can find the peer's node address in the processModel's tempTradingPeerNodeAddress in that case. - NodeAddress peer = trade.getTradingPeerNodeAddress() != null ? - trade.getTradingPeerNodeAddress() : - processModel.getTempTradingPeerNodeAddress(); + // If there was an error during offer verification, the tradingPeerNodeAddress of the tradeModel might not be set yet. + // We can find the peer's node address in the protocolModel's tempTradingPeerNodeAddress in that case. + NodeAddress peer = tradeModel.getTradingPeerNodeAddress() != null ? + tradeModel.getTradingPeerNodeAddress() : + protocolModel.getTempTradingPeerNodeAddress(); log.info("Send AckMessage for {} to peer {}. tradeId={}, sourceUid={}", ackMessage.getSourceMsgClassName(), peer, tradeId, sourceUid); - processModel.getP2PService().getMailboxMessageService().sendEncryptedMailboxMessage( + protocolModel.getP2PService().getMailboxMessageService().sendEncryptedMailboxMessage( peer, peersPubKeyRing, ackMessage, @@ -302,10 +291,10 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D timeoutTimer = UserThread.runAfter(() -> { log.error("Timeout reached. TradeID={}, state={}, timeoutSec={}", - trade.getId(), trade.stateProperty().get(), timeoutSec); - trade.setErrorMessage("Timeout reached. Protocol did not complete in " + timeoutSec + " sec."); + tradeModel.getId(), tradeModel.getTradeState(), timeoutSec); + tradeModel.setErrorMessage("Timeout reached. Protocol did not complete in " + timeoutSec + " sec."); - processModel.getTradeManager().requestPersistence(); + protocolModel.getTradeManager().requestPersistence(); cleanup(); }, timeoutSec); } @@ -345,8 +334,8 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D private boolean isPubKeyValid(DecryptedMessageWithPubKey message) { // We can only validate the peers pubKey if we have it already. If we are the taker we get it from the offer - // Otherwise it depends on the state of the trade protocol if we have received the peers pubKeyRing already. - PubKeyRing peersPubKeyRing = processModel.getTradingPeer().getPubKeyRing(); + // Otherwise it depends on the state of the tradeModel protocol if we have received the peers pubKeyRing already. + PubKeyRing peersPubKeyRing = protocolModel.getTradePeer().getPubKeyRing(); boolean isValid = true; if (peersPubKeyRing != null && !message.getSignaturePubKey().equals(peersPubKeyRing.getSignaturePubKey())) { @@ -362,7 +351,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D /////////////////////////////////////////////////////////////////////////////////////////// private void handleTaskRunnerSuccess(@Nullable TradeMessage message, String source) { - log.info("TaskRunner successfully completed. Triggered from {}, tradeId={}", source, trade.getId()); + log.info("TaskRunner successfully completed. Triggered from {}, tradeId={}", source, tradeModel.getId()); if (message != null) { sendAckMessage(message, true, null); @@ -385,11 +374,11 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D private boolean isMyMessage(NetworkEnvelope message) { if (message instanceof TradeMessage) { TradeMessage tradeMessage = (TradeMessage) message; - return tradeMessage.getTradeId().equals(trade.getId()); + return tradeMessage.getTradeId().equals(tradeModel.getId()); } else if (message instanceof AckMessage) { AckMessage ackMessage = (AckMessage) message; return ackMessage.getSourceType() == AckMessageSourceType.TRADE_MESSAGE && - ackMessage.getSourceId().equals(trade.getId()); + ackMessage.getSourceId().equals(tradeModel.getId()); } else { return false; } diff --git a/core/src/main/java/bisq/core/trade/protocol/TradeProtocolFactory.java b/core/src/main/java/bisq/core/trade/protocol/TradeProtocolFactory.java index fe521e9918..54549adc2f 100644 --- a/core/src/main/java/bisq/core/trade/protocol/TradeProtocolFactory.java +++ b/core/src/main/java/bisq/core/trade/protocol/TradeProtocolFactory.java @@ -17,24 +17,43 @@ package bisq.core.trade.protocol; -import bisq.core.trade.BuyerAsMakerTrade; -import bisq.core.trade.BuyerAsTakerTrade; -import bisq.core.trade.SellerAsMakerTrade; -import bisq.core.trade.SellerAsTakerTrade; -import bisq.core.trade.Trade; +import bisq.core.trade.model.TradeModel; +import bisq.core.trade.model.bisq_v1.BuyerAsMakerTrade; +import bisq.core.trade.model.bisq_v1.BuyerAsTakerTrade; +import bisq.core.trade.model.bisq_v1.SellerAsMakerTrade; +import bisq.core.trade.model.bisq_v1.SellerAsTakerTrade; +import bisq.core.trade.model.bsq_swap.BsqSwapBuyerAsMakerTrade; +import bisq.core.trade.model.bsq_swap.BsqSwapBuyerAsTakerTrade; +import bisq.core.trade.model.bsq_swap.BsqSwapSellerAsMakerTrade; +import bisq.core.trade.model.bsq_swap.BsqSwapSellerAsTakerTrade; +import bisq.core.trade.protocol.bisq_v1.BuyerAsMakerProtocol; +import bisq.core.trade.protocol.bisq_v1.BuyerAsTakerProtocol; +import bisq.core.trade.protocol.bisq_v1.SellerAsMakerProtocol; +import bisq.core.trade.protocol.bisq_v1.SellerAsTakerProtocol; +import bisq.core.trade.protocol.bsq_swap.BsqSwapBuyerAsMakerProtocol; +import bisq.core.trade.protocol.bsq_swap.BsqSwapBuyerAsTakerProtocol; +import bisq.core.trade.protocol.bsq_swap.BsqSwapSellerAsMakerProtocol; +import bisq.core.trade.protocol.bsq_swap.BsqSwapSellerAsTakerProtocol; public class TradeProtocolFactory { - public static TradeProtocol getNewTradeProtocol(Trade trade) { - if (trade instanceof BuyerAsMakerTrade) { - return new BuyerAsMakerProtocol((BuyerAsMakerTrade) trade); - } else if (trade instanceof BuyerAsTakerTrade) { - return new BuyerAsTakerProtocol((BuyerAsTakerTrade) trade); - } else if (trade instanceof SellerAsMakerTrade) { - return new SellerAsMakerProtocol((SellerAsMakerTrade) trade); - } else if (trade instanceof SellerAsTakerTrade) { - return new SellerAsTakerProtocol((SellerAsTakerTrade) trade); - } else { - throw new IllegalStateException("Trade not of expected type. Trade=" + trade); - } + public static TradeProtocol getNewTradeProtocol(TradeModel tradeModel) { + if (tradeModel instanceof BuyerAsMakerTrade) { + return new BuyerAsMakerProtocol((BuyerAsMakerTrade) tradeModel); + } else if (tradeModel instanceof BuyerAsTakerTrade) { + return new BuyerAsTakerProtocol((BuyerAsTakerTrade) tradeModel); + } else if (tradeModel instanceof SellerAsMakerTrade) { + return new SellerAsMakerProtocol((SellerAsMakerTrade) tradeModel); + } else if (tradeModel instanceof SellerAsTakerTrade) { + return new SellerAsTakerProtocol((SellerAsTakerTrade) tradeModel); + } else if (tradeModel instanceof BsqSwapBuyerAsMakerTrade) { + return new BsqSwapBuyerAsMakerProtocol((BsqSwapBuyerAsMakerTrade) tradeModel); + } else if (tradeModel instanceof BsqSwapBuyerAsTakerTrade) { + return new BsqSwapBuyerAsTakerProtocol((BsqSwapBuyerAsTakerTrade) tradeModel); + } else if (tradeModel instanceof BsqSwapSellerAsMakerTrade) { + return new BsqSwapSellerAsMakerProtocol((BsqSwapSellerAsMakerTrade) tradeModel); + } else if (tradeModel instanceof BsqSwapSellerAsTakerTrade) { + return new BsqSwapSellerAsTakerProtocol((BsqSwapSellerAsTakerTrade) tradeModel); + } else + throw new IllegalStateException("Trade not of expected type. Trade=" + tradeModel); } } diff --git a/core/src/main/java/bisq/core/trade/protocol/TradeTaskRunner.java b/core/src/main/java/bisq/core/trade/protocol/TradeTaskRunner.java index fef4ff490d..78171d9038 100644 --- a/core/src/main/java/bisq/core/trade/protocol/TradeTaskRunner.java +++ b/core/src/main/java/bisq/core/trade/protocol/TradeTaskRunner.java @@ -17,16 +17,22 @@ package bisq.core.trade.protocol; -import bisq.core.trade.Trade; +import bisq.core.trade.model.TradeModel; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ResultHandler; import bisq.common.taskrunner.TaskRunner; -public class TradeTaskRunner extends TaskRunner { +public class TradeTaskRunner extends TaskRunner { - public TradeTaskRunner(Trade sharedModel, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { + public TradeTaskRunner(TradeModel sharedModel, + ResultHandler resultHandler, + ErrorMessageHandler errorMessageHandler) { + super(sharedModel, getSharedModelClass(sharedModel), resultHandler, errorMessageHandler); + } + + static Class getSharedModelClass(TradeModel sharedModel) { //noinspection unchecked - super(sharedModel, (Class) sharedModel.getClass().getSuperclass().getSuperclass(), resultHandler, errorMessageHandler); + return (Class) sharedModel.getClass().getSuperclass().getSuperclass(); } } diff --git a/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/BuyerAsMakerProtocol.java similarity index 74% rename from core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/BuyerAsMakerProtocol.java index 4bff4a9c6e..16d47b59b3 100644 --- a/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/BuyerAsMakerProtocol.java @@ -15,29 +15,30 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol; +package bisq.core.trade.protocol.bisq_v1; -import bisq.core.trade.BuyerAsMakerTrade; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest; -import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage; -import bisq.core.trade.messages.InputsForDepositTxRequest; -import bisq.core.trade.messages.PayoutTxPublishedMessage; -import bisq.core.trade.protocol.tasks.ApplyFilter; -import bisq.core.trade.protocol.tasks.TradeTask; -import bisq.core.trade.protocol.tasks.buyer.BuyerFinalizesDelayedPayoutTx; -import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest; -import bisq.core.trade.protocol.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureResponse; -import bisq.core.trade.protocol.tasks.buyer.BuyerSetupDepositTxListener; -import bisq.core.trade.protocol.tasks.buyer.BuyerSignsDelayedPayoutTx; -import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesPreparedDelayedPayoutTx; -import bisq.core.trade.protocol.tasks.buyer_as_maker.BuyerAsMakerCreatesAndSignsDepositTx; -import bisq.core.trade.protocol.tasks.buyer_as_maker.BuyerAsMakerSendsInputsForDepositTxResponse; -import bisq.core.trade.protocol.tasks.maker.MakerCreateAndSignContract; -import bisq.core.trade.protocol.tasks.maker.MakerProcessesInputsForDepositTxRequest; -import bisq.core.trade.protocol.tasks.maker.MakerRemovesOpenOffer; -import bisq.core.trade.protocol.tasks.maker.MakerSetsLockTime; -import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment; +import bisq.core.trade.model.bisq_v1.BuyerAsMakerTrade; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.TradeTaskRunner; +import bisq.core.trade.protocol.bisq_v1.messages.DelayedPayoutTxSignatureRequest; +import bisq.core.trade.protocol.bisq_v1.messages.DepositTxAndDelayedPayoutTxMessage; +import bisq.core.trade.protocol.bisq_v1.messages.InputsForDepositTxRequest; +import bisq.core.trade.protocol.bisq_v1.messages.PayoutTxPublishedMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.ApplyFilter; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerFinalizesDelayedPayoutTx; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureResponse; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerSetupDepositTxListener; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerSignsDelayedPayoutTx; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerVerifiesPreparedDelayedPayoutTx; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer_as_maker.BuyerAsMakerCreatesAndSignsDepositTx; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer_as_maker.BuyerAsMakerSendsInputsForDepositTxResponse; +import bisq.core.trade.protocol.bisq_v1.tasks.maker.MakerCreateAndSignContract; +import bisq.core.trade.protocol.bisq_v1.tasks.maker.MakerProcessesInputsForDepositTxRequest; +import bisq.core.trade.protocol.bisq_v1.tasks.maker.MakerRemovesOpenOffer; +import bisq.core.trade.protocol.bisq_v1.tasks.maker.MakerSetsLockTime; +import bisq.core.trade.protocol.bisq_v1.tasks.maker.MakerVerifyTakerFeePayment; import bisq.network.p2p.NodeAddress; diff --git a/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/BuyerAsTakerProtocol.java similarity index 74% rename from core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/BuyerAsTakerProtocol.java index 2731385f14..a99e4be080 100644 --- a/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/BuyerAsTakerProtocol.java @@ -15,34 +15,34 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol; +package bisq.core.trade.protocol.bisq_v1; import bisq.core.offer.Offer; -import bisq.core.trade.BuyerAsTakerTrade; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest; -import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage; -import bisq.core.trade.messages.InputsForDepositTxResponse; -import bisq.core.trade.messages.PayoutTxPublishedMessage; -import bisq.core.trade.messages.TradeMessage; -import bisq.core.trade.protocol.tasks.ApplyFilter; -import bisq.core.trade.protocol.tasks.TradeTask; -import bisq.core.trade.protocol.tasks.buyer.BuyerFinalizesDelayedPayoutTx; -import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest; -import bisq.core.trade.protocol.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureResponse; -import bisq.core.trade.protocol.tasks.buyer.BuyerSetupDepositTxListener; -import bisq.core.trade.protocol.tasks.buyer.BuyerSignsDelayedPayoutTx; -import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesPreparedDelayedPayoutTx; -import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerCreatesDepositTxInputs; -import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSendsDepositTxMessage; -import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSignsDepositTx; -import bisq.core.trade.protocol.tasks.taker.CreateTakerFeeTx; -import bisq.core.trade.protocol.tasks.taker.TakerProcessesInputsForDepositTxResponse; -import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx; -import bisq.core.trade.protocol.tasks.taker.TakerSendInputsForDepositTxRequest; -import bisq.core.trade.protocol.tasks.taker.TakerVerifyAndSignContract; -import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment; +import bisq.core.trade.model.bisq_v1.BuyerAsTakerTrade; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.TradeMessage; +import bisq.core.trade.protocol.bisq_v1.messages.DelayedPayoutTxSignatureRequest; +import bisq.core.trade.protocol.bisq_v1.messages.DepositTxAndDelayedPayoutTxMessage; +import bisq.core.trade.protocol.bisq_v1.messages.InputsForDepositTxResponse; +import bisq.core.trade.protocol.bisq_v1.messages.PayoutTxPublishedMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.ApplyFilter; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerFinalizesDelayedPayoutTx; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureResponse; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerSetupDepositTxListener; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerSignsDelayedPayoutTx; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerVerifiesPreparedDelayedPayoutTx; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer_as_taker.BuyerAsTakerCreatesDepositTxInputs; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer_as_taker.BuyerAsTakerSendsDepositTxMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer_as_taker.BuyerAsTakerSignsDepositTx; +import bisq.core.trade.protocol.bisq_v1.tasks.taker.CreateTakerFeeTx; +import bisq.core.trade.protocol.bisq_v1.tasks.taker.TakerProcessesInputsForDepositTxResponse; +import bisq.core.trade.protocol.bisq_v1.tasks.taker.TakerPublishFeeTx; +import bisq.core.trade.protocol.bisq_v1.tasks.taker.TakerSendInputsForDepositTxRequest; +import bisq.core.trade.protocol.bisq_v1.tasks.taker.TakerVerifyAndSignContract; +import bisq.core.trade.protocol.bisq_v1.tasks.taker.TakerVerifyMakerFeePayment; import bisq.network.p2p.NodeAddress; @@ -64,7 +64,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol super(trade); Offer offer = checkNotNull(trade.getOffer()); - processModel.getTradingPeer().setPubKeyRing(offer.getPubKeyRing()); + processModel.getTradePeer().setPubKeyRing(offer.getPubKeyRing()); } diff --git a/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/BuyerProtocol.java similarity index 85% rename from core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/BuyerProtocol.java index 2a920b8496..858cada6c8 100644 --- a/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/BuyerProtocol.java @@ -15,25 +15,27 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol; +package bisq.core.trade.protocol.bisq_v1; -import bisq.core.trade.BuyerTrade; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest; -import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage; -import bisq.core.trade.messages.PayoutTxPublishedMessage; -import bisq.core.trade.messages.TradeMessage; -import bisq.core.trade.protocol.tasks.ApplyFilter; -import bisq.core.trade.protocol.tasks.TradeTask; -import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness; -import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDepositTxAndDelayedPayoutTxMessage; -import bisq.core.trade.protocol.tasks.buyer.BuyerProcessPayoutTxPublishedMessage; -import bisq.core.trade.protocol.tasks.buyer.BuyerSendCounterCurrencyTransferStartedMessage; -import bisq.core.trade.protocol.tasks.buyer.BuyerSendsShareBuyerPaymentAccountMessage; -import bisq.core.trade.protocol.tasks.buyer.BuyerSetupDepositTxListener; -import bisq.core.trade.protocol.tasks.buyer.BuyerSetupPayoutTxListener; -import bisq.core.trade.protocol.tasks.buyer.BuyerSignPayoutTx; -import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesFinalDelayedPayoutTx; +import bisq.core.trade.model.bisq_v1.BuyerTrade; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.FluentProtocol; +import bisq.core.trade.protocol.TradeMessage; +import bisq.core.trade.protocol.TradeTaskRunner; +import bisq.core.trade.protocol.bisq_v1.messages.DelayedPayoutTxSignatureRequest; +import bisq.core.trade.protocol.bisq_v1.messages.DepositTxAndDelayedPayoutTxMessage; +import bisq.core.trade.protocol.bisq_v1.messages.PayoutTxPublishedMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.ApplyFilter; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; +import bisq.core.trade.protocol.bisq_v1.tasks.VerifyPeersAccountAgeWitness; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerProcessDepositTxAndDelayedPayoutTxMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerProcessPayoutTxPublishedMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerSendCounterCurrencyTransferStartedMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerSendsShareBuyerPaymentAccountMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerSetupDepositTxListener; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerSetupPayoutTxListener; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerSignPayoutTx; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerVerifiesFinalDelayedPayoutTx; import bisq.network.p2p.NodeAddress; @@ -106,7 +108,7 @@ public abstract class BuyerProtocol extends DisputeProtocol { expect(anyPhase(Trade.Phase.TAKER_FEE_PUBLISHED, Trade.Phase.DEPOSIT_PUBLISHED) .with(message) .from(peer) - .preCondition(trade.getDepositTx() == null || processModel.getTradingPeer().getPaymentAccountPayload() == null, + .preCondition(trade.getDepositTx() == null || processModel.getTradePeer().getPaymentAccountPayload() == null, () -> { log.warn("We received a DepositTxAndDelayedPayoutTxMessage but we have already processed the deposit and " + "delayed payout tx so we ignore the message. This can happen if the ACK message to the peer did not " + diff --git a/core/src/main/java/bisq/core/trade/protocol/DisputeProtocol.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/DisputeProtocol.java similarity index 71% rename from core/src/main/java/bisq/core/trade/protocol/DisputeProtocol.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/DisputeProtocol.java index 23807c96f5..ee549e1cde 100644 --- a/core/src/main/java/bisq/core/trade/protocol/DisputeProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/DisputeProtocol.java @@ -15,26 +15,33 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol; +package bisq.core.trade.protocol.bisq_v1; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.MediatedPayoutTxPublishedMessage; -import bisq.core.trade.messages.MediatedPayoutTxSignatureMessage; -import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage; -import bisq.core.trade.messages.TradeMessage; -import bisq.core.trade.protocol.tasks.ApplyFilter; -import bisq.core.trade.protocol.tasks.ProcessPeerPublishedDelayedPayoutTxMessage; -import bisq.core.trade.protocol.tasks.arbitration.PublishedDelayedPayoutTx; -import bisq.core.trade.protocol.tasks.arbitration.SendPeerPublishedDelayedPayoutTxMessage; -import bisq.core.trade.protocol.tasks.mediation.BroadcastMediatedPayoutTx; -import bisq.core.trade.protocol.tasks.mediation.FinalizeMediatedPayoutTx; -import bisq.core.trade.protocol.tasks.mediation.ProcessMediatedPayoutSignatureMessage; -import bisq.core.trade.protocol.tasks.mediation.ProcessMediatedPayoutTxPublishedMessage; -import bisq.core.trade.protocol.tasks.mediation.SendMediatedPayoutSignatureMessage; -import bisq.core.trade.protocol.tasks.mediation.SendMediatedPayoutTxPublishedMessage; -import bisq.core.trade.protocol.tasks.mediation.SetupMediatedPayoutTxListener; -import bisq.core.trade.protocol.tasks.mediation.SignMediatedPayoutTx; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.FluentProtocol; +import bisq.core.trade.protocol.TradeMessage; +import bisq.core.trade.protocol.TradeProtocol; +import bisq.core.trade.protocol.TradeTaskRunner; +import bisq.core.trade.protocol.bisq_v1.messages.CounterCurrencyTransferStartedMessage; +import bisq.core.trade.protocol.bisq_v1.messages.DepositTxAndDelayedPayoutTxMessage; +import bisq.core.trade.protocol.bisq_v1.messages.MediatedPayoutTxPublishedMessage; +import bisq.core.trade.protocol.bisq_v1.messages.MediatedPayoutTxSignatureMessage; +import bisq.core.trade.protocol.bisq_v1.messages.PeerPublishedDelayedPayoutTxMessage; +import bisq.core.trade.protocol.bisq_v1.model.ProcessModel; +import bisq.core.trade.protocol.bisq_v1.tasks.ApplyFilter; +import bisq.core.trade.protocol.bisq_v1.tasks.ProcessPeerPublishedDelayedPayoutTxMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.arbitration.PublishedDelayedPayoutTx; +import bisq.core.trade.protocol.bisq_v1.tasks.arbitration.SendPeerPublishedDelayedPayoutTxMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.mediation.BroadcastMediatedPayoutTx; +import bisq.core.trade.protocol.bisq_v1.tasks.mediation.FinalizeMediatedPayoutTx; +import bisq.core.trade.protocol.bisq_v1.tasks.mediation.ProcessMediatedPayoutSignatureMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.mediation.ProcessMediatedPayoutTxPublishedMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.mediation.SendMediatedPayoutSignatureMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.mediation.SendMediatedPayoutTxPublishedMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.mediation.SetupMediatedPayoutTxListener; +import bisq.core.trade.protocol.bisq_v1.tasks.mediation.SignMediatedPayoutTx; +import bisq.network.p2p.AckMessage; import bisq.network.p2p.NodeAddress; import bisq.common.handlers.ErrorMessageHandler; @@ -45,6 +52,9 @@ import lombok.extern.slf4j.Slf4j; @Slf4j public class DisputeProtocol extends TradeProtocol { + protected Trade trade; + protected final ProcessModel processModel; + enum DisputeEvent implements FluentProtocol.Event { MEDIATION_RESULT_ACCEPTED, MEDIATION_RESULT_REJECTED, @@ -53,6 +63,31 @@ public class DisputeProtocol extends TradeProtocol { public DisputeProtocol(Trade trade) { super(trade); + this.trade = trade; + this.processModel = trade.getProcessModel(); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // TradeProtocol implementation + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + protected void onAckMessage(AckMessage ackMessage, NodeAddress peer) { + // We handle the ack for CounterCurrencyTransferStartedMessage and DepositTxAndDelayedPayoutTxMessage + // as we support automatic re-send of the msg in case it was not ACKed after a certain time + if (ackMessage.getSourceMsgClassName().equals(CounterCurrencyTransferStartedMessage.class.getSimpleName())) { + processModel.setPaymentStartedAckMessage(ackMessage); + } else if (ackMessage.getSourceMsgClassName().equals(DepositTxAndDelayedPayoutTxMessage.class.getSimpleName())) { + processModel.setDepositTxSentAckMessage(ackMessage); + } + + if (ackMessage.isSuccess()) { + log.info("Received AckMessage for {} from {} with tradeId {} and uid {}", + ackMessage.getSourceMsgClassName(), peer, tradeModel.getId(), ackMessage.getSourceUid()); + } else { + log.warn("Received AckMessage with error state for {} from {} with tradeId {} and errorMessage={}", + ackMessage.getSourceMsgClassName(), peer, tradeModel.getId(), ackMessage.getErrorMessage()); + } } @@ -67,7 +102,7 @@ public class DisputeProtocol extends TradeProtocol { Trade.Phase.FIAT_SENT, Trade.Phase.FIAT_RECEIVED) .with(event) - .preCondition(trade.getProcessModel().getTradingPeer().getMediatedPayoutTxSignature() == null, + .preCondition(trade.getProcessModel().getTradePeer().getMediatedPayoutTxSignature() == null, () -> errorMessageHandler.handleErrorMessage("We have received already the signature from the peer.")) .preCondition(trade.getPayoutTx() == null, () -> errorMessageHandler.handleErrorMessage("Payout tx is already published."))) diff --git a/core/src/main/java/bisq/core/trade/protocol/MakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/MakerProtocol.java similarity index 89% rename from core/src/main/java/bisq/core/trade/protocol/MakerProtocol.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/MakerProtocol.java index 349e677a70..7c1b3299d7 100644 --- a/core/src/main/java/bisq/core/trade/protocol/MakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/MakerProtocol.java @@ -15,10 +15,10 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol; +package bisq.core.trade.protocol.bisq_v1; -import bisq.core.trade.messages.InputsForDepositTxRequest; +import bisq.core.trade.protocol.bisq_v1.messages.InputsForDepositTxRequest; import bisq.network.p2p.NodeAddress; diff --git a/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/SellerAsMakerProtocol.java similarity index 76% rename from core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/SellerAsMakerProtocol.java index 524ab34691..53a7722f30 100644 --- a/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/SellerAsMakerProtocol.java @@ -15,30 +15,31 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol; +package bisq.core.trade.protocol.bisq_v1; -import bisq.core.trade.SellerAsMakerTrade; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage; -import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse; -import bisq.core.trade.messages.DepositTxMessage; -import bisq.core.trade.messages.InputsForDepositTxRequest; -import bisq.core.trade.messages.TradeMessage; -import bisq.core.trade.protocol.tasks.ApplyFilter; -import bisq.core.trade.protocol.tasks.TradeTask; -import bisq.core.trade.protocol.tasks.maker.MakerCreateAndSignContract; -import bisq.core.trade.protocol.tasks.maker.MakerProcessesInputsForDepositTxRequest; -import bisq.core.trade.protocol.tasks.maker.MakerRemovesOpenOffer; -import bisq.core.trade.protocol.tasks.maker.MakerSetsLockTime; -import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment; -import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx; -import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest; -import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx; -import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerCreatesUnsignedDepositTx; -import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerFinalizesDepositTx; -import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerProcessDepositTxMessage; -import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerSendsInputsForDepositTxResponse; +import bisq.core.trade.model.bisq_v1.SellerAsMakerTrade; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.TradeMessage; +import bisq.core.trade.protocol.TradeTaskRunner; +import bisq.core.trade.protocol.bisq_v1.messages.CounterCurrencyTransferStartedMessage; +import bisq.core.trade.protocol.bisq_v1.messages.DelayedPayoutTxSignatureResponse; +import bisq.core.trade.protocol.bisq_v1.messages.DepositTxMessage; +import bisq.core.trade.protocol.bisq_v1.messages.InputsForDepositTxRequest; +import bisq.core.trade.protocol.bisq_v1.tasks.ApplyFilter; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; +import bisq.core.trade.protocol.bisq_v1.tasks.maker.MakerCreateAndSignContract; +import bisq.core.trade.protocol.bisq_v1.tasks.maker.MakerProcessesInputsForDepositTxRequest; +import bisq.core.trade.protocol.bisq_v1.tasks.maker.MakerRemovesOpenOffer; +import bisq.core.trade.protocol.bisq_v1.tasks.maker.MakerSetsLockTime; +import bisq.core.trade.protocol.bisq_v1.tasks.maker.MakerVerifyTakerFeePayment; +import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerCreatesDelayedPayoutTx; +import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest; +import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerSignsDelayedPayoutTx; +import bisq.core.trade.protocol.bisq_v1.tasks.seller_as_maker.SellerAsMakerCreatesUnsignedDepositTx; +import bisq.core.trade.protocol.bisq_v1.tasks.seller_as_maker.SellerAsMakerFinalizesDepositTx; +import bisq.core.trade.protocol.bisq_v1.tasks.seller_as_maker.SellerAsMakerProcessDepositTxMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.seller_as_maker.SellerAsMakerSendsInputsForDepositTxResponse; import bisq.network.p2p.NodeAddress; diff --git a/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/SellerAsTakerProtocol.java similarity index 77% rename from core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/SellerAsTakerProtocol.java index 7e807d137a..4cacf30098 100644 --- a/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/SellerAsTakerProtocol.java @@ -15,29 +15,29 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol; +package bisq.core.trade.protocol.bisq_v1; import bisq.core.offer.Offer; -import bisq.core.trade.SellerAsTakerTrade; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage; -import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse; -import bisq.core.trade.messages.InputsForDepositTxResponse; -import bisq.core.trade.messages.TradeMessage; -import bisq.core.trade.protocol.tasks.ApplyFilter; -import bisq.core.trade.protocol.tasks.TradeTask; -import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx; -import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest; -import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx; -import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerCreatesDepositTxInputs; -import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerSignsDepositTx; -import bisq.core.trade.protocol.tasks.taker.CreateTakerFeeTx; -import bisq.core.trade.protocol.tasks.taker.TakerProcessesInputsForDepositTxResponse; -import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx; -import bisq.core.trade.protocol.tasks.taker.TakerSendInputsForDepositTxRequest; -import bisq.core.trade.protocol.tasks.taker.TakerVerifyAndSignContract; -import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment; +import bisq.core.trade.model.bisq_v1.SellerAsTakerTrade; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.TradeMessage; +import bisq.core.trade.protocol.bisq_v1.messages.CounterCurrencyTransferStartedMessage; +import bisq.core.trade.protocol.bisq_v1.messages.DelayedPayoutTxSignatureResponse; +import bisq.core.trade.protocol.bisq_v1.messages.InputsForDepositTxResponse; +import bisq.core.trade.protocol.bisq_v1.tasks.ApplyFilter; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; +import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerCreatesDelayedPayoutTx; +import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest; +import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerSignsDelayedPayoutTx; +import bisq.core.trade.protocol.bisq_v1.tasks.seller_as_taker.SellerAsTakerCreatesDepositTxInputs; +import bisq.core.trade.protocol.bisq_v1.tasks.seller_as_taker.SellerAsTakerSignsDepositTx; +import bisq.core.trade.protocol.bisq_v1.tasks.taker.CreateTakerFeeTx; +import bisq.core.trade.protocol.bisq_v1.tasks.taker.TakerProcessesInputsForDepositTxResponse; +import bisq.core.trade.protocol.bisq_v1.tasks.taker.TakerPublishFeeTx; +import bisq.core.trade.protocol.bisq_v1.tasks.taker.TakerSendInputsForDepositTxRequest; +import bisq.core.trade.protocol.bisq_v1.tasks.taker.TakerVerifyAndSignContract; +import bisq.core.trade.protocol.bisq_v1.tasks.taker.TakerVerifyMakerFeePayment; import bisq.network.p2p.NodeAddress; @@ -58,7 +58,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc public SellerAsTakerProtocol(SellerAsTakerTrade trade) { super(trade); Offer offer = checkNotNull(trade.getOffer()); - processModel.getTradingPeer().setPubKeyRing(offer.getPubKeyRing()); + processModel.getTradePeer().setPubKeyRing(offer.getPubKeyRing()); } diff --git a/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/SellerProtocol.java similarity index 82% rename from core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/SellerProtocol.java index ef5f83b0d3..68a587e6e4 100644 --- a/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/SellerProtocol.java @@ -15,27 +15,29 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol; +package bisq.core.trade.protocol.bisq_v1; -import bisq.core.trade.SellerTrade; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage; -import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse; -import bisq.core.trade.messages.ShareBuyerPaymentAccountMessage; -import bisq.core.trade.messages.TradeMessage; -import bisq.core.trade.protocol.tasks.ApplyFilter; -import bisq.core.trade.protocol.tasks.TradeTask; -import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness; -import bisq.core.trade.protocol.tasks.seller.SellerBroadcastPayoutTx; -import bisq.core.trade.protocol.tasks.seller.SellerFinalizesDelayedPayoutTx; -import bisq.core.trade.protocol.tasks.seller.SellerProcessCounterCurrencyTransferStartedMessage; -import bisq.core.trade.protocol.tasks.seller.SellerProcessDelayedPayoutTxSignatureResponse; -import bisq.core.trade.protocol.tasks.seller.SellerProcessShareBuyerPaymentAccountMessage; -import bisq.core.trade.protocol.tasks.seller.SellerPublishesDepositTx; -import bisq.core.trade.protocol.tasks.seller.SellerPublishesTradeStatistics; -import bisq.core.trade.protocol.tasks.seller.SellerSendPayoutTxPublishedMessage; -import bisq.core.trade.protocol.tasks.seller.SellerSendsDepositTxAndDelayedPayoutTxMessage; -import bisq.core.trade.protocol.tasks.seller.SellerSignAndFinalizePayoutTx; +import bisq.core.trade.model.bisq_v1.SellerTrade; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.FluentProtocol; +import bisq.core.trade.protocol.TradeMessage; +import bisq.core.trade.protocol.TradeTaskRunner; +import bisq.core.trade.protocol.bisq_v1.messages.CounterCurrencyTransferStartedMessage; +import bisq.core.trade.protocol.bisq_v1.messages.DelayedPayoutTxSignatureResponse; +import bisq.core.trade.protocol.bisq_v1.messages.ShareBuyerPaymentAccountMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.ApplyFilter; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; +import bisq.core.trade.protocol.bisq_v1.tasks.VerifyPeersAccountAgeWitness; +import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerBroadcastPayoutTx; +import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerFinalizesDelayedPayoutTx; +import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerProcessCounterCurrencyTransferStartedMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerProcessDelayedPayoutTxSignatureResponse; +import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerProcessShareBuyerPaymentAccountMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerPublishesDepositTx; +import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerPublishesTradeStatistics; +import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerSendPayoutTxPublishedMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerSendsDepositTxAndDelayedPayoutTxMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerSignAndFinalizePayoutTx; import bisq.network.p2p.NodeAddress; diff --git a/core/src/main/java/bisq/core/trade/protocol/TakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/TakerProtocol.java similarity index 90% rename from core/src/main/java/bisq/core/trade/protocol/TakerProtocol.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/TakerProtocol.java index 249d0ac73b..b02b7cba91 100644 --- a/core/src/main/java/bisq/core/trade/protocol/TakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/TakerProtocol.java @@ -15,7 +15,9 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol; +package bisq.core.trade.protocol.bisq_v1; + +import bisq.core.trade.protocol.FluentProtocol; public interface TakerProtocol { void onTakeOffer(); diff --git a/core/src/main/java/bisq/core/trade/messages/CounterCurrencyTransferStartedMessage.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/CounterCurrencyTransferStartedMessage.java similarity index 99% rename from core/src/main/java/bisq/core/trade/messages/CounterCurrencyTransferStartedMessage.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/CounterCurrencyTransferStartedMessage.java index 30811f56f7..9da43cae45 100644 --- a/core/src/main/java/bisq/core/trade/messages/CounterCurrencyTransferStartedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/CounterCurrencyTransferStartedMessage.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.messages; +package bisq.core.trade.protocol.bisq_v1.messages; import bisq.network.p2p.NodeAddress; diff --git a/core/src/main/java/bisq/core/trade/messages/DelayedPayoutTxSignatureRequest.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/DelayedPayoutTxSignatureRequest.java similarity index 97% rename from core/src/main/java/bisq/core/trade/messages/DelayedPayoutTxSignatureRequest.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/DelayedPayoutTxSignatureRequest.java index 81afad3caf..7674a50fe8 100644 --- a/core/src/main/java/bisq/core/trade/messages/DelayedPayoutTxSignatureRequest.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/DelayedPayoutTxSignatureRequest.java @@ -15,7 +15,9 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.messages; +package bisq.core.trade.protocol.bisq_v1.messages; + +import bisq.core.trade.protocol.TradeMessage; import bisq.network.p2p.DirectMessage; import bisq.network.p2p.NodeAddress; diff --git a/core/src/main/java/bisq/core/trade/messages/DelayedPayoutTxSignatureResponse.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/DelayedPayoutTxSignatureResponse.java similarity index 97% rename from core/src/main/java/bisq/core/trade/messages/DelayedPayoutTxSignatureResponse.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/DelayedPayoutTxSignatureResponse.java index ad3767a3ab..f9c81d9c2a 100644 --- a/core/src/main/java/bisq/core/trade/messages/DelayedPayoutTxSignatureResponse.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/DelayedPayoutTxSignatureResponse.java @@ -15,7 +15,9 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.messages; +package bisq.core.trade.protocol.bisq_v1.messages; + +import bisq.core.trade.protocol.TradeMessage; import bisq.network.p2p.DirectMessage; import bisq.network.p2p.NodeAddress; diff --git a/core/src/main/java/bisq/core/trade/messages/DepositTxAndDelayedPayoutTxMessage.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/DepositTxAndDelayedPayoutTxMessage.java similarity index 99% rename from core/src/main/java/bisq/core/trade/messages/DepositTxAndDelayedPayoutTxMessage.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/DepositTxAndDelayedPayoutTxMessage.java index 8297ee92d5..0ed88a22c4 100644 --- a/core/src/main/java/bisq/core/trade/messages/DepositTxAndDelayedPayoutTxMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/DepositTxAndDelayedPayoutTxMessage.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.messages; +package bisq.core.trade.protocol.bisq_v1.messages; import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.proto.CoreProtoResolver; diff --git a/core/src/main/java/bisq/core/trade/messages/DepositTxMessage.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/DepositTxMessage.java similarity index 97% rename from core/src/main/java/bisq/core/trade/messages/DepositTxMessage.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/DepositTxMessage.java index 631f1a9ca2..465eb05e1a 100644 --- a/core/src/main/java/bisq/core/trade/messages/DepositTxMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/DepositTxMessage.java @@ -15,7 +15,9 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.messages; +package bisq.core.trade.protocol.bisq_v1.messages; + +import bisq.core.trade.protocol.TradeMessage; import bisq.network.p2p.DirectMessage; import bisq.network.p2p.NodeAddress; diff --git a/core/src/main/java/bisq/core/trade/messages/InputsForDepositTxRequest.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/InputsForDepositTxRequest.java similarity index 98% rename from core/src/main/java/bisq/core/trade/messages/InputsForDepositTxRequest.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/InputsForDepositTxRequest.java index 63197dbaba..d6cf92ab3d 100644 --- a/core/src/main/java/bisq/core/trade/messages/InputsForDepositTxRequest.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/InputsForDepositTxRequest.java @@ -15,11 +15,12 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.messages; +package bisq.core.trade.protocol.bisq_v1.messages; import bisq.core.btc.model.RawTransactionInput; import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.proto.CoreProtoResolver; +import bisq.core.trade.protocol.TradeMessage; import bisq.network.p2p.DirectMessage; import bisq.network.p2p.NodeAddress; @@ -182,8 +183,7 @@ public final class InputsForDepositTxRequest extends TradeMessage implements Dir CoreProtoResolver coreProtoResolver, int messageVersion) { List rawTransactionInputs = proto.getRawTransactionInputsList().stream() - .map(rawTransactionInput -> new RawTransactionInput(rawTransactionInput.getIndex(), - rawTransactionInput.getParentTransaction().toByteArray(), rawTransactionInput.getValue())) + .map(RawTransactionInput::fromProto) .collect(Collectors.toList()); List acceptedArbitratorNodeAddresses = proto.getAcceptedArbitratorNodeAddressesList().stream() .map(NodeAddress::fromProto).collect(Collectors.toList()); diff --git a/core/src/main/java/bisq/core/trade/messages/InputsForDepositTxResponse.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/InputsForDepositTxResponse.java similarity index 99% rename from core/src/main/java/bisq/core/trade/messages/InputsForDepositTxResponse.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/InputsForDepositTxResponse.java index 72305028f7..fca8d0569d 100644 --- a/core/src/main/java/bisq/core/trade/messages/InputsForDepositTxResponse.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/InputsForDepositTxResponse.java @@ -15,11 +15,12 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.messages; +package bisq.core.trade.protocol.bisq_v1.messages; import bisq.core.btc.model.RawTransactionInput; import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.proto.CoreProtoResolver; +import bisq.core.trade.protocol.TradeMessage; import bisq.network.p2p.DirectMessage; import bisq.network.p2p.NodeAddress; diff --git a/core/src/main/java/bisq/core/trade/messages/MediatedPayoutTxPublishedMessage.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/MediatedPayoutTxPublishedMessage.java similarity index 98% rename from core/src/main/java/bisq/core/trade/messages/MediatedPayoutTxPublishedMessage.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/MediatedPayoutTxPublishedMessage.java index 444b6af804..83e03b1961 100644 --- a/core/src/main/java/bisq/core/trade/messages/MediatedPayoutTxPublishedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/MediatedPayoutTxPublishedMessage.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.messages; +package bisq.core.trade.protocol.bisq_v1.messages; import bisq.network.p2p.NodeAddress; diff --git a/core/src/main/java/bisq/core/trade/messages/MediatedPayoutTxSignatureMessage.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/MediatedPayoutTxSignatureMessage.java similarity index 98% rename from core/src/main/java/bisq/core/trade/messages/MediatedPayoutTxSignatureMessage.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/MediatedPayoutTxSignatureMessage.java index bc7cc84571..eecd3e82e6 100644 --- a/core/src/main/java/bisq/core/trade/messages/MediatedPayoutTxSignatureMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/MediatedPayoutTxSignatureMessage.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.messages; +package bisq.core.trade.protocol.bisq_v1.messages; import bisq.network.p2p.NodeAddress; diff --git a/core/src/main/java/bisq/core/trade/messages/PayoutTxPublishedMessage.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/PayoutTxPublishedMessage.java similarity index 98% rename from core/src/main/java/bisq/core/trade/messages/PayoutTxPublishedMessage.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/PayoutTxPublishedMessage.java index 86ed851ba8..2b89fee4cc 100644 --- a/core/src/main/java/bisq/core/trade/messages/PayoutTxPublishedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/PayoutTxPublishedMessage.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.messages; +package bisq.core.trade.protocol.bisq_v1.messages; import bisq.core.account.sign.SignedWitness; diff --git a/core/src/main/java/bisq/core/trade/messages/PeerPublishedDelayedPayoutTxMessage.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/PeerPublishedDelayedPayoutTxMessage.java similarity index 98% rename from core/src/main/java/bisq/core/trade/messages/PeerPublishedDelayedPayoutTxMessage.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/PeerPublishedDelayedPayoutTxMessage.java index d93a32737b..4cf815df56 100644 --- a/core/src/main/java/bisq/core/trade/messages/PeerPublishedDelayedPayoutTxMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/PeerPublishedDelayedPayoutTxMessage.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.messages; +package bisq.core.trade.protocol.bisq_v1.messages; import bisq.network.p2p.NodeAddress; diff --git a/core/src/main/java/bisq/core/trade/messages/RefreshTradeStateRequest.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/RefreshTradeStateRequest.java similarity index 97% rename from core/src/main/java/bisq/core/trade/messages/RefreshTradeStateRequest.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/RefreshTradeStateRequest.java index c6abcd67ee..5efc83c79a 100644 --- a/core/src/main/java/bisq/core/trade/messages/RefreshTradeStateRequest.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/RefreshTradeStateRequest.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.messages; +package bisq.core.trade.protocol.bisq_v1.messages; import bisq.network.p2p.NodeAddress; diff --git a/core/src/main/java/bisq/core/trade/messages/ShareBuyerPaymentAccountMessage.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/ShareBuyerPaymentAccountMessage.java similarity index 98% rename from core/src/main/java/bisq/core/trade/messages/ShareBuyerPaymentAccountMessage.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/ShareBuyerPaymentAccountMessage.java index 6401d49947..3814b7ee98 100644 --- a/core/src/main/java/bisq/core/trade/messages/ShareBuyerPaymentAccountMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/ShareBuyerPaymentAccountMessage.java @@ -32,7 +32,7 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.messages; +package bisq.core.trade.protocol.bisq_v1.messages; import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.proto.CoreProtoResolver; diff --git a/core/src/main/java/bisq/core/trade/messages/TradeMailboxMessage.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/TradeMailboxMessage.java similarity index 92% rename from core/src/main/java/bisq/core/trade/messages/TradeMailboxMessage.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/TradeMailboxMessage.java index 705c0b12ed..f5237d085c 100644 --- a/core/src/main/java/bisq/core/trade/messages/TradeMailboxMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/TradeMailboxMessage.java @@ -15,7 +15,9 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.messages; +package bisq.core.trade.protocol.bisq_v1.messages; + +import bisq.core.trade.protocol.TradeMessage; import bisq.network.p2p.mailbox.MailboxMessage; diff --git a/core/src/main/java/bisq/core/trade/messages/TraderSignedWitnessMessage.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/TraderSignedWitnessMessage.java similarity index 98% rename from core/src/main/java/bisq/core/trade/messages/TraderSignedWitnessMessage.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/TraderSignedWitnessMessage.java index c2bbde53b3..51f811dd66 100644 --- a/core/src/main/java/bisq/core/trade/messages/TraderSignedWitnessMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/messages/TraderSignedWitnessMessage.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.messages; +package bisq.core.trade.protocol.bisq_v1.messages; import bisq.core.account.sign.SignedWitness; diff --git a/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/model/ProcessModel.java similarity index 95% rename from core/src/main/java/bisq/core/trade/protocol/ProcessModel.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/model/ProcessModel.java index b22d478b2d..c321912485 100644 --- a/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/model/ProcessModel.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol; +package bisq.core.trade.protocol.bisq_v1.model; import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.btc.model.RawTransactionInput; @@ -33,10 +33,12 @@ import bisq.core.proto.CoreProtoResolver; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import bisq.core.support.dispute.mediation.mediator.MediatorManager; import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; -import bisq.core.trade.MakerTrade; -import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; -import bisq.core.trade.messages.TradeMessage; +import bisq.core.trade.model.MakerTrade; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.ProtocolModel; +import bisq.core.trade.protocol.Provider; +import bisq.core.trade.protocol.TradeMessage; import bisq.core.trade.statistics.ReferralIdService; import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.user.User; @@ -49,8 +51,6 @@ import bisq.common.crypto.Hash; import bisq.common.crypto.KeyRing; import bisq.common.crypto.PubKeyRing; import bisq.common.proto.ProtoUtil; -import bisq.common.proto.persistable.PersistablePayload; -import bisq.common.taskrunner.Model; import com.google.protobuf.ByteString; @@ -77,19 +77,19 @@ import static com.google.common.base.Preconditions.checkNotNull; /** * This is the base model for the trade protocol. It is persisted with the trade (non transient fields). - * It uses the {@link ProcessModelServiceProvider} for access to domain services. + * It uses the {@link Provider} for access to domain services. */ @Getter @Slf4j -public class ProcessModel implements Model, PersistablePayload { +public class ProcessModel implements ProtocolModel { public static byte[] hashOfPaymentAccountPayload(PaymentAccountPayload paymentAccountPayload) { return Hash.getRipemd160hash(checkNotNull(paymentAccountPayload).toProtoMessage().toByteArray()); } // Transient/Immutable (net set in constructor so they are not final, but at init) - transient private ProcessModelServiceProvider provider; + transient private Provider provider; transient private TradeManager tradeManager; transient private Offer offer; @@ -115,7 +115,6 @@ public class ProcessModel implements Model, PersistablePayload { @Getter transient private Transaction depositTx; - // Persistable Immutable private final TradingPeer tradingPeer; private final String offerId; @@ -181,7 +180,7 @@ public class ProcessModel implements Model, PersistablePayload { this.tradingPeer = tradingPeer != null ? tradingPeer : new TradingPeer(); } - public void applyTransient(ProcessModelServiceProvider provider, + public void applyTransient(Provider provider, TradeManager tradeManager, Offer offer) { this.offer = offer; @@ -260,6 +259,11 @@ public class ProcessModel implements Model, PersistablePayload { public void onComplete() { } + @Override + public TradingPeer getTradePeer() { + return tradingPeer; + } + public void setTakeOfferFeeTx(Transaction takeOfferFeeTx) { this.takeOfferFeeTx = takeOfferFeeTx; takeOfferFeeTxId = takeOfferFeeTx.getTxId().toString(); @@ -289,11 +293,12 @@ public class ProcessModel implements Model, PersistablePayload { return takeOfferFeeTx; } + @Override public NodeAddress getMyNodeAddress() { return getP2PService().getAddress(); } - void setPaymentStartedAckMessage(AckMessage ackMessage) { + public void setPaymentStartedAckMessage(AckMessage ackMessage) { MessageState messageState = ackMessage.isSuccess() ? MessageState.ACKNOWLEDGED : MessageState.FAILED; @@ -307,7 +312,7 @@ public class ProcessModel implements Model, PersistablePayload { } } - void setDepositTxSentAckMessage(AckMessage ackMessage) { + public void setDepositTxSentAckMessage(AckMessage ackMessage) { MessageState messageState = ackMessage.isSuccess() ? MessageState.ACKNOWLEDGED : MessageState.FAILED; @@ -321,10 +326,6 @@ public class ProcessModel implements Model, PersistablePayload { } } - void witnessDebugLog(Trade trade) { - getAccountAgeWitnessService().getAccountAgeWitnessUtils().witnessDebugLog(trade, null); - } - /////////////////////////////////////////////////////////////////////////////////////////// // Delegates diff --git a/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/model/TradingPeer.java similarity index 97% rename from core/src/main/java/bisq/core/trade/protocol/TradingPeer.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/model/TradingPeer.java index ebd37a24ea..192c0d9707 100644 --- a/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/model/TradingPeer.java @@ -15,15 +15,15 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol; +package bisq.core.trade.protocol.bisq_v1.model; import bisq.core.btc.model.RawTransactionInput; import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.proto.CoreProtoResolver; +import bisq.core.trade.protocol.TradePeer; import bisq.common.crypto.PubKeyRing; import bisq.common.proto.ProtoUtil; -import bisq.common.proto.persistable.PersistablePayload; import com.google.protobuf.ByteString; import com.google.protobuf.Message; @@ -44,7 +44,7 @@ import javax.annotation.Nullable; @Slf4j @Getter @Setter -public final class TradingPeer implements PersistablePayload { +public final class TradingPeer implements TradePeer { // Transient/Mutable // Added in v1.2.0 @Setter diff --git a/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/ApplyFilter.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/ApplyFilter.java new file mode 100644 index 0000000000..0057484311 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/ApplyFilter.java @@ -0,0 +1,63 @@ +/* + * 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.core.trade.protocol.bisq_v1.tasks; + +import bisq.core.filter.FilterManager; +import bisq.core.payment.payload.PaymentAccountPayload; +import bisq.core.trade.bisq_v1.TradeUtil; +import bisq.core.trade.model.bisq_v1.Trade; + +import bisq.network.p2p.NodeAddress; + +import bisq.common.taskrunner.TaskRunner; + +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkNotNull; + +@Slf4j +public class ApplyFilter extends TradeTask { + public ApplyFilter(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + NodeAddress nodeAddress = checkNotNull(processModel.getTempTradingPeerNodeAddress()); + @Nullable + PaymentAccountPayload paymentAccountPayload = processModel.getTradePeer().getPaymentAccountPayload(); + + FilterManager filterManager = processModel.getFilterManager(); + + TradeUtil.applyFilter(trade, + filterManager, + nodeAddress, + paymentAccountPayload, + this::complete, + this::failed); + } catch (Throwable t) { + failed(t); + } + } +} + diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/BroadcastPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/BroadcastPayoutTx.java similarity index 97% rename from core/src/main/java/bisq/core/trade/protocol/tasks/BroadcastPayoutTx.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/BroadcastPayoutTx.java index df3b8df2c6..a75613e9c0 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/BroadcastPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/BroadcastPayoutTx.java @@ -15,11 +15,11 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks; +package bisq.core.trade.protocol.bisq_v1.tasks; import bisq.core.btc.exceptions.TxBroadcastException; import bisq.core.btc.wallet.TxBroadcaster; -import bisq.core.trade.Trade; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.common.taskrunner.TaskRunner; diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessPeerPublishedDelayedPayoutTxMessage.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/ProcessPeerPublishedDelayedPayoutTxMessage.java similarity index 92% rename from core/src/main/java/bisq/core/trade/protocol/tasks/ProcessPeerPublishedDelayedPayoutTxMessage.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/ProcessPeerPublishedDelayedPayoutTxMessage.java index aab0bb5a3b..999b98ca29 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessPeerPublishedDelayedPayoutTxMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/ProcessPeerPublishedDelayedPayoutTxMessage.java @@ -15,11 +15,11 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks; +package bisq.core.trade.protocol.bisq_v1.tasks; import bisq.core.btc.wallet.WalletService; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.messages.PeerPublishedDelayedPayoutTxMessage; import bisq.core.util.Validator; import bisq.common.taskrunner.TaskRunner; diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/SendMailboxMessageTask.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/SendMailboxMessageTask.java similarity index 93% rename from core/src/main/java/bisq/core/trade/protocol/tasks/SendMailboxMessageTask.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/SendMailboxMessageTask.java index b4eb80cf3d..7368553a46 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/SendMailboxMessageTask.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/SendMailboxMessageTask.java @@ -15,11 +15,11 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks; +package bisq.core.trade.protocol.bisq_v1.tasks; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.TradeMailboxMessage; -import bisq.core.trade.messages.TradeMessage; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.TradeMessage; +import bisq.core.trade.protocol.bisq_v1.messages.TradeMailboxMessage; import bisq.network.p2p.NodeAddress; import bisq.network.p2p.SendMailboxMessageListener; @@ -57,7 +57,7 @@ public abstract class SendMailboxMessageTask extends TradeTask { processModel.getP2PService().getMailboxMessageService().sendEncryptedMailboxMessage( peersNodeAddress, - processModel.getTradingPeer().getPubKeyRing(), + processModel.getTradePeer().getPubKeyRing(), message, new SendMailboxMessageListener() { @Override diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/SetupPayoutTxListener.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/SetupPayoutTxListener.java similarity index 97% rename from core/src/main/java/bisq/core/trade/protocol/tasks/SetupPayoutTxListener.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/SetupPayoutTxListener.java index 5d925ae41c..090c723d44 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/SetupPayoutTxListener.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/SetupPayoutTxListener.java @@ -15,12 +15,12 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks; +package bisq.core.trade.protocol.bisq_v1.tasks; import bisq.core.btc.listeners.AddressConfidenceListener; import bisq.core.btc.model.AddressEntry; import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.trade.Trade; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.common.UserThread; import bisq.common.taskrunner.TaskRunner; @@ -95,7 +95,7 @@ public abstract class SetupPayoutTxListener extends TradeTask { BtcWalletService.printTx("payoutTx received from network", walletTx); setState(); } else { - log.info("We had the payout tx already set. tradeId={}, state={}", trade.getId(), trade.getState()); + log.info("We had the payout tx already set. tradeId={}, state={}", trade.getId(), trade.getTradeState()); } processModel.getBtcWalletService().resetCoinLockedInMultiSigAddressEntry(trade.getId()); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/TradeTask.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/TradeTask.java similarity index 88% rename from core/src/main/java/bisq/core/trade/protocol/tasks/TradeTask.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/TradeTask.java index 332b571f74..61a2f4a053 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/TradeTask.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/TradeTask.java @@ -15,10 +15,11 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks; +package bisq.core.trade.protocol.bisq_v1.tasks; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.ProcessModel; +import bisq.core.trade.model.TradeModel; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.model.ProcessModel; import bisq.common.taskrunner.Task; import bisq.common.taskrunner.TaskRunner; @@ -26,7 +27,7 @@ import bisq.common.taskrunner.TaskRunner; import lombok.extern.slf4j.Slf4j; @Slf4j -public abstract class TradeTask extends Task { +public abstract class TradeTask extends Task { protected final ProcessModel processModel; protected final Trade trade; diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/VerifyPeersAccountAgeWitness.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/VerifyPeersAccountAgeWitness.java similarity index 93% rename from core/src/main/java/bisq/core/trade/protocol/tasks/VerifyPeersAccountAgeWitness.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/VerifyPeersAccountAgeWitness.java index 68ea2d9eeb..74dcf65309 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/VerifyPeersAccountAgeWitness.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/VerifyPeersAccountAgeWitness.java @@ -15,14 +15,14 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks; +package bisq.core.trade.protocol.bisq_v1.tasks; import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.locale.CurrencyUtil; import bisq.core.offer.Offer; import bisq.core.payment.payload.PaymentAccountPayload; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.TradingPeer; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.model.TradingPeer; import bisq.common.crypto.PubKeyRing; import bisq.common.taskrunner.TaskRunner; @@ -53,7 +53,7 @@ public class VerifyPeersAccountAgeWitness extends TradeTask { } AccountAgeWitnessService accountAgeWitnessService = processModel.getAccountAgeWitnessService(); - TradingPeer tradingPeer = processModel.getTradingPeer(); + TradingPeer tradingPeer = processModel.getTradePeer(); PaymentAccountPayload peersPaymentAccountPayload = checkNotNull(tradingPeer.getPaymentAccountPayload(), "Peers peersPaymentAccountPayload must not be null"); PubKeyRing peersPubKeyRing = checkNotNull(tradingPeer.getPubKeyRing(), "peersPubKeyRing must not be null"); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/arbitration/PublishedDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/arbitration/PublishedDelayedPayoutTx.java similarity index 93% rename from core/src/main/java/bisq/core/trade/protocol/tasks/arbitration/PublishedDelayedPayoutTx.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/arbitration/PublishedDelayedPayoutTx.java index b35e1f53a6..93f0f69536 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/arbitration/PublishedDelayedPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/arbitration/PublishedDelayedPayoutTx.java @@ -15,14 +15,14 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.arbitration; +package bisq.core.trade.protocol.bisq_v1.tasks.arbitration; import bisq.core.btc.exceptions.TxBroadcastException; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.TxBroadcaster; import bisq.core.btc.wallet.WalletService; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/arbitration/SendPeerPublishedDelayedPayoutTxMessage.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/arbitration/SendPeerPublishedDelayedPayoutTxMessage.java similarity index 83% rename from core/src/main/java/bisq/core/trade/protocol/tasks/arbitration/SendPeerPublishedDelayedPayoutTxMessage.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/arbitration/SendPeerPublishedDelayedPayoutTxMessage.java index cdc995c612..c2aa5e6b1d 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/arbitration/SendPeerPublishedDelayedPayoutTxMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/arbitration/SendPeerPublishedDelayedPayoutTxMessage.java @@ -15,12 +15,12 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.arbitration; +package bisq.core.trade.protocol.bisq_v1.tasks.arbitration; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage; -import bisq.core.trade.messages.TradeMailboxMessage; -import bisq.core.trade.protocol.tasks.SendMailboxMessageTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.messages.PeerPublishedDelayedPayoutTxMessage; +import bisq.core.trade.protocol.bisq_v1.messages.TradeMailboxMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.SendMailboxMessageTask; import bisq.common.taskrunner.TaskRunner; diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerFinalizesDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerFinalizesDelayedPayoutTx.java similarity index 86% rename from core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerFinalizesDelayedPayoutTx.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerFinalizesDelayedPayoutTx.java index 30824a14d7..a4f27592eb 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerFinalizesDelayedPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerFinalizesDelayedPayoutTx.java @@ -1,9 +1,9 @@ -package bisq.core.trade.protocol.tasks.buyer; +package bisq.core.trade.protocol.bisq_v1.tasks.buyer; import bisq.core.btc.model.AddressEntry; import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; import bisq.common.util.Utilities; @@ -37,10 +37,10 @@ public class BuyerFinalizesDelayedPayoutTx extends TradeTask { checkArgument(Arrays.equals(buyerMultiSigPubKey, btcWalletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG).getPubKey()), "buyerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id); - byte[] sellerMultiSigPubKey = processModel.getTradingPeer().getMultiSigPubKey(); + byte[] sellerMultiSigPubKey = processModel.getTradePeer().getMultiSigPubKey(); byte[] buyerSignature = processModel.getDelayedPayoutTxSignature(); - byte[] sellerSignature = processModel.getTradingPeer().getDelayedPayoutTxSignature(); + byte[] sellerSignature = processModel.getTradePeer().getDelayedPayoutTxSignature(); Transaction signedDelayedPayoutTx = processModel.getTradeWalletService().finalizeUnconnectedDelayedPayoutTx( preparedDelayedPayoutTx, diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessDelayedPayoutTxSignatureRequest.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerProcessDelayedPayoutTxSignatureRequest.java similarity index 86% rename from core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessDelayedPayoutTxSignatureRequest.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerProcessDelayedPayoutTxSignatureRequest.java index 19cdb957f7..0e0d0c0455 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessDelayedPayoutTxSignatureRequest.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerProcessDelayedPayoutTxSignatureRequest.java @@ -15,11 +15,11 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.buyer; +package bisq.core.trade.protocol.bisq_v1.tasks.buyer; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.messages.DelayedPayoutTxSignatureRequest; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.core.util.Validator; import bisq.common.taskrunner.TaskRunner; @@ -46,7 +46,7 @@ public class BuyerProcessDelayedPayoutTxSignatureRequest extends TradeTask { byte[] delayedPayoutTxAsBytes = checkNotNull(request.getDelayedPayoutTx()); Transaction preparedDelayedPayoutTx = processModel.getBtcWalletService().getTxFromSerializedTx(delayedPayoutTxAsBytes); processModel.setPreparedDelayedPayoutTx(preparedDelayedPayoutTx); - processModel.getTradingPeer().setDelayedPayoutTxSignature(checkNotNull(request.getDelayedPayoutTxSellerSignature())); + processModel.getTradePeer().setDelayedPayoutTxSignature(checkNotNull(request.getDelayedPayoutTxSellerSignature())); // When we receive that message the taker has published the taker fee, so we apply it to the trade. // The takerFeeTx was sent in the first message. It should be part of DelayedPayoutTxSignatureRequest diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessDepositTxAndDelayedPayoutTxMessage.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerProcessDepositTxAndDelayedPayoutTxMessage.java similarity index 85% rename from core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessDepositTxAndDelayedPayoutTxMessage.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerProcessDepositTxAndDelayedPayoutTxMessage.java index 7e6149bc26..0318f61554 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessDepositTxAndDelayedPayoutTxMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerProcessDepositTxAndDelayedPayoutTxMessage.java @@ -15,17 +15,18 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.buyer; +package bisq.core.trade.protocol.bisq_v1.tasks.buyer; import bisq.core.btc.model.AddressEntry; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.WalletService; import bisq.core.payment.payload.PaymentAccountPayload; -import bisq.core.trade.Contract; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage; -import bisq.core.trade.protocol.ProcessModel; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Contract; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.messages.DepositTxAndDelayedPayoutTxMessage; +import bisq.core.trade.protocol.bisq_v1.model.ProcessModel; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; +import bisq.core.util.JsonUtil; import bisq.core.util.Validator; import bisq.common.crypto.Hash; @@ -40,6 +41,8 @@ import java.util.Arrays; import lombok.extern.slf4j.Slf4j; +import static bisq.core.trade.model.bisq_v1.Trade.State.BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG; +import static bisq.core.trade.model.bisq_v1.Trade.State.BUYER_SAW_DEPOSIT_TX_IN_NETWORK; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @@ -84,14 +87,14 @@ public class BuyerProcessDepositTxAndDelayedPayoutTxMessage extends TradeTask { checkArgument(Arrays.equals(sellerPaymentAccountPayloadHash, peersPaymentAccountPayloadHash), "Hash of payment account is invalid"); - processModel.getTradingPeer().setPaymentAccountPayload(sellerPaymentAccountPayload); + processModel.getTradePeer().setPaymentAccountPayload(sellerPaymentAccountPayload); contract.setPaymentAccountPayloads(sellerPaymentAccountPayload, processModel.getPaymentAccountPayload(trade), processModel.getPubKeyRing()); // As we have added the payment accounts we need to update the json. We also update the signature // thought that has less relevance with the changes of 1.7.0 - String contractAsJson = Utilities.objectToJson(contract); + String contractAsJson = JsonUtil.objectToJson(contract); String signature = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), contractAsJson); trade.setContractAsJson(contractAsJson); if (contract.isBuyerMakerAndSellerTaker()) { @@ -105,8 +108,8 @@ public class BuyerProcessDepositTxAndDelayedPayoutTxMessage extends TradeTask { } // If we got already the confirmation we don't want to apply an earlier state - if (trade.getState().ordinal() < Trade.State.BUYER_SAW_DEPOSIT_TX_IN_NETWORK.ordinal()) { - trade.setState(Trade.State.BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG); + if (trade.getTradeState().ordinal() < BUYER_SAW_DEPOSIT_TX_IN_NETWORK.ordinal()) { + trade.setState(BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG); } processModel.getBtcWalletService().swapTradeEntryToAvailableEntry(trade.getId(), diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessPayoutTxPublishedMessage.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerProcessPayoutTxPublishedMessage.java similarity index 92% rename from core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessPayoutTxPublishedMessage.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerProcessPayoutTxPublishedMessage.java index 5b546201af..e01e118fcd 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessPayoutTxPublishedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerProcessPayoutTxPublishedMessage.java @@ -15,14 +15,14 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.buyer; +package bisq.core.trade.protocol.bisq_v1.tasks.buyer; import bisq.core.account.sign.SignedWitness; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.WalletService; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.PayoutTxPublishedMessage; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.messages.PayoutTxPublishedMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.core.util.Validator; import bisq.common.taskrunner.TaskRunner; @@ -44,7 +44,6 @@ public class BuyerProcessPayoutTxPublishedMessage extends TradeTask { protected void run() { try { runInterceptHook(); - log.debug("current trade state " + trade.getState()); PayoutTxPublishedMessage message = (PayoutTxPublishedMessage) processModel.getTradeMessage(); Validator.checkTradeId(processModel.getOfferId(), message); checkNotNull(message); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendCounterCurrencyTransferStartedMessage.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerSendCounterCurrencyTransferStartedMessage.java similarity index 91% rename from core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendCounterCurrencyTransferStartedMessage.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerSendCounterCurrencyTransferStartedMessage.java index b594d646ce..a5bc12088c 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendCounterCurrencyTransferStartedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerSendCounterCurrencyTransferStartedMessage.java @@ -15,15 +15,15 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.buyer; +package bisq.core.trade.protocol.bisq_v1.tasks.buyer; import bisq.core.btc.model.AddressEntry; import bisq.core.network.MessageState; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage; -import bisq.core.trade.messages.TradeMailboxMessage; -import bisq.core.trade.messages.TradeMessage; -import bisq.core.trade.protocol.tasks.SendMailboxMessageTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.TradeMessage; +import bisq.core.trade.protocol.bisq_v1.messages.CounterCurrencyTransferStartedMessage; +import bisq.core.trade.protocol.bisq_v1.messages.TradeMailboxMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.SendMailboxMessageTask; import bisq.common.Timer; import bisq.common.UserThread; @@ -35,6 +35,8 @@ import java.util.concurrent.TimeUnit; import lombok.extern.slf4j.Slf4j; +import static bisq.core.trade.model.bisq_v1.Trade.State.BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG; + /** * We send the seller the BuyerSendCounterCurrencyTransferStartedMessage. * We wait to receive a ACK message back and resend the message @@ -83,8 +85,8 @@ public class BuyerSendCounterCurrencyTransferStartedMessage extends SendMailboxM @Override protected void setStateSent() { - if (trade.getState().ordinal() < Trade.State.BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG.ordinal()) { - trade.setStateIfValidTransitionTo(Trade.State.BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG); + if (trade.getTradeState().ordinal() < BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG.ordinal()) { + trade.setStateIfValidTransitionTo(BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG); } processModel.getTradeManager().requestPersistence(); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendsDelayedPayoutTxSignatureResponse.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerSendsDelayedPayoutTxSignatureResponse.java similarity index 91% rename from core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendsDelayedPayoutTxSignatureResponse.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerSendsDelayedPayoutTxSignatureResponse.java index 5b9702798b..1f56c3dd69 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendsDelayedPayoutTxSignatureResponse.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerSendsDelayedPayoutTxSignatureResponse.java @@ -15,11 +15,11 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.buyer; +package bisq.core.trade.protocol.bisq_v1.tasks.buyer; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.messages.DelayedPayoutTxSignatureResponse; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.network.p2p.NodeAddress; import bisq.network.p2p.SendDirectMessageListener; @@ -59,7 +59,7 @@ public class BuyerSendsDelayedPayoutTxSignatureResponse extends TradeTask { message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); processModel.getP2PService().sendEncryptedDirectMessage( peersNodeAddress, - processModel.getTradingPeer().getPubKeyRing(), + processModel.getTradePeer().getPubKeyRing(), message, new SendDirectMessageListener() { @Override diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendsShareBuyerPaymentAccountMessage.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerSendsShareBuyerPaymentAccountMessage.java similarity index 93% rename from core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendsShareBuyerPaymentAccountMessage.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerSendsShareBuyerPaymentAccountMessage.java index 9076f809c5..92edf3c56d 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendsShareBuyerPaymentAccountMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerSendsShareBuyerPaymentAccountMessage.java @@ -32,15 +32,15 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.buyer; +package bisq.core.trade.protocol.bisq_v1.tasks.buyer; import bisq.core.network.MessageState; import bisq.core.payment.payload.PaymentAccountPayload; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.ShareBuyerPaymentAccountMessage; -import bisq.core.trade.messages.TradeMailboxMessage; -import bisq.core.trade.messages.TradeMessage; -import bisq.core.trade.protocol.tasks.SendMailboxMessageTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.TradeMessage; +import bisq.core.trade.protocol.bisq_v1.messages.ShareBuyerPaymentAccountMessage; +import bisq.core.trade.protocol.bisq_v1.messages.TradeMailboxMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.SendMailboxMessageTask; import bisq.common.Timer; import bisq.common.UserThread; diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSetupDepositTxListener.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerSetupDepositTxListener.java similarity index 98% rename from core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSetupDepositTxListener.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerSetupDepositTxListener.java index 9c6bfa2b74..3c0ea6a6ae 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSetupDepositTxListener.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerSetupDepositTxListener.java @@ -15,13 +15,13 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.buyer; +package bisq.core.trade.protocol.bisq_v1.tasks.buyer; import bisq.core.btc.listeners.AddressConfidenceListener; import bisq.core.btc.model.AddressEntry; import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.common.UserThread; import bisq.common.taskrunner.TaskRunner; diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSetupPayoutTxListener.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerSetupPayoutTxListener.java similarity index 89% rename from core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSetupPayoutTxListener.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerSetupPayoutTxListener.java index c58ce41ef1..6aa2f3a46b 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSetupPayoutTxListener.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerSetupPayoutTxListener.java @@ -15,10 +15,10 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.buyer; +package bisq.core.trade.protocol.bisq_v1.tasks.buyer; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.tasks.SetupPayoutTxListener; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.tasks.SetupPayoutTxListener; import bisq.common.taskrunner.TaskRunner; diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSignPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerSignPayoutTx.java similarity index 78% rename from core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSignPayoutTx.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerSignPayoutTx.java index 70dd1b8074..af96a52355 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSignPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerSignPayoutTx.java @@ -15,13 +15,13 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.buyer; +package bisq.core.trade.protocol.bisq_v1.tasks.buyer; import bisq.core.btc.model.AddressEntry; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.offer.Offer; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; @@ -34,9 +34,6 @@ import java.util.Arrays; import lombok.extern.slf4j.Slf4j; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - @Slf4j public class BuyerSignPayoutTx extends TradeTask { @@ -48,27 +45,27 @@ public class BuyerSignPayoutTx extends TradeTask { protected void run() { try { runInterceptHook(); - Preconditions.checkNotNull(trade.getTradeAmount(), "trade.getTradeAmount() must not be null"); + Preconditions.checkNotNull(trade.getAmount(), "trade.getTradeAmount() must not be null"); Preconditions.checkNotNull(trade.getDepositTx(), "trade.getDepositTx() must not be null"); - Offer offer = checkNotNull(trade.getOffer(), "offer must not be null"); + Offer offer = Preconditions.checkNotNull(trade.getOffer(), "offer must not be null"); BtcWalletService walletService = processModel.getBtcWalletService(); String id = processModel.getOffer().getId(); - Coin buyerPayoutAmount = offer.getBuyerSecurityDeposit().add(trade.getTradeAmount()); + Coin buyerPayoutAmount = offer.getBuyerSecurityDeposit().add(trade.getAmount()); Coin sellerPayoutAmount = offer.getSellerSecurityDeposit(); String buyerPayoutAddressString = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.TRADE_PAYOUT).getAddressString(); - final String sellerPayoutAddressString = processModel.getTradingPeer().getPayoutAddressString(); + final String sellerPayoutAddressString = processModel.getTradePeer().getPayoutAddressString(); DeterministicKey buyerMultiSigKeyPair = walletService.getMultiSigKeyPair(id, processModel.getMyMultiSigPubKey()); byte[] buyerMultiSigPubKey = processModel.getMyMultiSigPubKey(); - checkArgument(Arrays.equals(buyerMultiSigPubKey, - walletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG).getPubKey()), + Preconditions.checkArgument(Arrays.equals(buyerMultiSigPubKey, + walletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG).getPubKey()), "buyerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id); - byte[] sellerMultiSigPubKey = processModel.getTradingPeer().getMultiSigPubKey(); + byte[] sellerMultiSigPubKey = processModel.getTradePeer().getMultiSigPubKey(); byte[] payoutTxSignature = processModel.getTradeWalletService().buyerSignsPayoutTx( trade.getDepositTx(), diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSignsDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerSignsDelayedPayoutTx.java similarity index 92% rename from core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSignsDelayedPayoutTx.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerSignsDelayedPayoutTx.java index cab7fc896b..c2e9e891aa 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSignsDelayedPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerSignsDelayedPayoutTx.java @@ -15,12 +15,12 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.buyer; +package bisq.core.trade.protocol.bisq_v1.tasks.buyer; import bisq.core.btc.model.AddressEntry; import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; @@ -60,7 +60,7 @@ public class BuyerSignsDelayedPayoutTx extends TradeTask { checkArgument(Arrays.equals(buyerMultiSigPubKey, btcWalletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG).getPubKey()), "buyerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id); - byte[] sellerMultiSigPubKey = processModel.getTradingPeer().getMultiSigPubKey(); + byte[] sellerMultiSigPubKey = processModel.getTradePeer().getMultiSigPubKey(); byte[] delayedPayoutTxSignature = processModel.getTradeWalletService().signDelayedPayoutTx( preparedDelayedPayoutTx, preparedDepositTx, diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesFinalDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerVerifiesFinalDelayedPayoutTx.java similarity index 90% rename from core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesFinalDelayedPayoutTx.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerVerifiesFinalDelayedPayoutTx.java index b2a5eaf1b3..0cca5d82e6 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesFinalDelayedPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerVerifiesFinalDelayedPayoutTx.java @@ -15,11 +15,11 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.buyer; +package bisq.core.trade.protocol.bisq_v1.tasks.buyer; -import bisq.core.trade.Trade; -import bisq.core.trade.TradeDataValidation; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.bisq_v1.TradeDataValidation; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesPreparedDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerVerifiesPreparedDelayedPayoutTx.java similarity index 89% rename from core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesPreparedDelayedPayoutTx.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerVerifiesPreparedDelayedPayoutTx.java index 5f259b5574..dc536ffa9c 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesPreparedDelayedPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer/BuyerVerifiesPreparedDelayedPayoutTx.java @@ -15,11 +15,11 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.buyer; +package bisq.core.trade.protocol.bisq_v1.tasks.buyer; -import bisq.core.trade.Trade; -import bisq.core.trade.TradeDataValidation; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.bisq_v1.TradeDataValidation; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; @@ -64,7 +64,7 @@ public class BuyerVerifiesPreparedDelayedPayoutTx extends TradeTask { private boolean isDepositTxNonMalleable() { var buyerInputs = checkNotNull(processModel.getRawTransactionInputs()); - var sellerInputs = checkNotNull(processModel.getTradingPeer().getRawTransactionInputs()); + var sellerInputs = checkNotNull(processModel.getTradePeer().getRawTransactionInputs()); return buyerInputs.stream().allMatch(processModel.getTradeWalletService()::isP2WH) && sellerInputs.stream().allMatch(processModel.getTradeWalletService()::isP2WH); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_maker/BuyerAsMakerCreatesAndSignsDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer_as_maker/BuyerAsMakerCreatesAndSignsDepositTx.java similarity index 90% rename from core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_maker/BuyerAsMakerCreatesAndSignsDepositTx.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer_as_maker/BuyerAsMakerCreatesAndSignsDepositTx.java index 5a7ad8d919..4c4166f6de 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_maker/BuyerAsMakerCreatesAndSignsDepositTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer_as_maker/BuyerAsMakerCreatesAndSignsDepositTx.java @@ -15,16 +15,16 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.buyer_as_maker; +package bisq.core.trade.protocol.bisq_v1.tasks.buyer_as_maker; import bisq.core.btc.model.AddressEntry; import bisq.core.btc.model.PreparedDepositTxAndMakerInputs; import bisq.core.btc.model.RawTransactionInput; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.offer.Offer; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.TradingPeer; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.model.TradingPeer; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; @@ -52,11 +52,11 @@ public class BuyerAsMakerCreatesAndSignsDepositTx extends TradeTask { protected void run() { try { runInterceptHook(); - Coin tradeAmount = checkNotNull(trade.getTradeAmount(), "trade.getTradeAmount() must not be null"); + Coin tradeAmount = checkNotNull(trade.getAmount(), "trade.getTradeAmount() must not be null"); BtcWalletService walletService = processModel.getBtcWalletService(); String id = processModel.getOffer().getId(); - TradingPeer tradingPeer = processModel.getTradingPeer(); + TradingPeer tradingPeer = processModel.getTradePeer(); Offer offer = checkNotNull(trade.getOffer()); Coin makerInputAmount = offer.getBuyerSecurityDeposit(); @@ -67,7 +67,7 @@ public class BuyerAsMakerCreatesAndSignsDepositTx extends TradeTask { walletService.saveAddressEntryList(); Coin msOutputAmount = makerInputAmount - .add(trade.getTxFee()) + .add(trade.getTradeTxFee()) .add(offer.getSellerSecurityDeposit()) .add(tradeAmount); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_maker/BuyerAsMakerSendsInputsForDepositTxResponse.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer_as_maker/BuyerAsMakerSendsInputsForDepositTxResponse.java similarity index 88% rename from core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_maker/BuyerAsMakerSendsInputsForDepositTxResponse.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer_as_maker/BuyerAsMakerSendsInputsForDepositTxResponse.java index 85de45cf24..1989028809 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_maker/BuyerAsMakerSendsInputsForDepositTxResponse.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer_as_maker/BuyerAsMakerSendsInputsForDepositTxResponse.java @@ -15,10 +15,10 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.buyer_as_maker; +package bisq.core.trade.protocol.bisq_v1.tasks.buyer_as_maker; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.tasks.maker.MakerSendsInputsForDepositTxResponse; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.tasks.maker.MakerSendsInputsForDepositTxResponse; import bisq.common.taskrunner.TaskRunner; diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerCreatesDepositTxInputs.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer_as_taker/BuyerAsTakerCreatesDepositTxInputs.java similarity index 90% rename from core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerCreatesDepositTxInputs.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer_as_taker/BuyerAsTakerCreatesDepositTxInputs.java index 0277ca7b76..02966844ed 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerCreatesDepositTxInputs.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer_as_taker/BuyerAsTakerCreatesDepositTxInputs.java @@ -15,11 +15,11 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.buyer_as_taker; +package bisq.core.trade.protocol.bisq_v1.tasks.buyer_as_taker; import bisq.core.btc.model.InputsAndChangeOutput; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; @@ -41,7 +41,7 @@ public class BuyerAsTakerCreatesDepositTxInputs extends TradeTask { try { runInterceptHook(); - Coin txFee = trade.getTxFee(); + Coin txFee = trade.getTradeTxFee(); Coin takerInputAmount = checkNotNull(trade.getOffer()).getBuyerSecurityDeposit() .add(txFee) .add(txFee); // 2 times the fee as we need it for payout tx as well diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSendsDepositTxMessage.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer_as_taker/BuyerAsTakerSendsDepositTxMessage.java similarity index 91% rename from core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSendsDepositTxMessage.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer_as_taker/BuyerAsTakerSendsDepositTxMessage.java index ab03ece48a..a28bde602e 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSendsDepositTxMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer_as_taker/BuyerAsTakerSendsDepositTxMessage.java @@ -15,11 +15,11 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.buyer_as_taker; +package bisq.core.trade.protocol.bisq_v1.tasks.buyer_as_taker; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.DepositTxMessage; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.messages.DepositTxMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.network.p2p.NodeAddress; import bisq.network.p2p.SendDirectMessageListener; @@ -53,7 +53,7 @@ public class BuyerAsTakerSendsDepositTxMessage extends TradeTask { message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); processModel.getP2PService().sendEncryptedDirectMessage( peersNodeAddress, - processModel.getTradingPeer().getPubKeyRing(), + processModel.getTradePeer().getPubKeyRing(), message, new SendDirectMessageListener() { @Override diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSignsDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer_as_taker/BuyerAsTakerSignsDepositTx.java similarity index 88% rename from core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSignsDepositTx.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer_as_taker/BuyerAsTakerSignsDepositTx.java index c195059b19..5da2dc3779 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSignsDepositTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/buyer_as_taker/BuyerAsTakerSignsDepositTx.java @@ -15,15 +15,15 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.buyer_as_taker; +package bisq.core.trade.protocol.bisq_v1.tasks.buyer_as_taker; import bisq.core.btc.model.AddressEntry; import bisq.core.btc.model.RawTransactionInput; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.offer.Offer; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.TradingPeer; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.model.TradingPeer; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.common.crypto.Hash; import bisq.common.taskrunner.TaskRunner; @@ -69,15 +69,15 @@ public class BuyerAsTakerSignsDepositTx extends TradeTask { AddressEntry buyerMultiSigAddressEntry = addressEntryOptional.get(); Coin buyerInput = Coin.valueOf(buyerInputs.stream().mapToLong(input -> input.value).sum()); - Coin multiSigValue = buyerInput.subtract(trade.getTxFee().multiply(2)); + Coin multiSigValue = buyerInput.subtract(trade.getTradeTxFee().multiply(2)); processModel.getBtcWalletService().setCoinLockedInMultiSigAddressEntry(buyerMultiSigAddressEntry, multiSigValue.value); walletService.saveAddressEntryList(); Offer offer = trade.getOffer(); - Coin msOutputAmount = offer.getBuyerSecurityDeposit().add(offer.getSellerSecurityDeposit()).add(trade.getTxFee()) - .add(checkNotNull(trade.getTradeAmount())); + Coin msOutputAmount = offer.getBuyerSecurityDeposit().add(offer.getSellerSecurityDeposit()).add(trade.getTradeTxFee()) + .add(checkNotNull(trade.getAmount())); - TradingPeer tradingPeer = processModel.getTradingPeer(); + TradingPeer tradingPeer = processModel.getTradePeer(); byte[] buyerMultiSigPubKey = processModel.getMyMultiSigPubKey(); checkArgument(Arrays.equals(buyerMultiSigPubKey, buyerMultiSigAddressEntry.getPubKey()), "buyerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateAndSignContract.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/maker/MakerCreateAndSignContract.java similarity index 82% rename from core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateAndSignContract.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/maker/MakerCreateAndSignContract.java index 1801d7b1ed..d933a01060 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateAndSignContract.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/maker/MakerCreateAndSignContract.java @@ -15,23 +15,25 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.maker; +package bisq.core.trade.protocol.bisq_v1.tasks.maker; import bisq.core.btc.model.AddressEntry; import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.trade.BuyerAsMakerTrade; -import bisq.core.trade.Contract; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.ProcessModel; -import bisq.core.trade.protocol.TradingPeer; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.offer.Offer; +import bisq.core.offer.bisq_v1.OfferPayload; +import bisq.core.trade.model.bisq_v1.BuyerAsMakerTrade; +import bisq.core.trade.model.bisq_v1.Contract; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.model.ProcessModel; +import bisq.core.trade.protocol.bisq_v1.model.TradingPeer; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; +import bisq.core.util.JsonUtil; import bisq.network.p2p.NodeAddress; import bisq.common.crypto.Hash; import bisq.common.crypto.Sig; import bisq.common.taskrunner.TaskRunner; -import bisq.common.util.Utilities; import lombok.extern.slf4j.Slf4j; @@ -49,15 +51,15 @@ public class MakerCreateAndSignContract extends TradeTask { runInterceptHook(); String takerFeeTxId = checkNotNull(processModel.getTakeOfferFeeTxId()); - TradingPeer taker = processModel.getTradingPeer(); + TradingPeer taker = processModel.getTradePeer(); boolean isBuyerMakerAndSellerTaker = trade instanceof BuyerAsMakerTrade; NodeAddress buyerNodeAddress = isBuyerMakerAndSellerTaker ? processModel.getMyNodeAddress() : processModel.getTempTradingPeerNodeAddress(); NodeAddress sellerNodeAddress = isBuyerMakerAndSellerTaker ? processModel.getTempTradingPeerNodeAddress() : processModel.getMyNodeAddress(); BtcWalletService walletService = processModel.getBtcWalletService(); - String id = processModel.getOffer().getId(); - + Offer offer = processModel.getOffer(); + String id = offer.getId(); AddressEntry makerAddressEntry = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG); byte[] makerMultiSigPubKey = makerAddressEntry.getPubKey(); @@ -67,11 +69,12 @@ public class MakerCreateAndSignContract extends TradeTask { byte[] hashOfTakersPaymentAccountPayload = taker.getHashOfPaymentAccountPayload(); String makersPaymentMethodId = checkNotNull(processModel.getPaymentAccountPayload(trade)).getPaymentMethodId(); String takersPaymentMethodId = checkNotNull(taker.getPaymentMethodId()); + OfferPayload offerPayload = offer.getOfferPayload().orElseThrow(); Contract contract = new Contract( - processModel.getOffer().getOfferPayload(), - checkNotNull(trade.getTradeAmount()).value, - trade.getTradePrice().getValue(), + offerPayload, + checkNotNull(trade.getAmount()).value, + trade.getPrice().getValue(), takerFeeTxId, buyerNodeAddress, sellerNodeAddress, @@ -94,7 +97,7 @@ public class MakerCreateAndSignContract extends TradeTask { makersPaymentMethodId, takersPaymentMethodId ); - String contractAsJson = Utilities.objectToJson(contract); + String contractAsJson = JsonUtil.objectToJson(contract); String signature = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), contractAsJson); trade.setContract(contract); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessesInputsForDepositTxRequest.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/maker/MakerProcessesInputsForDepositTxRequest.java similarity index 91% rename from core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessesInputsForDepositTxRequest.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/maker/MakerProcessesInputsForDepositTxRequest.java index e112627aba..c2ac35f6d9 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessesInputsForDepositTxRequest.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/maker/MakerProcessesInputsForDepositTxRequest.java @@ -15,15 +15,15 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.maker; +package bisq.core.trade.protocol.bisq_v1.tasks.maker; import bisq.core.exceptions.TradePriceOutOfToleranceException; import bisq.core.offer.Offer; import bisq.core.support.dispute.mediation.mediator.Mediator; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.InputsForDepositTxRequest; -import bisq.core.trade.protocol.TradingPeer; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.messages.InputsForDepositTxRequest; +import bisq.core.trade.protocol.bisq_v1.model.TradingPeer; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.core.user.User; import bisq.network.p2p.NodeAddress; @@ -57,7 +57,7 @@ public class MakerProcessesInputsForDepositTxRequest extends TradeTask { checkNotNull(request); checkTradeId(processModel.getOfferId(), request); - TradingPeer tradingPeer = processModel.getTradingPeer(); + TradingPeer tradingPeer = processModel.getTradePeer(); // 1.7.0: We do not expect the payment account anymore but in case peer has not updated we still process it. Optional.ofNullable(request.getTakerPaymentAccountPayload()) @@ -105,7 +105,7 @@ public class MakerProcessesInputsForDepositTxRequest extends TradeTask { try { long takersTradePrice = request.getTradePrice(); offer.checkTradePriceTolerance(takersTradePrice); - trade.setTradePrice(takersTradePrice); + trade.setPriceAsLong(takersTradePrice); } catch (TradePriceOutOfToleranceException e) { failed(e.getMessage()); } catch (Throwable e2) { @@ -113,7 +113,7 @@ public class MakerProcessesInputsForDepositTxRequest extends TradeTask { } checkArgument(request.getTradeAmount() > 0); - trade.setTradeAmount(Coin.valueOf(request.getTradeAmount())); + trade.setAmount(Coin.valueOf(request.getTradeAmount())); trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress()); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerRemovesOpenOffer.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/maker/MakerRemovesOpenOffer.java similarity index 89% rename from core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerRemovesOpenOffer.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/maker/MakerRemovesOpenOffer.java index 6fe8c58769..843f2d5d4d 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerRemovesOpenOffer.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/maker/MakerRemovesOpenOffer.java @@ -15,10 +15,10 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.maker; +package bisq.core.trade.protocol.bisq_v1.tasks.maker; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsInputsForDepositTxResponse.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/maker/MakerSendsInputsForDepositTxResponse.java similarity index 94% rename from core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsInputsForDepositTxResponse.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/maker/MakerSendsInputsForDepositTxResponse.java index 44b338fffe..0233e17799 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsInputsForDepositTxResponse.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/maker/MakerSendsInputsForDepositTxResponse.java @@ -15,14 +15,14 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.maker; +package bisq.core.trade.protocol.bisq_v1.tasks.maker; import bisq.core.btc.model.AddressEntry; import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.InputsForDepositTxResponse; -import bisq.core.trade.protocol.ProcessModel; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.messages.InputsForDepositTxResponse; +import bisq.core.trade.protocol.bisq_v1.model.ProcessModel; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.network.p2p.NodeAddress; import bisq.network.p2p.SendDirectMessageListener; @@ -103,7 +103,7 @@ public abstract class MakerSendsInputsForDepositTxResponse extends TradeTask { message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); processModel.getP2PService().sendEncryptedDirectMessage( peersNodeAddress, - processModel.getTradingPeer().getPubKeyRing(), + processModel.getTradePeer().getPubKeyRing(), message, new SendDirectMessageListener() { @Override diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSetsLockTime.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/maker/MakerSetsLockTime.java similarity index 89% rename from core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSetsLockTime.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/maker/MakerSetsLockTime.java index fde4359bec..089bbec587 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSetsLockTime.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/maker/MakerSetsLockTime.java @@ -15,11 +15,11 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.maker; +package bisq.core.trade.protocol.bisq_v1.tasks.maker; import bisq.core.btc.wallet.Restrictions; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.common.config.Config; import bisq.common.taskrunner.TaskRunner; @@ -41,7 +41,7 @@ public class MakerSetsLockTime extends TradeTask { // For regtest dev environment we use 5 blocks int delay = Config.baseCurrencyNetwork().isRegtest() ? 5 : - Restrictions.getLockTime(processModel.getOffer().getPaymentMethod().isAsset()); + Restrictions.getLockTime(processModel.getOffer().getPaymentMethod().isBlockchain()); long lockTime = processModel.getBtcWalletService().getBestChainHeight() + delay; log.info("lockTime={}, delay={}", lockTime, delay); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerVerifyTakerFeePayment.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/maker/MakerVerifyTakerFeePayment.java similarity index 89% rename from core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerVerifyTakerFeePayment.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/maker/MakerVerifyTakerFeePayment.java index 0c8a5fa67b..6a70e2b61c 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerVerifyTakerFeePayment.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/maker/MakerVerifyTakerFeePayment.java @@ -15,10 +15,10 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.maker; +package bisq.core.trade.protocol.bisq_v1.tasks.maker; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/BroadcastMediatedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/mediation/BroadcastMediatedPayoutTx.java similarity index 89% rename from core/src/main/java/bisq/core/trade/protocol/tasks/mediation/BroadcastMediatedPayoutTx.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/mediation/BroadcastMediatedPayoutTx.java index 7ebfa20ce5..cbdcbc1bca 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/BroadcastMediatedPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/mediation/BroadcastMediatedPayoutTx.java @@ -15,11 +15,11 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.mediation; +package bisq.core.trade.protocol.bisq_v1.tasks.mediation; import bisq.core.support.dispute.mediation.MediationResultState; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.tasks.BroadcastPayoutTx; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.tasks.BroadcastPayoutTx; import bisq.common.taskrunner.TaskRunner; diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/FinalizeMediatedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/mediation/FinalizeMediatedPayoutTx.java similarity index 90% rename from core/src/main/java/bisq/core/trade/protocol/tasks/mediation/FinalizeMediatedPayoutTx.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/mediation/FinalizeMediatedPayoutTx.java index 875b78f7af..97f364d9ef 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/FinalizeMediatedPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/mediation/FinalizeMediatedPayoutTx.java @@ -15,15 +15,15 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.mediation; +package bisq.core.trade.protocol.bisq_v1.tasks.mediation; import bisq.core.btc.model.AddressEntry; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.offer.Offer; -import bisq.core.trade.Contract; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.TradingPeer; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Contract; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.model.TradingPeer; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; @@ -52,13 +52,13 @@ public class FinalizeMediatedPayoutTx extends TradeTask { Transaction depositTx = checkNotNull(trade.getDepositTx()); String tradeId = trade.getId(); - TradingPeer tradingPeer = processModel.getTradingPeer(); + TradingPeer tradingPeer = processModel.getTradePeer(); BtcWalletService walletService = processModel.getBtcWalletService(); Offer offer = checkNotNull(trade.getOffer(), "offer must not be null"); - Coin tradeAmount = checkNotNull(trade.getTradeAmount(), "tradeAmount must not be null"); + Coin tradeAmount = checkNotNull(trade.getAmount(), "tradeAmount must not be null"); Contract contract = checkNotNull(trade.getContract(), "contract must not be null"); - checkNotNull(trade.getTradeAmount(), "trade.getTradeAmount() must not be null"); + checkNotNull(trade.getAmount(), "trade.getTradeAmount() must not be null"); byte[] mySignature = checkNotNull(processModel.getMediatedPayoutTxSignature(), diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/ProcessMediatedPayoutSignatureMessage.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/mediation/ProcessMediatedPayoutSignatureMessage.java similarity index 83% rename from core/src/main/java/bisq/core/trade/protocol/tasks/mediation/ProcessMediatedPayoutSignatureMessage.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/mediation/ProcessMediatedPayoutSignatureMessage.java index ba0aad7204..b04324eef7 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/ProcessMediatedPayoutSignatureMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/mediation/ProcessMediatedPayoutSignatureMessage.java @@ -15,12 +15,12 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.mediation; +package bisq.core.trade.protocol.bisq_v1.tasks.mediation; import bisq.core.support.dispute.mediation.MediationResultState; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.MediatedPayoutTxSignatureMessage; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.messages.MediatedPayoutTxSignatureMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.core.util.Validator; import bisq.common.taskrunner.TaskRunner; @@ -39,12 +39,11 @@ public class ProcessMediatedPayoutSignatureMessage extends TradeTask { protected void run() { try { runInterceptHook(); - log.debug("current trade state " + trade.getState()); MediatedPayoutTxSignatureMessage message = (MediatedPayoutTxSignatureMessage) processModel.getTradeMessage(); Validator.checkTradeId(processModel.getOfferId(), message); checkNotNull(message); - processModel.getTradingPeer().setMediatedPayoutTxSignature(checkNotNull(message.getTxSignature())); + processModel.getTradePeer().setMediatedPayoutTxSignature(checkNotNull(message.getTxSignature())); // update to the latest peer address of our peer if the message is correct trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress()); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/ProcessMediatedPayoutTxPublishedMessage.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/mediation/ProcessMediatedPayoutTxPublishedMessage.java similarity index 93% rename from core/src/main/java/bisq/core/trade/protocol/tasks/mediation/ProcessMediatedPayoutTxPublishedMessage.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/mediation/ProcessMediatedPayoutTxPublishedMessage.java index cb06ed645e..08a2b65c36 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/ProcessMediatedPayoutTxPublishedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/mediation/ProcessMediatedPayoutTxPublishedMessage.java @@ -15,14 +15,13 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.mediation; +package bisq.core.trade.protocol.bisq_v1.tasks.mediation; -import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.WalletService; import bisq.core.support.dispute.mediation.MediationResultState; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.MediatedPayoutTxPublishedMessage; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.messages.MediatedPayoutTxPublishedMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.core.util.Validator; import bisq.common.UserThread; diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SendMediatedPayoutSignatureMessage.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/mediation/SendMediatedPayoutSignatureMessage.java similarity index 94% rename from core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SendMediatedPayoutSignatureMessage.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/mediation/SendMediatedPayoutSignatureMessage.java index 51ff7267f9..e29b85267d 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SendMediatedPayoutSignatureMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/mediation/SendMediatedPayoutSignatureMessage.java @@ -15,13 +15,13 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.mediation; +package bisq.core.trade.protocol.bisq_v1.tasks.mediation; import bisq.core.support.dispute.mediation.MediationResultState; -import bisq.core.trade.Contract; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.MediatedPayoutTxSignatureMessage; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Contract; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.messages.MediatedPayoutTxSignatureMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.network.p2p.NodeAddress; import bisq.network.p2p.P2PService; diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SendMediatedPayoutTxPublishedMessage.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/mediation/SendMediatedPayoutTxPublishedMessage.java similarity index 89% rename from core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SendMediatedPayoutTxPublishedMessage.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/mediation/SendMediatedPayoutTxPublishedMessage.java index f03baace1b..a29217d4f1 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SendMediatedPayoutTxPublishedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/mediation/SendMediatedPayoutTxPublishedMessage.java @@ -15,13 +15,13 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.mediation; +package bisq.core.trade.protocol.bisq_v1.tasks.mediation; import bisq.core.support.dispute.mediation.MediationResultState; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.MediatedPayoutTxPublishedMessage; -import bisq.core.trade.messages.TradeMailboxMessage; -import bisq.core.trade.protocol.tasks.SendMailboxMessageTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.messages.MediatedPayoutTxPublishedMessage; +import bisq.core.trade.protocol.bisq_v1.messages.TradeMailboxMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.SendMailboxMessageTask; import bisq.common.taskrunner.TaskRunner; diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SetupMediatedPayoutTxListener.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/mediation/SetupMediatedPayoutTxListener.java similarity index 90% rename from core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SetupMediatedPayoutTxListener.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/mediation/SetupMediatedPayoutTxListener.java index dc0b60b314..86cc22fb8e 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SetupMediatedPayoutTxListener.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/mediation/SetupMediatedPayoutTxListener.java @@ -15,11 +15,11 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.mediation; +package bisq.core.trade.protocol.bisq_v1.tasks.mediation; import bisq.core.support.dispute.mediation.MediationResultState; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.tasks.SetupPayoutTxListener; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.tasks.SetupPayoutTxListener; import bisq.common.taskrunner.TaskRunner; diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SignMediatedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/mediation/SignMediatedPayoutTx.java similarity index 91% rename from core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SignMediatedPayoutTx.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/mediation/SignMediatedPayoutTx.java index 6699d83200..9b182fd037 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SignMediatedPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/mediation/SignMediatedPayoutTx.java @@ -15,15 +15,15 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.mediation; +package bisq.core.trade.protocol.bisq_v1.tasks.mediation; import bisq.core.btc.model.AddressEntry; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.offer.Offer; -import bisq.core.trade.Contract; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.TradingPeer; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Contract; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.model.TradingPeer; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; @@ -50,7 +50,7 @@ public class SignMediatedPayoutTx extends TradeTask { try { runInterceptHook(); - TradingPeer tradingPeer = processModel.getTradingPeer(); + TradingPeer tradingPeer = processModel.getTradePeer(); if (processModel.getMediatedPayoutTxSignature() != null) { log.warn("processModel.getTxSignatureFromMediation is already set"); } @@ -59,7 +59,7 @@ public class SignMediatedPayoutTx extends TradeTask { BtcWalletService walletService = processModel.getBtcWalletService(); Transaction depositTx = checkNotNull(trade.getDepositTx(), "trade.getDepositTx() must not be null"); Offer offer = checkNotNull(trade.getOffer(), "offer must not be null"); - Coin tradeAmount = checkNotNull(trade.getTradeAmount(), "tradeAmount must not be null"); + Coin tradeAmount = checkNotNull(trade.getAmount(), "tradeAmount must not be null"); Contract contract = checkNotNull(trade.getContract(), "contract must not be null"); Coin totalPayoutAmount = offer.getBuyerSecurityDeposit().add(tradeAmount).add(offer.getSellerSecurityDeposit()); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerBroadcastPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerBroadcastPayoutTx.java similarity index 88% rename from core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerBroadcastPayoutTx.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerBroadcastPayoutTx.java index d0a0f6c20d..238056c346 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerBroadcastPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerBroadcastPayoutTx.java @@ -15,10 +15,10 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.seller; +package bisq.core.trade.protocol.bisq_v1.tasks.seller; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.tasks.BroadcastPayoutTx; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.tasks.BroadcastPayoutTx; import bisq.common.taskrunner.TaskRunner; diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerCreatesDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerCreatesDelayedPayoutTx.java similarity index 89% rename from core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerCreatesDelayedPayoutTx.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerCreatesDelayedPayoutTx.java index a466f11264..9b8e21b957 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerCreatesDelayedPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerCreatesDelayedPayoutTx.java @@ -15,13 +15,13 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.seller; +package bisq.core.trade.protocol.bisq_v1.tasks.seller; import bisq.core.btc.wallet.TradeWalletService; import bisq.core.dao.governance.param.Param; -import bisq.core.trade.Trade; -import bisq.core.trade.TradeDataValidation; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.bisq_v1.TradeDataValidation; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; @@ -45,7 +45,7 @@ public class SellerCreatesDelayedPayoutTx extends TradeTask { runInterceptHook(); String donationAddressString = processModel.getDaoFacade().getParamValue(Param.RECIPIENT_BTC_ADDRESS); - Coin minerFee = trade.getTxFee(); + Coin minerFee = trade.getTradeTxFee(); TradeWalletService tradeWalletService = processModel.getTradeWalletService(); Transaction depositTx = checkNotNull(processModel.getDepositTx()); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerFinalizesDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerFinalizesDelayedPayoutTx.java similarity index 88% rename from core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerFinalizesDelayedPayoutTx.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerFinalizesDelayedPayoutTx.java index 2683e52602..e3150f6c5d 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerFinalizesDelayedPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerFinalizesDelayedPayoutTx.java @@ -15,12 +15,12 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.seller; +package bisq.core.trade.protocol.bisq_v1.tasks.seller; import bisq.core.btc.model.AddressEntry; import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; import bisq.common.util.Utilities; @@ -49,13 +49,13 @@ public class SellerFinalizesDelayedPayoutTx extends TradeTask { BtcWalletService btcWalletService = processModel.getBtcWalletService(); String id = processModel.getOffer().getId(); - byte[] buyerMultiSigPubKey = processModel.getTradingPeer().getMultiSigPubKey(); + byte[] buyerMultiSigPubKey = processModel.getTradePeer().getMultiSigPubKey(); byte[] sellerMultiSigPubKey = processModel.getMyMultiSigPubKey(); checkArgument(Arrays.equals(sellerMultiSigPubKey, btcWalletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG).getPubKey()), "sellerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id); - byte[] buyerSignature = processModel.getTradingPeer().getDelayedPayoutTxSignature(); + byte[] buyerSignature = processModel.getTradePeer().getDelayedPayoutTxSignature(); byte[] sellerSignature = processModel.getDelayedPayoutTxSignature(); Transaction signedDelayedPayoutTx = processModel.getTradeWalletService().finalizeDelayedPayoutTx( diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java similarity index 87% rename from core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java index 7dc721d935..43f2b8b3b0 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java @@ -15,12 +15,12 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.seller; +package bisq.core.trade.protocol.bisq_v1.tasks.seller; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage; -import bisq.core.trade.protocol.TradingPeer; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.messages.CounterCurrencyTransferStartedMessage; +import bisq.core.trade.protocol.bisq_v1.model.TradingPeer; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.core.util.Validator; import bisq.common.taskrunner.TaskRunner; @@ -39,12 +39,11 @@ public class SellerProcessCounterCurrencyTransferStartedMessage extends TradeTas protected void run() { try { runInterceptHook(); - log.debug("current trade state " + trade.getState()); CounterCurrencyTransferStartedMessage message = (CounterCurrencyTransferStartedMessage) processModel.getTradeMessage(); Validator.checkTradeId(processModel.getOfferId(), message); checkNotNull(message); - TradingPeer tradingPeer = processModel.getTradingPeer(); + TradingPeer tradingPeer = processModel.getTradePeer(); tradingPeer.setPayoutAddressString(Validator.nonEmptyStringOf(message.getBuyerPayoutAddress())); tradingPeer.setSignature(checkNotNull(message.getBuyerSignature())); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessDelayedPayoutTxSignatureResponse.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerProcessDelayedPayoutTxSignatureResponse.java similarity index 84% rename from core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessDelayedPayoutTxSignatureResponse.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerProcessDelayedPayoutTxSignatureResponse.java index 33d60965a4..0b1360859d 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessDelayedPayoutTxSignatureResponse.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerProcessDelayedPayoutTxSignatureResponse.java @@ -15,11 +15,11 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.seller; +package bisq.core.trade.protocol.bisq_v1.tasks.seller; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.messages.DelayedPayoutTxSignatureResponse; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; @@ -42,7 +42,7 @@ public class SellerProcessDelayedPayoutTxSignatureResponse extends TradeTask { checkNotNull(response); checkTradeId(processModel.getOfferId(), response); - processModel.getTradingPeer().setDelayedPayoutTxSignature(checkNotNull(response.getDelayedPayoutTxBuyerSignature())); + processModel.getTradePeer().setDelayedPayoutTxSignature(checkNotNull(response.getDelayedPayoutTxBuyerSignature())); processModel.getTradeWalletService().sellerAddsBuyerWitnessesToDepositTx( processModel.getDepositTx(), diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessShareBuyerPaymentAccountMessage.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerProcessShareBuyerPaymentAccountMessage.java similarity index 87% rename from core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessShareBuyerPaymentAccountMessage.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerProcessShareBuyerPaymentAccountMessage.java index dc20761aae..1659f1fde6 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessShareBuyerPaymentAccountMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerProcessShareBuyerPaymentAccountMessage.java @@ -32,18 +32,18 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.seller; +package bisq.core.trade.protocol.bisq_v1.tasks.seller; import bisq.core.payment.payload.PaymentAccountPayload; -import bisq.core.trade.Contract; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.ShareBuyerPaymentAccountMessage; -import bisq.core.trade.protocol.ProcessModel; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Contract; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.messages.ShareBuyerPaymentAccountMessage; +import bisq.core.trade.protocol.bisq_v1.model.ProcessModel; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; +import bisq.core.util.JsonUtil; import bisq.common.crypto.Sig; import bisq.common.taskrunner.TaskRunner; -import bisq.common.util.Utilities; import java.util.Arrays; @@ -75,14 +75,14 @@ public class SellerProcessShareBuyerPaymentAccountMessage extends TradeTask { checkArgument(Arrays.equals(buyerPaymentAccountPayloadHash, peersPaymentAccountPayloadHash), "Hash of payment account is invalid"); - processModel.getTradingPeer().setPaymentAccountPayload(buyerPaymentAccountPayload); + processModel.getTradePeer().setPaymentAccountPayload(buyerPaymentAccountPayload); checkNotNull(contract).setPaymentAccountPayloads(buyerPaymentAccountPayload, processModel.getPaymentAccountPayload(trade), processModel.getPubKeyRing()); // As we have added the payment accounts we need to update the json. We also update the signature // thought that has less relevance with the changes of 1.7.0 - String contractAsJson = Utilities.objectToJson(contract); + String contractAsJson = JsonUtil.objectToJson(contract); String signature = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), contractAsJson); trade.setContractAsJson(contractAsJson); if (contract.isBuyerMakerAndSellerTaker()) { diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerPublishesDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerPublishesDepositTx.java similarity index 94% rename from core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerPublishesDepositTx.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerPublishesDepositTx.java index 1afae6dad3..4be5e257b9 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerPublishesDepositTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerPublishesDepositTx.java @@ -15,13 +15,13 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.seller; +package bisq.core.trade.protocol.bisq_v1.tasks.seller; import bisq.core.btc.exceptions.TxBroadcastException; import bisq.core.btc.model.AddressEntry; import bisq.core.btc.wallet.TxBroadcaster; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerPublishesTradeStatistics.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerPublishesTradeStatistics.java similarity index 91% rename from core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerPublishesTradeStatistics.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerPublishesTradeStatistics.java index 89b6ac5a14..d3bff19ef0 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerPublishesTradeStatistics.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerPublishesTradeStatistics.java @@ -15,10 +15,10 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.seller; +package bisq.core.trade.protocol.bisq_v1.tasks.seller; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.core.trade.statistics.TradeStatistics3; import bisq.network.p2p.network.TorNetworkNode; @@ -50,7 +50,7 @@ public class SellerPublishesTradeStatistics extends TradeTask { // The peer as buyer does not publish anymore with v.1.4.0 (where Capability.TRADE_STATISTICS_3 was added) String referralId = processModel.getReferralIdService().getOptionalReferralId().orElse(null); - boolean isTorNetworkNode = model.getProcessModel().getP2PService().getNetworkNode() instanceof TorNetworkNode; + boolean isTorNetworkNode = processModel.getP2PService().getNetworkNode() instanceof TorNetworkNode; TradeStatistics3 tradeStatistics = TradeStatistics3.from(trade, referralId, isTorNetworkNode); if (tradeStatistics.isValid()) { log.info("Publishing trade statistics"); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendDelayedPayoutTxSignatureRequest.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerSendDelayedPayoutTxSignatureRequest.java similarity index 91% rename from core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendDelayedPayoutTxSignatureRequest.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerSendDelayedPayoutTxSignatureRequest.java index 58cfe2da58..bde5555022 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendDelayedPayoutTxSignatureRequest.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerSendDelayedPayoutTxSignatureRequest.java @@ -15,11 +15,11 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.seller; +package bisq.core.trade.protocol.bisq_v1.tasks.seller; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.messages.DelayedPayoutTxSignatureRequest; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.network.p2p.NodeAddress; import bisq.network.p2p.SendDirectMessageListener; @@ -60,7 +60,7 @@ public class SellerSendDelayedPayoutTxSignatureRequest extends TradeTask { message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); processModel.getP2PService().sendEncryptedDirectMessage( peersNodeAddress, - processModel.getTradingPeer().getPubKeyRing(), + processModel.getTradePeer().getPubKeyRing(), message, new SendDirectMessageListener() { @Override diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendPayoutTxPublishedMessage.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerSendPayoutTxPublishedMessage.java similarity index 92% rename from core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendPayoutTxPublishedMessage.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerSendPayoutTxPublishedMessage.java index be6990835d..ae65e10da6 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendPayoutTxPublishedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerSendPayoutTxPublishedMessage.java @@ -15,14 +15,14 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.seller; +package bisq.core.trade.protocol.bisq_v1.tasks.seller; import bisq.core.account.sign.SignedWitness; import bisq.core.account.witness.AccountAgeWitnessService; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.PayoutTxPublishedMessage; -import bisq.core.trade.messages.TradeMailboxMessage; -import bisq.core.trade.protocol.tasks.SendMailboxMessageTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.messages.PayoutTxPublishedMessage; +import bisq.core.trade.protocol.bisq_v1.messages.TradeMailboxMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.SendMailboxMessageTask; import bisq.common.taskrunner.TaskRunner; diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendsDepositTxAndDelayedPayoutTxMessage.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerSendsDepositTxAndDelayedPayoutTxMessage.java similarity index 95% rename from core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendsDepositTxAndDelayedPayoutTxMessage.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerSendsDepositTxAndDelayedPayoutTxMessage.java index b20b9654c9..dc08270b6e 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendsDepositTxAndDelayedPayoutTxMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerSendsDepositTxAndDelayedPayoutTxMessage.java @@ -15,15 +15,15 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.seller; +package bisq.core.trade.protocol.bisq_v1.tasks.seller; import bisq.core.network.MessageState; import bisq.core.payment.payload.PaymentAccountPayload; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage; -import bisq.core.trade.messages.TradeMailboxMessage; -import bisq.core.trade.messages.TradeMessage; -import bisq.core.trade.protocol.tasks.SendMailboxMessageTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.TradeMessage; +import bisq.core.trade.protocol.bisq_v1.messages.DepositTxAndDelayedPayoutTxMessage; +import bisq.core.trade.protocol.bisq_v1.messages.TradeMailboxMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.SendMailboxMessageTask; import bisq.common.Timer; import bisq.common.UserThread; diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignAndFinalizePayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerSignAndFinalizePayoutTx.java similarity index 91% rename from core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignAndFinalizePayoutTx.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerSignAndFinalizePayoutTx.java index d6b0babc65..eb004b7446 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignAndFinalizePayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerSignAndFinalizePayoutTx.java @@ -15,14 +15,14 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.seller; +package bisq.core.trade.protocol.bisq_v1.tasks.seller; import bisq.core.btc.model.AddressEntry; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.offer.Offer; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.TradingPeer; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.model.TradingPeer; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; @@ -49,16 +49,16 @@ public class SellerSignAndFinalizePayoutTx extends TradeTask { try { runInterceptHook(); - checkNotNull(trade.getTradeAmount(), "trade.getTradeAmount() must not be null"); + checkNotNull(trade.getAmount(), "trade.getTradeAmount() must not be null"); Offer offer = trade.getOffer(); - TradingPeer tradingPeer = processModel.getTradingPeer(); + TradingPeer tradingPeer = processModel.getTradePeer(); BtcWalletService walletService = processModel.getBtcWalletService(); String id = processModel.getOffer().getId(); final byte[] buyerSignature = tradingPeer.getSignature(); - Coin buyerPayoutAmount = checkNotNull(offer.getBuyerSecurityDeposit()).add(trade.getTradeAmount()); + Coin buyerPayoutAmount = checkNotNull(offer.getBuyerSecurityDeposit()).add(trade.getAmount()); Coin sellerPayoutAmount = offer.getSellerSecurityDeposit(); final String buyerPayoutAddressString = tradingPeer.getPayoutAddressString(); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignsDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerSignsDelayedPayoutTx.java similarity index 92% rename from core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignsDelayedPayoutTx.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerSignsDelayedPayoutTx.java index adabac92eb..909ccc101c 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignsDelayedPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller/SellerSignsDelayedPayoutTx.java @@ -15,12 +15,12 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.seller; +package bisq.core.trade.protocol.bisq_v1.tasks.seller; import bisq.core.btc.model.AddressEntry; import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; @@ -59,7 +59,7 @@ public class SellerSignsDelayedPayoutTx extends TradeTask { checkArgument(Arrays.equals(sellerMultiSigPubKey, btcWalletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG).getPubKey()), "sellerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id); - byte[] buyerMultiSigPubKey = processModel.getTradingPeer().getMultiSigPubKey(); + byte[] buyerMultiSigPubKey = processModel.getTradePeer().getMultiSigPubKey(); byte[] delayedPayoutTxSignature = processModel.getTradeWalletService().signDelayedPayoutTx( preparedDelayedPayoutTx, diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerCreatesUnsignedDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller_as_maker/SellerAsMakerCreatesUnsignedDepositTx.java similarity index 91% rename from core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerCreatesUnsignedDepositTx.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller_as_maker/SellerAsMakerCreatesUnsignedDepositTx.java index b382fd01ed..8a6e3b1849 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerCreatesUnsignedDepositTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller_as_maker/SellerAsMakerCreatesUnsignedDepositTx.java @@ -15,16 +15,16 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.seller_as_maker; +package bisq.core.trade.protocol.bisq_v1.tasks.seller_as_maker; import bisq.core.btc.model.AddressEntry; import bisq.core.btc.model.PreparedDepositTxAndMakerInputs; import bisq.core.btc.model.RawTransactionInput; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.offer.Offer; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.TradingPeer; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.model.TradingPeer; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.common.crypto.Hash; import bisq.common.taskrunner.TaskRunner; @@ -51,11 +51,11 @@ public class SellerAsMakerCreatesUnsignedDepositTx extends TradeTask { protected void run() { try { runInterceptHook(); - checkNotNull(trade.getTradeAmount(), "trade.getTradeAmount() must not be null"); + checkNotNull(trade.getAmount(), "trade.getTradeAmount() must not be null"); BtcWalletService walletService = processModel.getBtcWalletService(); String id = processModel.getOffer().getId(); - TradingPeer tradingPeer = processModel.getTradingPeer(); + TradingPeer tradingPeer = processModel.getTradePeer(); Offer offer = checkNotNull(trade.getOffer()); // params @@ -66,7 +66,7 @@ public class SellerAsMakerCreatesUnsignedDepositTx extends TradeTask { + trade.getContractAsJson() + "\n------------------------------------------------------------\n"); - Coin makerInputAmount = offer.getSellerSecurityDeposit().add(trade.getTradeAmount()); + Coin makerInputAmount = offer.getSellerSecurityDeposit().add(trade.getAmount()); Optional addressEntryOptional = walletService.getAddressEntry(id, AddressEntry.Context.MULTI_SIG); checkArgument(addressEntryOptional.isPresent(), "addressEntryOptional must be present"); AddressEntry makerMultiSigAddressEntry = addressEntryOptional.get(); @@ -75,7 +75,7 @@ public class SellerAsMakerCreatesUnsignedDepositTx extends TradeTask { walletService.saveAddressEntryList(); Coin msOutputAmount = makerInputAmount - .add(trade.getTxFee()) + .add(trade.getTradeTxFee()) .add(offer.getBuyerSecurityDeposit()); List takerRawTransactionInputs = checkNotNull(tradingPeer.getRawTransactionInputs()); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerFinalizesDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller_as_maker/SellerAsMakerFinalizesDepositTx.java similarity index 88% rename from core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerFinalizesDepositTx.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller_as_maker/SellerAsMakerFinalizesDepositTx.java index 3902668f82..fb42b6a6f7 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerFinalizesDepositTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller_as_maker/SellerAsMakerFinalizesDepositTx.java @@ -15,10 +15,10 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.seller_as_maker; +package bisq.core.trade.protocol.bisq_v1.tasks.seller_as_maker; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; @@ -39,11 +39,11 @@ public class SellerAsMakerFinalizesDepositTx extends TradeTask { try { runInterceptHook(); - byte[] takersRawPreparedDepositTx = checkNotNull(processModel.getTradingPeer().getPreparedDepositTx()); + byte[] takersRawPreparedDepositTx = checkNotNull(processModel.getTradePeer().getPreparedDepositTx()); byte[] myRawPreparedDepositTx = checkNotNull(processModel.getPreparedDepositTx()); Transaction takersDepositTx = processModel.getBtcWalletService().getTxFromSerializedTx(takersRawPreparedDepositTx); Transaction myDepositTx = processModel.getBtcWalletService().getTxFromSerializedTx(myRawPreparedDepositTx); - int numTakersInputs = checkNotNull(processModel.getTradingPeer().getRawTransactionInputs()).size(); + int numTakersInputs = checkNotNull(processModel.getTradePeer().getRawTransactionInputs()).size(); processModel.getTradeWalletService().sellerAsMakerFinalizesDepositTx(myDepositTx, takersDepositTx, numTakersInputs); processModel.setDepositTx(myDepositTx); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerProcessDepositTxMessage.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller_as_maker/SellerAsMakerProcessDepositTxMessage.java similarity index 84% rename from core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerProcessDepositTxMessage.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller_as_maker/SellerAsMakerProcessDepositTxMessage.java index 0433347f84..1e6d8162cc 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerProcessDepositTxMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller_as_maker/SellerAsMakerProcessDepositTxMessage.java @@ -15,11 +15,11 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.seller_as_maker; +package bisq.core.trade.protocol.bisq_v1.tasks.seller_as_maker; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.DepositTxMessage; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.messages.DepositTxMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.core.util.Validator; import bisq.common.taskrunner.TaskRunner; @@ -38,12 +38,11 @@ public class SellerAsMakerProcessDepositTxMessage extends TradeTask { protected void run() { try { runInterceptHook(); - log.debug("current trade state " + trade.getState()); DepositTxMessage message = (DepositTxMessage) processModel.getTradeMessage(); Validator.checkTradeId(processModel.getOfferId(), message); checkNotNull(message); - processModel.getTradingPeer().setPreparedDepositTx(checkNotNull(message.getDepositTxWithoutWitnesses())); + processModel.getTradePeer().setPreparedDepositTx(checkNotNull(message.getDepositTxWithoutWitnesses())); trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress()); // When we receive that message the taker has published the taker fee, so we apply it to the trade. diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerSendsInputsForDepositTxResponse.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller_as_maker/SellerAsMakerSendsInputsForDepositTxResponse.java similarity index 90% rename from core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerSendsInputsForDepositTxResponse.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller_as_maker/SellerAsMakerSendsInputsForDepositTxResponse.java index e22d7b3dc5..b2bd8b3ac8 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerSendsInputsForDepositTxResponse.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller_as_maker/SellerAsMakerSendsInputsForDepositTxResponse.java @@ -15,10 +15,10 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.seller_as_maker; +package bisq.core.trade.protocol.bisq_v1.tasks.seller_as_maker; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.tasks.maker.MakerSendsInputsForDepositTxResponse; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.tasks.maker.MakerSendsInputsForDepositTxResponse; import bisq.common.taskrunner.TaskRunner; diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerCreatesDepositTxInputs.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller_as_taker/SellerAsTakerCreatesDepositTxInputs.java similarity index 88% rename from core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerCreatesDepositTxInputs.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller_as_taker/SellerAsTakerCreatesDepositTxInputs.java index 7aa4118e6e..ad12514d16 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerCreatesDepositTxInputs.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller_as_taker/SellerAsTakerCreatesDepositTxInputs.java @@ -15,12 +15,12 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.seller_as_taker; +package bisq.core.trade.protocol.bisq_v1.tasks.seller_as_taker; import bisq.core.btc.model.InputsAndChangeOutput; import bisq.core.offer.Offer; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; @@ -41,9 +41,9 @@ public class SellerAsTakerCreatesDepositTxInputs extends TradeTask { try { runInterceptHook(); - Coin tradeAmount = checkNotNull(trade.getTradeAmount()); + Coin tradeAmount = checkNotNull(trade.getAmount()); Offer offer = checkNotNull(trade.getOffer()); - Coin txFee = trade.getTxFee(); + Coin txFee = trade.getTradeTxFee(); Coin takerInputAmount = offer.getSellerSecurityDeposit() .add(txFee) .add(txFee) // We add 2 times the fee as one is for the payout tx diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerSignsDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller_as_taker/SellerAsTakerSignsDepositTx.java similarity index 85% rename from core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerSignsDepositTx.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller_as_taker/SellerAsTakerSignsDepositTx.java index 52d93e25c6..c5fccfdcd5 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerSignsDepositTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/seller_as_taker/SellerAsTakerSignsDepositTx.java @@ -15,16 +15,16 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.seller_as_taker; +package bisq.core.trade.protocol.bisq_v1.tasks.seller_as_taker; import bisq.core.btc.model.AddressEntry; import bisq.core.btc.model.RawTransactionInput; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.offer.Offer; -import bisq.core.trade.Contract; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.TradingPeer; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Contract; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.model.TradingPeer; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; @@ -66,16 +66,16 @@ public class SellerAsTakerSignsDepositTx extends TradeTask { Coin sellerInput = Coin.valueOf(sellerInputs.stream().mapToLong(input -> input.value).sum()); - Coin totalFee = trade.getTxFee().multiply(2); // Fee for deposit and payout tx + Coin totalFee = trade.getTradeTxFee().multiply(2); // Fee for deposit and payout tx Coin multiSigValue = sellerInput.subtract(totalFee); processModel.getBtcWalletService().setCoinLockedInMultiSigAddressEntry(sellerMultiSigAddressEntry, multiSigValue.value); walletService.saveAddressEntryList(); Offer offer = trade.getOffer(); - Coin msOutputAmount = offer.getBuyerSecurityDeposit().add(offer.getSellerSecurityDeposit()).add(trade.getTxFee()) - .add(checkNotNull(trade.getTradeAmount())); + Coin msOutputAmount = offer.getBuyerSecurityDeposit().add(offer.getSellerSecurityDeposit()).add(trade.getTradeTxFee()) + .add(checkNotNull(trade.getAmount())); - TradingPeer tradingPeer = processModel.getTradingPeer(); + TradingPeer tradingPeer = processModel.getTradePeer(); Transaction depositTx = processModel.getTradeWalletService().takerSignsDepositTx( true, @@ -95,7 +95,7 @@ public class SellerAsTakerSignsDepositTx extends TradeTask { } catch (Throwable t) { Contract contract = trade.getContract(); if (contract != null) - contract.printDiff(processModel.getTradingPeer().getContractAsJson()); + contract.printDiff(processModel.getTradePeer().getContractAsJson()); failed(t); } } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/CreateTakerFeeTx.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/taker/CreateTakerFeeTx.java similarity index 94% rename from core/src/main/java/bisq/core/trade/protocol/tasks/taker/CreateTakerFeeTx.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/taker/CreateTakerFeeTx.java index e646802008..b03817ab9b 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/CreateTakerFeeTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/taker/CreateTakerFeeTx.java @@ -15,15 +15,15 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.taker; +package bisq.core.trade.protocol.bisq_v1.tasks.taker; import bisq.core.btc.model.AddressEntry; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.TradeWalletService; import bisq.core.btc.wallet.WalletService; import bisq.core.dao.exceptions.DaoDisabledException; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.core.util.FeeReceiverSelector; import bisq.common.taskrunner.TaskRunner; @@ -75,7 +75,7 @@ public class CreateTakerFeeTx extends TradeTask { processModel.getFundsNeededForTrade(), processModel.isUseSavingsWallet(), trade.getTakerFee(), - trade.getTxFee(), + trade.getTradeTxFee(), feeReceiver, false, null); @@ -87,8 +87,8 @@ public class CreateTakerFeeTx extends TradeTask { changeAddress, processModel.getFundsNeededForTrade(), processModel.isUseSavingsWallet(), - trade.getTxFee()); - transaction = processModel.getBsqWalletService().signTx(txWithBsqFee); + trade.getTradeTxFee()); + transaction = processModel.getBsqWalletService().signTxAndVerifyNoDustOutputs(txWithBsqFee); WalletService.checkAllScriptSignaturesForTx(transaction); } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerProcessesInputsForDepositTxResponse.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/taker/TakerProcessesInputsForDepositTxResponse.java similarity index 92% rename from core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerProcessesInputsForDepositTxResponse.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/taker/TakerProcessesInputsForDepositTxResponse.java index ada1866d35..f91ac655c5 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerProcessesInputsForDepositTxResponse.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/taker/TakerProcessesInputsForDepositTxResponse.java @@ -15,13 +15,13 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.taker; +package bisq.core.trade.protocol.bisq_v1.tasks.taker; import bisq.core.btc.wallet.Restrictions; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.InputsForDepositTxResponse; -import bisq.core.trade.protocol.TradingPeer; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.messages.InputsForDepositTxResponse; +import bisq.core.trade.protocol.bisq_v1.model.TradingPeer; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.common.config.Config; import bisq.common.taskrunner.TaskRunner; @@ -49,7 +49,7 @@ public class TakerProcessesInputsForDepositTxResponse extends TradeTask { checkTradeId(processModel.getOfferId(), response); checkNotNull(response); - TradingPeer tradingPeer = processModel.getTradingPeer(); + TradingPeer tradingPeer = processModel.getTradePeer(); // 1.7.0: We do not expect the payment account anymore but in case peer has not updated we still process it. Optional.ofNullable(response.getMakerPaymentAccountPayload()) @@ -70,7 +70,7 @@ public class TakerProcessesInputsForDepositTxResponse extends TradeTask { long lockTime = response.getLockTime(); if (Config.baseCurrencyNetwork().isMainnet()) { int myLockTime = processModel.getBtcWalletService().getBestChainHeight() + - Restrictions.getLockTime(processModel.getOffer().getPaymentMethod().isAsset()); + Restrictions.getLockTime(processModel.getOffer().getPaymentMethod().isBlockchain()); // We allow a tolerance of 3 blocks as BestChainHeight might be a bit different on maker and taker in case a new // block was just found checkArgument(Math.abs(lockTime - myLockTime) <= 3, diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerPublishFeeTx.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/taker/TakerPublishFeeTx.java similarity index 96% rename from core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerPublishFeeTx.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/taker/TakerPublishFeeTx.java index 6324f74e00..45f467980d 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerPublishFeeTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/taker/TakerPublishFeeTx.java @@ -15,15 +15,15 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.taker; +package bisq.core.trade.protocol.bisq_v1.tasks.taker; import bisq.core.btc.exceptions.TxBroadcastException; import bisq.core.btc.wallet.BsqWalletService; import bisq.core.btc.wallet.TradeWalletService; import bisq.core.btc.wallet.TxBroadcaster; import bisq.core.dao.state.model.blockchain.TxType; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendInputsForDepositTxRequest.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/taker/TakerSendInputsForDepositTxRequest.java similarity index 93% rename from core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendInputsForDepositTxRequest.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/taker/TakerSendInputsForDepositTxRequest.java index 695971db4d..75cfa53d7c 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendInputsForDepositTxRequest.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/taker/TakerSendInputsForDepositTxRequest.java @@ -15,14 +15,14 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.taker; +package bisq.core.trade.protocol.bisq_v1.tasks.taker; import bisq.core.btc.model.AddressEntry; import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.InputsForDepositTxRequest; -import bisq.core.trade.protocol.ProcessModel; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.messages.InputsForDepositTxRequest; +import bisq.core.trade.protocol.bisq_v1.model.ProcessModel; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.core.user.User; import bisq.network.p2p.NodeAddress; @@ -57,7 +57,7 @@ public class TakerSendInputsForDepositTxRequest extends TradeTask { protected void run() { try { runInterceptHook(); - Coin tradeAmount = checkNotNull(trade.getTradeAmount(), "TradeAmount must not be null"); + Coin tradeAmount = checkNotNull(trade.getAmount(), "TradeAmount must not be null"); String takerFeeTxId = checkNotNull(processModel.getTakeOfferFeeTxId(), "TakeOfferFeeTxId must not be null"); User user = checkNotNull(processModel.getUser(), "User must not be null"); List acceptedArbitratorAddresses = user.getAcceptedArbitratorAddresses() == null ? @@ -109,8 +109,8 @@ public class TakerSendInputsForDepositTxRequest extends TradeTask { offerId, processModel.getMyNodeAddress(), tradeAmount.value, - trade.getTradePrice().getValue(), - trade.getTxFee().getValue(), + trade.getPrice().getValue(), + trade.getTradeTxFee().getValue(), trade.getTakerFee().getValue(), trade.isCurrencyForTakerFeeBtc(), processModel.getRawTransactionInputs(), @@ -142,7 +142,7 @@ public class TakerSendInputsForDepositTxRequest extends TradeTask { processModel.getP2PService().sendEncryptedDirectMessage( trade.getTradingPeerNodeAddress(), - processModel.getTradingPeer().getPubKeyRing(), + processModel.getTradePeer().getPubKeyRing(), request, new SendDirectMessageListener() { public void onArrived() { diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerVerifyAndSignContract.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/taker/TakerVerifyAndSignContract.java similarity index 82% rename from core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerVerifyAndSignContract.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/taker/TakerVerifyAndSignContract.java index 7812eec939..cce714ed3a 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerVerifyAndSignContract.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/taker/TakerVerifyAndSignContract.java @@ -15,23 +15,25 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.taker; +package bisq.core.trade.protocol.bisq_v1.tasks.taker; import bisq.core.btc.model.AddressEntry; import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.trade.Contract; -import bisq.core.trade.SellerAsTakerTrade; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.ProcessModel; -import bisq.core.trade.protocol.TradingPeer; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.offer.Offer; +import bisq.core.offer.bisq_v1.OfferPayload; +import bisq.core.trade.model.bisq_v1.Contract; +import bisq.core.trade.model.bisq_v1.SellerAsTakerTrade; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.model.ProcessModel; +import bisq.core.trade.protocol.bisq_v1.model.TradingPeer; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; +import bisq.core.util.JsonUtil; import bisq.network.p2p.NodeAddress; import bisq.common.crypto.Hash; import bisq.common.crypto.Sig; import bisq.common.taskrunner.TaskRunner; -import bisq.common.util.Utilities; import org.bitcoinj.core.Coin; @@ -54,7 +56,7 @@ public class TakerVerifyAndSignContract extends TradeTask { runInterceptHook(); String takerFeeTxId = checkNotNull(processModel.getTakeOfferFeeTxId()); - TradingPeer maker = processModel.getTradingPeer(); + TradingPeer maker = processModel.getTradePeer(); boolean isBuyerMakerAndSellerTaker = trade instanceof SellerAsTakerTrade; NodeAddress buyerNodeAddress = isBuyerMakerAndSellerTaker ? @@ -65,7 +67,8 @@ public class TakerVerifyAndSignContract extends TradeTask { processModel.getTempTradingPeerNodeAddress(); BtcWalletService walletService = processModel.getBtcWalletService(); - String id = processModel.getOffer().getId(); + Offer offer = processModel.getOffer(); + String id = offer.getId(); AddressEntry takerPayoutAddressEntry = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.TRADE_PAYOUT); String takerPayoutAddressString = takerPayoutAddressEntry.getAddressString(); AddressEntry takerMultiSigAddressEntry = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG); @@ -79,11 +82,12 @@ public class TakerVerifyAndSignContract extends TradeTask { String makersPaymentMethodId = checkNotNull(maker.getPaymentMethodId()); String takersPaymentMethodId = checkNotNull(processModel.getPaymentAccountPayload(trade)).getPaymentMethodId(); - Coin tradeAmount = checkNotNull(trade.getTradeAmount()); + Coin tradeAmount = checkNotNull(trade.getAmount()); + OfferPayload offerPayload = offer.getOfferPayload().orElseThrow(); Contract contract = new Contract( - processModel.getOffer().getOfferPayload(), + offerPayload, tradeAmount.value, - trade.getTradePrice().getValue(), + trade.getPrice().getValue(), takerFeeTxId, buyerNodeAddress, sellerNodeAddress, @@ -106,10 +110,10 @@ public class TakerVerifyAndSignContract extends TradeTask { makersPaymentMethodId, takersPaymentMethodId ); - String contractAsJson = Utilities.objectToJson(contract); + String contractAsJson = JsonUtil.objectToJson(contract); - if (!contractAsJson.equals(processModel.getTradingPeer().getContractAsJson())) { - contract.printDiff(processModel.getTradingPeer().getContractAsJson()); + if (!contractAsJson.equals(processModel.getTradePeer().getContractAsJson())) { + contract.printDiff(processModel.getTradePeer().getContractAsJson()); failed("Contracts are not matching"); } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerVerifyMakerFeePayment.java b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/taker/TakerVerifyMakerFeePayment.java similarity index 90% rename from core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerVerifyMakerFeePayment.java rename to core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/taker/TakerVerifyMakerFeePayment.java index 7d8d208d4d..941cb6e73f 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerVerifyMakerFeePayment.java +++ b/core/src/main/java/bisq/core/trade/protocol/bisq_v1/tasks/taker/TakerVerifyMakerFeePayment.java @@ -15,10 +15,10 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.taker; +package bisq.core.trade.protocol.bisq_v1.tasks.taker; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/BsqSwapBuyerAsMakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/BsqSwapBuyerAsMakerProtocol.java new file mode 100644 index 0000000000..05bd43a134 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/BsqSwapBuyerAsMakerProtocol.java @@ -0,0 +1,111 @@ +/* + * 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.core.trade.protocol.bsq_swap; + + +import bisq.core.trade.model.bsq_swap.BsqSwapBuyerAsMakerTrade; +import bisq.core.trade.protocol.TradeMessage; +import bisq.core.trade.protocol.TradeTaskRunner; +import bisq.core.trade.protocol.bsq_swap.messages.BsqSwapFinalizeTxRequest; +import bisq.core.trade.protocol.bsq_swap.messages.BsqSwapRequest; +import bisq.core.trade.protocol.bsq_swap.messages.SellersBsqSwapRequest; +import bisq.core.trade.protocol.bsq_swap.tasks.ApplyFilter; +import bisq.core.trade.protocol.bsq_swap.tasks.buyer.BuyerPublishesTx; +import bisq.core.trade.protocol.bsq_swap.tasks.buyer.PublishTradeStatistics; +import bisq.core.trade.protocol.bsq_swap.tasks.buyer.SendFinalizedTxMessage; +import bisq.core.trade.protocol.bsq_swap.tasks.buyer_as_maker.BuyerAsMakerCreatesAndSignsFinalizedTx; +import bisq.core.trade.protocol.bsq_swap.tasks.buyer_as_maker.BuyerAsMakerCreatesBsqInputsAndChange; +import bisq.core.trade.protocol.bsq_swap.tasks.buyer_as_maker.BuyerAsMakerProcessBsqSwapFinalizeTxRequest; +import bisq.core.trade.protocol.bsq_swap.tasks.buyer_as_maker.BuyerAsMakerRemoveOpenOffer; +import bisq.core.trade.protocol.bsq_swap.tasks.buyer_as_maker.ProcessSellersBsqSwapRequest; +import bisq.core.trade.protocol.bsq_swap.tasks.buyer_as_maker.SendBsqSwapTxInputsMessage; + +import bisq.network.p2p.NodeAddress; + +import bisq.common.handlers.ErrorMessageHandler; + +import lombok.extern.slf4j.Slf4j; + +import static bisq.core.trade.model.bsq_swap.BsqSwapTrade.State.PREPARATION; + +@Slf4j +public class BsqSwapBuyerAsMakerProtocol extends BsqSwapBuyerProtocol implements BsqSwapMakerProtocol { + + public BsqSwapBuyerAsMakerProtocol(BsqSwapBuyerAsMakerTrade trade) { + super(trade); + } + + @Override + public void handleTakeOfferRequest(BsqSwapRequest bsqSwapRequest, + NodeAddress sender, + ErrorMessageHandler errorMessageHandler) { + SellersBsqSwapRequest request = (SellersBsqSwapRequest) bsqSwapRequest; + expect(preCondition(PREPARATION == trade.getTradeState()) + .with(request) + .from(sender)) + .setup(tasks( + ApplyFilter.class, + ProcessSellersBsqSwapRequest.class, + BuyerAsMakerCreatesBsqInputsAndChange.class, + SendBsqSwapTxInputsMessage.class) + .using(new TradeTaskRunner(trade, + () -> handleTaskRunnerSuccess(request), + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(request, errorMessage); + })) + .withTimeout(40)) + .executeTasks(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Incoming message handling + /////////////////////////////////////////////////////////////////////////////////////////// + + void handle(BsqSwapFinalizeTxRequest message, NodeAddress sender) { + expect(preCondition(PREPARATION == trade.getTradeState()) + .with(message) + .from(sender)) + .setup(tasks( + BuyerAsMakerProcessBsqSwapFinalizeTxRequest.class, + BuyerAsMakerCreatesAndSignsFinalizedTx.class, + BuyerPublishesTx.class, + BuyerAsMakerRemoveOpenOffer.class, + PublishTradeStatistics.class, + SendFinalizedTxMessage.class) + .using(new TradeTaskRunner(trade, + () -> { + stopTimeout(); + handleTaskRunnerSuccess(message); + }, + errorMessage -> handleTaskRunnerFault(message, errorMessage))) + ) + .executeTasks(); + } + + @Override + protected void onTradeMessage(TradeMessage message, NodeAddress peer) { + log.info("Received {} from {} with tradeId {} and uid {}", + message.getClass().getSimpleName(), peer, message.getTradeId(), message.getUid()); + + if (message instanceof BsqSwapFinalizeTxRequest) { + handle((BsqSwapFinalizeTxRequest) message, peer); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/BsqSwapBuyerAsTakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/BsqSwapBuyerAsTakerProtocol.java new file mode 100644 index 0000000000..943806038e --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/BsqSwapBuyerAsTakerProtocol.java @@ -0,0 +1,99 @@ +/* + * 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.core.trade.protocol.bsq_swap; + + +import bisq.core.offer.Offer; +import bisq.core.trade.model.bsq_swap.BsqSwapBuyerAsTakerTrade; +import bisq.core.trade.protocol.TradeMessage; +import bisq.core.trade.protocol.TradeTaskRunner; +import bisq.core.trade.protocol.bsq_swap.messages.BsqSwapFinalizeTxRequest; +import bisq.core.trade.protocol.bsq_swap.tasks.ApplyFilter; +import bisq.core.trade.protocol.bsq_swap.tasks.buyer.BuyerPublishesTx; +import bisq.core.trade.protocol.bsq_swap.tasks.buyer.PublishTradeStatistics; +import bisq.core.trade.protocol.bsq_swap.tasks.buyer.SendFinalizedTxMessage; +import bisq.core.trade.protocol.bsq_swap.tasks.buyer_as_taker.BuyerAsTakerCreatesAndSignsFinalizedTx; +import bisq.core.trade.protocol.bsq_swap.tasks.buyer_as_taker.BuyerAsTakerCreatesBsqInputsAndChange; +import bisq.core.trade.protocol.bsq_swap.tasks.buyer_as_taker.BuyerAsTakerProcessBsqSwapFinalizeTxRequest; +import bisq.core.trade.protocol.bsq_swap.tasks.buyer_as_taker.SendBuyersBsqSwapRequest; + +import bisq.network.p2p.NodeAddress; + +import lombok.extern.slf4j.Slf4j; + +import static bisq.core.trade.model.bsq_swap.BsqSwapTrade.State.PREPARATION; +import static bisq.core.trade.protocol.bisq_v1.TakerProtocol.TakerEvent.TAKE_OFFER; +import static com.google.common.base.Preconditions.checkNotNull; + +@Slf4j +public class BsqSwapBuyerAsTakerProtocol extends BsqSwapBuyerProtocol implements BsqSwapTakerProtocol { + + public BsqSwapBuyerAsTakerProtocol(BsqSwapBuyerAsTakerTrade trade) { + super(trade); + + Offer offer = checkNotNull(trade.getOffer()); + protocolModel.getTradePeer().setPubKeyRing(offer.getPubKeyRing()); + } + + @Override + public void onTakeOffer() { + expect(preCondition(PREPARATION == trade.getTradeState()) + .with(TAKE_OFFER) + .from(trade.getTradingPeerNodeAddress())) + .setup(tasks( + ApplyFilter.class, + BuyerAsTakerCreatesBsqInputsAndChange.class, + SendBuyersBsqSwapRequest.class) + .withTimeout(40)) + .executeTasks(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Incoming message handling + /////////////////////////////////////////////////////////////////////////////////////////// + + void handle(BsqSwapFinalizeTxRequest message, NodeAddress sender) { + expect(preCondition(PREPARATION == trade.getTradeState()) + .with(message) + .from(sender)) + .setup(tasks( + BuyerAsTakerProcessBsqSwapFinalizeTxRequest.class, + BuyerAsTakerCreatesAndSignsFinalizedTx.class, + BuyerPublishesTx.class, + PublishTradeStatistics.class, + SendFinalizedTxMessage.class) + .using(new TradeTaskRunner(trade, + () -> { + stopTimeout(); + handleTaskRunnerSuccess(message); + }, + errorMessage -> handleTaskRunnerFault(message, errorMessage)))) + .executeTasks(); + } + + @Override + protected void onTradeMessage(TradeMessage message, NodeAddress peer) { + log.info("Received {} from {} with tradeId {} and uid {}", + message.getClass().getSimpleName(), peer, message.getTradeId(), message.getUid()); + + if (message instanceof BsqSwapFinalizeTxRequest) { + handle((BsqSwapFinalizeTxRequest) message, peer); + } + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradableListItem.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/BsqSwapBuyerProtocol.java similarity index 64% rename from desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradableListItem.java rename to core/src/main/java/bisq/core/trade/protocol/bsq_swap/BsqSwapBuyerProtocol.java index 602b4148cf..d8082250bb 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradableListItem.java +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/BsqSwapBuyerProtocol.java @@ -15,22 +15,16 @@ * along with Bisq. If not, see . */ -package bisq.desktop.main.portfolio.closedtrades; +package bisq.core.trade.protocol.bsq_swap; -import bisq.core.trade.Tradable; -/** - * We could remove that wrapper if it is not needed for additional UI only fields. - */ -class ClosedTradableListItem { +import bisq.core.trade.model.bsq_swap.BsqSwapBuyerTrade; - private final Tradable tradable; +import lombok.extern.slf4j.Slf4j; - ClosedTradableListItem(Tradable tradable) { - this.tradable = tradable; - } - - Tradable getTradable() { - return tradable; +@Slf4j +public abstract class BsqSwapBuyerProtocol extends BsqSwapProtocol { + public BsqSwapBuyerProtocol(BsqSwapBuyerTrade trade) { + super(trade); } } diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/BsqSwapMakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/BsqSwapMakerProtocol.java new file mode 100644 index 0000000000..b4013f9804 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/BsqSwapMakerProtocol.java @@ -0,0 +1,31 @@ +/* + * 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.core.trade.protocol.bsq_swap; + + +import bisq.core.trade.protocol.bsq_swap.messages.BsqSwapRequest; + +import bisq.network.p2p.NodeAddress; + +import bisq.common.handlers.ErrorMessageHandler; + +public interface BsqSwapMakerProtocol { + void handleTakeOfferRequest(BsqSwapRequest request, + NodeAddress sender, + ErrorMessageHandler errorMessageHandler); +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/BsqSwapProtocol.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/BsqSwapProtocol.java new file mode 100644 index 0000000000..3588a32918 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/BsqSwapProtocol.java @@ -0,0 +1,44 @@ +/* + * 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.core.trade.protocol.bsq_swap; + + +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.TradeProtocol; + +import bisq.network.p2p.AckMessage; +import bisq.network.p2p.NodeAddress; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public abstract class BsqSwapProtocol extends TradeProtocol { + + protected final BsqSwapTrade trade; + + public BsqSwapProtocol(BsqSwapTrade trade) { + super(trade); + + this.trade = trade; + } + + @Override + protected void onAckMessage(AckMessage ackMessage, NodeAddress peer) { + log.info("Received ackMessage {} from peer {}", ackMessage, peer); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/BsqSwapSellerAsMakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/BsqSwapSellerAsMakerProtocol.java new file mode 100644 index 0000000000..74694a4004 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/BsqSwapSellerAsMakerProtocol.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.core.trade.protocol.bsq_swap; + + +import bisq.core.trade.model.bsq_swap.BsqSwapSellerAsMakerTrade; +import bisq.core.trade.protocol.TradeMessage; +import bisq.core.trade.protocol.TradeTaskRunner; +import bisq.core.trade.protocol.bsq_swap.messages.BsqSwapFinalizedTxMessage; +import bisq.core.trade.protocol.bsq_swap.messages.BsqSwapRequest; +import bisq.core.trade.protocol.bsq_swap.messages.BuyersBsqSwapRequest; +import bisq.core.trade.protocol.bsq_swap.tasks.ApplyFilter; +import bisq.core.trade.protocol.bsq_swap.tasks.seller.SellerMaybePublishesTx; +import bisq.core.trade.protocol.bsq_swap.tasks.seller.SendBsqSwapFinalizeTxRequest; +import bisq.core.trade.protocol.bsq_swap.tasks.seller_as_maker.ProcessBuyersBsqSwapRequest; +import bisq.core.trade.protocol.bsq_swap.tasks.seller_as_maker.SellerAsMakerCreatesAndSignsTx; +import bisq.core.trade.protocol.bsq_swap.tasks.seller_as_maker.SellerAsMakerProcessBsqSwapFinalizedTxMessage; +import bisq.core.trade.protocol.bsq_swap.tasks.seller_as_maker.SellerAsMakerSetupTxListener; + +import bisq.network.p2p.NodeAddress; + +import bisq.common.handlers.ErrorMessageHandler; + +import lombok.extern.slf4j.Slf4j; + +import static bisq.core.trade.model.bsq_swap.BsqSwapTrade.State.COMPLETED; +import static bisq.core.trade.model.bsq_swap.BsqSwapTrade.State.PREPARATION; + +@Slf4j +public class BsqSwapSellerAsMakerProtocol extends BsqSwapSellerProtocol implements BsqSwapMakerProtocol { + public BsqSwapSellerAsMakerProtocol(BsqSwapSellerAsMakerTrade trade) { + super(trade); + log.error("BsqSwapSellerAsMakerProtocol " + trade.getId()); + } + + @Override + public void handleTakeOfferRequest(BsqSwapRequest bsqSwapRequest, + NodeAddress sender, + ErrorMessageHandler errorMessageHandler) { + BuyersBsqSwapRequest request = (BuyersBsqSwapRequest) bsqSwapRequest; + expect(preCondition(PREPARATION == trade.getTradeState()) + .with(request) + .from(sender)) + .setup(tasks( + ApplyFilter.class, + ProcessBuyersBsqSwapRequest.class, + SellerAsMakerCreatesAndSignsTx.class, + SellerAsMakerSetupTxListener.class, + SendBsqSwapFinalizeTxRequest.class) + .using(new TradeTaskRunner(trade, + () -> { + stopTimeout(); + handleTaskRunnerSuccess(request); + }, + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(request, errorMessage); + })) + .withTimeout(40)) + .executeTasks(); + } + + // We treat BsqSwapFinalizedTxMessage as optional message, so we stop the timeout at handleTakeOfferRequest + private void handle(BsqSwapFinalizedTxMessage message, NodeAddress sender) { + expect(preCondition(PREPARATION == trade.getTradeState() || COMPLETED == trade.getTradeState()) + .with(message) + .from(sender)) + .setup(tasks( + SellerAsMakerProcessBsqSwapFinalizedTxMessage.class, + SellerMaybePublishesTx.class)) + .executeTasks(); + } + + @Override + protected void onTradeMessage(TradeMessage message, NodeAddress peer) { + log.info("Received {} from {} with tradeId {} and uid {}", + message.getClass().getSimpleName(), peer, message.getTradeId(), message.getUid()); + + if (message instanceof BsqSwapFinalizedTxMessage) { + handle((BsqSwapFinalizedTxMessage) message, peer); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/BsqSwapSellerAsTakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/BsqSwapSellerAsTakerProtocol.java new file mode 100644 index 0000000000..f055d7f32f --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/BsqSwapSellerAsTakerProtocol.java @@ -0,0 +1,110 @@ +/* + * 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.core.trade.protocol.bsq_swap; + + +import bisq.core.offer.Offer; +import bisq.core.trade.model.bsq_swap.BsqSwapSellerAsTakerTrade; +import bisq.core.trade.protocol.TradeMessage; +import bisq.core.trade.protocol.TradeTaskRunner; +import bisq.core.trade.protocol.bsq_swap.messages.BsqSwapFinalizedTxMessage; +import bisq.core.trade.protocol.bsq_swap.messages.BsqSwapTxInputsMessage; +import bisq.core.trade.protocol.bsq_swap.tasks.ApplyFilter; +import bisq.core.trade.protocol.bsq_swap.tasks.seller.SellerMaybePublishesTx; +import bisq.core.trade.protocol.bsq_swap.tasks.seller.SendBsqSwapFinalizeTxRequest; +import bisq.core.trade.protocol.bsq_swap.tasks.seller_as_taker.ProcessBsqSwapTxInputsMessage; +import bisq.core.trade.protocol.bsq_swap.tasks.seller_as_taker.SellerAsTakerCreatesAndSignsTx; +import bisq.core.trade.protocol.bsq_swap.tasks.seller_as_taker.SellerAsTakerProcessBsqSwapFinalizedTxMessage; +import bisq.core.trade.protocol.bsq_swap.tasks.seller_as_taker.SellerAsTakerSetupTxListener; +import bisq.core.trade.protocol.bsq_swap.tasks.seller_as_taker.SendSellersBsqSwapRequest; + +import bisq.network.p2p.NodeAddress; + +import lombok.extern.slf4j.Slf4j; + +import static bisq.core.trade.model.bsq_swap.BsqSwapTrade.State.COMPLETED; +import static bisq.core.trade.model.bsq_swap.BsqSwapTrade.State.PREPARATION; +import static bisq.core.trade.protocol.bisq_v1.TakerProtocol.TakerEvent.TAKE_OFFER; +import static com.google.common.base.Preconditions.checkNotNull; + +@Slf4j +public class BsqSwapSellerAsTakerProtocol extends BsqSwapSellerProtocol implements BsqSwapTakerProtocol { + public BsqSwapSellerAsTakerProtocol(BsqSwapSellerAsTakerTrade trade) { + super(trade); + + Offer offer = checkNotNull(trade.getOffer()); + protocolModel.getTradePeer().setPubKeyRing(offer.getPubKeyRing()); + } + + @Override + public void onTakeOffer() { + expect(preCondition(PREPARATION == trade.getTradeState()) + .with(TAKE_OFFER) + .from(trade.getTradingPeerNodeAddress())) + .setup(tasks( + ApplyFilter.class, + SendSellersBsqSwapRequest.class) + .withTimeout(40)) + .executeTasks(); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Incoming message handling + /////////////////////////////////////////////////////////////////////////////////////////// + + private void handle(BsqSwapTxInputsMessage message, NodeAddress sender) { + expect(preCondition(PREPARATION == trade.getTradeState()) + .with(message) + .from(sender)) + .setup(tasks( + ProcessBsqSwapTxInputsMessage.class, + SellerAsTakerCreatesAndSignsTx.class, + SellerAsTakerSetupTxListener.class, + SendBsqSwapFinalizeTxRequest.class) + .using(new TradeTaskRunner(trade, + () -> { + stopTimeout(); + handleTaskRunnerSuccess(message); + }, + errorMessage -> handleTaskRunnerFault(message, errorMessage)))) + .executeTasks(); + } + + // We treat BsqSwapFinalizedTxMessage as optional message, so we stop the timeout at handleBsqSwapTxInputsMessage + private void handle(BsqSwapFinalizedTxMessage message, NodeAddress sender) { + expect(preCondition(PREPARATION == trade.getTradeState() || COMPLETED == trade.getTradeState()) + .with(message) + .from(sender)) + .setup(tasks( + SellerAsTakerProcessBsqSwapFinalizedTxMessage.class, + SellerMaybePublishesTx.class)) + .executeTasks(); + } + + @Override + protected void onTradeMessage(TradeMessage message, NodeAddress peer) { + log.info("Received {} from {} with tradeId {} and uid {}", + message.getClass().getSimpleName(), peer, message.getTradeId(), message.getUid()); + + if (message instanceof BsqSwapTxInputsMessage) { + handle((BsqSwapTxInputsMessage) message, peer); + } else if (message instanceof BsqSwapFinalizedTxMessage) { + handle((BsqSwapFinalizedTxMessage) message, peer); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/BsqSwapSellerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/BsqSwapSellerProtocol.java new file mode 100644 index 0000000000..1d6a43a134 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/BsqSwapSellerProtocol.java @@ -0,0 +1,30 @@ +/* + * 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.core.trade.protocol.bsq_swap; + + +import bisq.core.trade.model.bsq_swap.BsqSwapSellerTrade; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public abstract class BsqSwapSellerProtocol extends BsqSwapProtocol { + public BsqSwapSellerProtocol(BsqSwapSellerTrade trade) { + super(trade); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/BsqSwapTakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/BsqSwapTakerProtocol.java new file mode 100644 index 0000000000..8b339f0a1f --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/BsqSwapTakerProtocol.java @@ -0,0 +1,23 @@ +/* + * 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.core.trade.protocol.bsq_swap; + +import bisq.core.trade.protocol.bisq_v1.TakerProtocol; + +public interface BsqSwapTakerProtocol extends TakerProtocol { +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/messages/BsqSwapFinalizeTxRequest.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/messages/BsqSwapFinalizeTxRequest.java new file mode 100644 index 0000000000..a23641c077 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/messages/BsqSwapFinalizeTxRequest.java @@ -0,0 +1,129 @@ +/* + * 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.core.trade.protocol.bsq_swap.messages; + +import bisq.core.btc.model.RawTransactionInput; +import bisq.core.trade.protocol.TradeMessage; + +import bisq.network.p2p.DirectMessage; +import bisq.network.p2p.NodeAddress; + +import bisq.common.app.Version; + +import com.google.protobuf.ByteString; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +import lombok.EqualsAndHashCode; +import lombok.Getter; + +@EqualsAndHashCode(callSuper = true) +@Getter +public final class BsqSwapFinalizeTxRequest extends TradeMessage implements DirectMessage { + private final NodeAddress senderNodeAddress; + private final byte[] tx; + private final List btcInputs; + private final long btcChange; + private final String bsqPayoutAddress; + private final String btcChangeAddress; + + + public BsqSwapFinalizeTxRequest(String tradeId, + NodeAddress senderNodeAddress, + byte[] tx, + List btcInputs, + long btcChange, + String bsqPayoutAddress, + String btcChangeAddress) { + this(Version.getP2PMessageVersion(), + tradeId, + UUID.randomUUID().toString(), + senderNodeAddress, + tx, + btcInputs, + btcChange, + bsqPayoutAddress, + btcChangeAddress); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + private BsqSwapFinalizeTxRequest(int messageVersion, + String tradeId, + String uid, + NodeAddress senderNodeAddress, + byte[] tx, + List btcInputs, + long btcChange, + String bsqPayoutAddress, + String btcChangeAddress) { + super(messageVersion, tradeId, uid); + this.senderNodeAddress = senderNodeAddress; + this.tx = tx; + this.btcInputs = btcInputs; + this.btcChange = btcChange; + this.bsqPayoutAddress = bsqPayoutAddress; + this.btcChangeAddress = btcChangeAddress; + } + + @Override + public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { + return getNetworkEnvelopeBuilder() + .setBsqSwapFinalizeTxRequest(protobuf.BsqSwapFinalizeTxRequest.newBuilder() + .setTradeId(tradeId) + .setUid(uid) + .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) + .setTx(ByteString.copyFrom(tx)) + .addAllBtcInputs(btcInputs.stream().map(RawTransactionInput::toProtoMessage).collect( + Collectors.toList())) + .setBtcChange(btcChange) + .setBsqPayoutAddress(bsqPayoutAddress) + .setBtcChangeAddress(btcChangeAddress)) + .build(); + } + + public static BsqSwapFinalizeTxRequest fromProto(protobuf.BsqSwapFinalizeTxRequest proto, int messageVersion) { + return new BsqSwapFinalizeTxRequest(messageVersion, + proto.getTradeId(), + proto.getUid(), + NodeAddress.fromProto(proto.getSenderNodeAddress()), + proto.getTx().toByteArray(), + proto.getBtcInputsList().stream() + .map(RawTransactionInput::fromProto) + .collect(Collectors.toList()), + proto.getBtcChange(), + proto.getBsqPayoutAddress(), + proto.getBtcChangeAddress() + ); + } + + @Override + public String toString() { + return "BsqSwapFinalizeTxRequest{" + + "\r\n senderNodeAddress=" + senderNodeAddress + + ",\r\n btcInputs=" + btcInputs + + ",\r\n btcChange=" + btcChange + + ",\r\n bsqPayoutAddress='" + bsqPayoutAddress + '\'' + + ",\r\n btcChangeAddress='" + btcChangeAddress + '\'' + + "\r\n} " + super.toString(); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/messages/BsqSwapFinalizedTxMessage.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/messages/BsqSwapFinalizedTxMessage.java new file mode 100644 index 0000000000..fd046bae39 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/messages/BsqSwapFinalizedTxMessage.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.core.trade.protocol.bsq_swap.messages; + +import bisq.core.trade.protocol.TradeMessage; + +import bisq.network.p2p.NodeAddress; + +import bisq.common.app.Version; + +import com.google.protobuf.ByteString; + +import java.util.UUID; + +import lombok.EqualsAndHashCode; +import lombok.Getter; + +@EqualsAndHashCode(callSuper = true) +@Getter +public final class BsqSwapFinalizedTxMessage extends TradeMessage { + private final NodeAddress senderNodeAddress; + private final byte[] tx; + + + public BsqSwapFinalizedTxMessage(String tradeId, + NodeAddress senderNodeAddress, + byte[] tx) { + this(Version.getP2PMessageVersion(), + tradeId, + UUID.randomUUID().toString(), + senderNodeAddress, + tx); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + private BsqSwapFinalizedTxMessage(int messageVersion, + String tradeId, + String uid, + NodeAddress senderNodeAddress, + byte[] tx) { + super(messageVersion, tradeId, uid); + this.senderNodeAddress = senderNodeAddress; + this.tx = tx; + } + + @Override + public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { + return getNetworkEnvelopeBuilder() + .setBsqSwapFinalizedTxMessage(protobuf.BsqSwapFinalizedTxMessage.newBuilder() + .setTradeId(tradeId) + .setUid(uid) + .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) + .setTx(ByteString.copyFrom(tx))) + .build(); + } + + public static BsqSwapFinalizedTxMessage fromProto(protobuf.BsqSwapFinalizedTxMessage proto, int messageVersion) { + return new BsqSwapFinalizedTxMessage(messageVersion, + proto.getTradeId(), + proto.getUid(), + NodeAddress.fromProto(proto.getSenderNodeAddress()), + proto.getTx().toByteArray() + ); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/messages/BsqSwapRequest.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/messages/BsqSwapRequest.java new file mode 100644 index 0000000000..81363e6c8a --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/messages/BsqSwapRequest.java @@ -0,0 +1,60 @@ +/* + * 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.core.trade.protocol.bsq_swap.messages; + +import bisq.core.trade.protocol.TradeMessage; + +import bisq.network.p2p.DirectMessage; +import bisq.network.p2p.NodeAddress; + +import bisq.common.crypto.PubKeyRing; + +import lombok.EqualsAndHashCode; +import lombok.Getter; + +@EqualsAndHashCode(callSuper = true) +@Getter +public abstract class BsqSwapRequest extends TradeMessage implements DirectMessage { + protected final NodeAddress senderNodeAddress; + protected final PubKeyRing takerPubKeyRing; + protected final long tradeAmount; + protected final long txFeePerVbyte; + protected final long makerFee; + protected final long takerFee; + protected final long tradeDate; + + protected BsqSwapRequest(int messageVersion, + String tradeId, + String uid, + NodeAddress senderNodeAddress, + PubKeyRing takerPubKeyRing, + long tradeAmount, + long txFeePerVbyte, + long makerFee, + long takerFee, + long tradeDate) { + super(messageVersion, tradeId, uid); + this.senderNodeAddress = senderNodeAddress; + this.takerPubKeyRing = takerPubKeyRing; + this.tradeAmount = tradeAmount; + this.txFeePerVbyte = txFeePerVbyte; + this.makerFee = makerFee; + this.takerFee = takerFee; + this.tradeDate = tradeDate; + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/messages/BsqSwapTxInputsMessage.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/messages/BsqSwapTxInputsMessage.java new file mode 100644 index 0000000000..6d405d4d9a --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/messages/BsqSwapTxInputsMessage.java @@ -0,0 +1,107 @@ +/* + * 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.core.trade.protocol.bsq_swap.messages; + +import bisq.core.btc.model.RawTransactionInput; +import bisq.core.trade.protocol.TradeMessage; + +import bisq.network.p2p.NodeAddress; + +import bisq.common.app.Version; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +import lombok.EqualsAndHashCode; +import lombok.Getter; + +@EqualsAndHashCode(callSuper = true) +@Getter +public final class BsqSwapTxInputsMessage extends TradeMessage implements TxInputsMessage { + private final NodeAddress senderNodeAddress; + private final List bsqInputs; + private final long bsqChange; + private final String buyersBtcPayoutAddress; + private final String buyersBsqChangeAddress; + + public BsqSwapTxInputsMessage(String tradeId, + NodeAddress senderNodeAddress, + List bsqInputs, + long bsqChange, + String buyersBtcPayoutAddress, + String buyersBsqChangeAddress) { + this(Version.getP2PMessageVersion(), + tradeId, + UUID.randomUUID().toString(), + senderNodeAddress, + bsqInputs, + bsqChange, + buyersBtcPayoutAddress, + buyersBsqChangeAddress); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + private BsqSwapTxInputsMessage(int messageVersion, + String tradeId, + String uid, + NodeAddress senderNodeAddress, + List bsqInputs, + long bsqChange, + String buyersBtcPayoutAddress, + String buyersBsqChangeAddress) { + super(messageVersion, tradeId, uid); + this.senderNodeAddress = senderNodeAddress; + this.bsqInputs = bsqInputs; + this.bsqChange = bsqChange; + this.buyersBtcPayoutAddress = buyersBtcPayoutAddress; + this.buyersBsqChangeAddress = buyersBsqChangeAddress; + } + + @Override + public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { + return getNetworkEnvelopeBuilder() + .setBsqSwapTxInputsMessage(protobuf.BsqSwapTxInputsMessage.newBuilder() + .setUid(uid) + .setTradeId(tradeId) + .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) + .addAllBsqInputs(bsqInputs.stream().map(RawTransactionInput::toProtoMessage).collect( + Collectors.toList())) + .setBsqChange(bsqChange) + .setBuyersBtcPayoutAddress(buyersBtcPayoutAddress) + .setBuyersBsqChangeAddress(buyersBsqChangeAddress)) + .build(); + } + + public static BsqSwapTxInputsMessage fromProto(protobuf.BsqSwapTxInputsMessage proto, + int messageVersion) { + return new BsqSwapTxInputsMessage(messageVersion, + proto.getTradeId(), + proto.getUid(), + NodeAddress.fromProto(proto.getSenderNodeAddress()), + proto.getBsqInputsList().stream() + .map(RawTransactionInput::fromProto) + .collect(Collectors.toList()), + proto.getBsqChange(), + proto.getBuyersBtcPayoutAddress(), + proto.getBuyersBsqChangeAddress()); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/messages/BuyersBsqSwapRequest.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/messages/BuyersBsqSwapRequest.java new file mode 100644 index 0000000000..4d584973d3 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/messages/BuyersBsqSwapRequest.java @@ -0,0 +1,138 @@ +/* + * 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.core.trade.protocol.bsq_swap.messages; + +import bisq.core.btc.model.RawTransactionInput; + +import bisq.network.p2p.NodeAddress; + +import bisq.common.app.Version; +import bisq.common.crypto.PubKeyRing; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +import lombok.EqualsAndHashCode; +import lombok.Getter; + +@EqualsAndHashCode(callSuper = true) +@Getter +public final class BuyersBsqSwapRequest extends BsqSwapRequest implements TxInputsMessage { + private final List bsqInputs; + private final long bsqChange; + private final String buyersBtcPayoutAddress; + private final String buyersBsqChangeAddress; + + // Data for take offer + Buyers data for tx + public BuyersBsqSwapRequest(String tradeId, + NodeAddress senderNodeAddress, + PubKeyRing takerPubKeyRing, + long tradeAmount, + long txFeePerVbyte, + long makerFee, + long takerFee, + long tradeDate, + List bsqInputs, + long bsqChange, + String buyersBtcPayoutAddress, + String buyersBsqChangeAddress) { + this(Version.getP2PMessageVersion(), + tradeId, + UUID.randomUUID().toString(), + senderNodeAddress, + takerPubKeyRing, + tradeAmount, + txFeePerVbyte, + makerFee, + takerFee, + tradeDate, + bsqInputs, + bsqChange, + buyersBtcPayoutAddress, + buyersBsqChangeAddress); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + private BuyersBsqSwapRequest(int messageVersion, + String tradeId, + String uid, + NodeAddress senderNodeAddress, + PubKeyRing takerPubKeyRing, + long tradeAmount, + long txFeePerVbyte, + long makerFee, + long takerFee, + long tradeDate, + List bsqInputs, + long bsqChange, + String buyersBtcPayoutAddress, + String buyersBsqChangeAddress) { + super(messageVersion, tradeId, uid, senderNodeAddress, takerPubKeyRing, + tradeAmount, txFeePerVbyte, makerFee, takerFee, tradeDate); + + this.bsqInputs = bsqInputs; + this.bsqChange = bsqChange; + this.buyersBtcPayoutAddress = buyersBtcPayoutAddress; + this.buyersBsqChangeAddress = buyersBsqChangeAddress; + } + + @Override + public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { + return getNetworkEnvelopeBuilder() + .setBuyersBsqSwapRequest(protobuf.BuyersBsqSwapRequest.newBuilder() + .setUid(uid) + .setTradeId(tradeId) + .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) + .setTakerPubKeyRing(takerPubKeyRing.toProtoMessage()) + .setTradeAmount(tradeAmount) + .setTxFeePerVbyte(txFeePerVbyte) + .setMakerFee(makerFee) + .setTakerFee(takerFee) + .setTradeDate(tradeDate) + .addAllBsqInputs(bsqInputs.stream().map(RawTransactionInput::toProtoMessage).collect( + Collectors.toList())) + .setBsqChange(bsqChange) + .setBuyersBtcPayoutAddress(buyersBtcPayoutAddress) + .setBuyersBsqChangeAddress(buyersBsqChangeAddress)) + .build(); + } + + public static BuyersBsqSwapRequest fromProto(protobuf.BuyersBsqSwapRequest proto, + int messageVersion) { + return new BuyersBsqSwapRequest(messageVersion, + proto.getTradeId(), + proto.getUid(), + NodeAddress.fromProto(proto.getSenderNodeAddress()), + PubKeyRing.fromProto(proto.getTakerPubKeyRing()), + proto.getTradeAmount(), + proto.getTxFeePerVbyte(), + proto.getMakerFee(), + proto.getTakerFee(), + proto.getTradeDate(), + proto.getBsqInputsList().stream() + .map(RawTransactionInput::fromProto) + .collect(Collectors.toList()), + proto.getBsqChange(), + proto.getBuyersBtcPayoutAddress(), + proto.getBuyersBsqChangeAddress()); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/messages/SellersBsqSwapRequest.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/messages/SellersBsqSwapRequest.java new file mode 100644 index 0000000000..64c4526e04 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/messages/SellersBsqSwapRequest.java @@ -0,0 +1,101 @@ +/* + * 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.core.trade.protocol.bsq_swap.messages; + +import bisq.network.p2p.NodeAddress; + +import bisq.common.app.Version; +import bisq.common.crypto.PubKeyRing; + +import java.util.UUID; + +import lombok.EqualsAndHashCode; +import lombok.Getter; + +@EqualsAndHashCode(callSuper = true) +@Getter +public final class SellersBsqSwapRequest extends BsqSwapRequest { + + public SellersBsqSwapRequest(String tradeId, + NodeAddress senderNodeAddress, + PubKeyRing takerPubKeyRing, + long tradeAmount, + long txFeePerVbyte, + long makerFee, + long takerFee, + long tradeDate) { + this(Version.getP2PMessageVersion(), + tradeId, + UUID.randomUUID().toString(), + senderNodeAddress, + takerPubKeyRing, + tradeAmount, + txFeePerVbyte, + makerFee, + takerFee, + tradeDate); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + private SellersBsqSwapRequest(int messageVersion, + String tradeId, + String uid, + NodeAddress senderNodeAddress, + PubKeyRing takerPubKeyRing, + long tradeAmount, + long txFeePerVbyte, + long makerFee, + long takerFee, + long tradeDate) { + super(messageVersion, tradeId, uid, senderNodeAddress, takerPubKeyRing, + tradeAmount, txFeePerVbyte, makerFee, takerFee, tradeDate); + } + + @Override + public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { + return getNetworkEnvelopeBuilder() + .setSellersBsqSwapRequest(protobuf.SellersBsqSwapRequest.newBuilder() + .setUid(uid) + .setTradeId(tradeId) + .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) + .setTakerPubKeyRing(takerPubKeyRing.toProtoMessage()) + .setTradeAmount(tradeAmount) + .setTxFeePerVbyte(txFeePerVbyte) + .setMakerFee(makerFee) + .setTakerFee(takerFee) + .setTradeDate(tradeDate)) + .build(); + } + + public static SellersBsqSwapRequest fromProto(protobuf.SellersBsqSwapRequest proto, + int messageVersion) { + return new SellersBsqSwapRequest(messageVersion, + proto.getTradeId(), + proto.getUid(), + NodeAddress.fromProto(proto.getSenderNodeAddress()), + PubKeyRing.fromProto(proto.getTakerPubKeyRing()), + proto.getTradeAmount(), + proto.getTxFeePerVbyte(), + proto.getMakerFee(), + proto.getTakerFee(), + proto.getTradeDate()); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/messages/TxInputsMessage.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/messages/TxInputsMessage.java new file mode 100644 index 0000000000..b760450fe2 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/messages/TxInputsMessage.java @@ -0,0 +1,34 @@ +/* + * 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.core.trade.protocol.bsq_swap.messages; + +import bisq.core.btc.model.RawTransactionInput; + +import bisq.network.p2p.DirectMessage; + +import java.util.List; + +public interface TxInputsMessage extends DirectMessage { + List getBsqInputs(); + + long getBsqChange(); + + String getBuyersBtcPayoutAddress(); + + String getBuyersBsqChangeAddress(); +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/model/BsqSwapProtocolModel.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/model/BsqSwapProtocolModel.java new file mode 100644 index 0000000000..bdf5dd88b2 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/model/BsqSwapProtocolModel.java @@ -0,0 +1,259 @@ +/* + * 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.core.trade.protocol.bsq_swap.model; + +import bisq.core.btc.model.RawTransactionInput; +import bisq.core.btc.wallet.BsqWalletService; +import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.TradeWalletService; +import bisq.core.btc.wallet.WalletsManager; +import bisq.core.dao.DaoFacade; +import bisq.core.filter.FilterManager; +import bisq.core.offer.Offer; +import bisq.core.offer.OpenOfferManager; +import bisq.core.trade.TradeManager; +import bisq.core.trade.protocol.ProtocolModel; +import bisq.core.trade.protocol.Provider; +import bisq.core.trade.protocol.TradeMessage; + +import bisq.network.p2p.NodeAddress; +import bisq.network.p2p.P2PService; + +import bisq.common.crypto.KeyRing; +import bisq.common.crypto.PubKeyRing; +import bisq.common.proto.ProtoUtil; +import bisq.common.util.Utilities; + +import com.google.protobuf.ByteString; + +import org.bitcoinj.core.Transaction; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.Nullable; + +// Fields marked as transient are only used during protocol execution which are based on directMessages so we do not +// persist them. + +/** + * This is the base model for the trade protocol. It is persisted with the trade (non transient fields). + * It uses the {@link Provider} for access to domain services. + */ + +@Getter +@Slf4j +public class BsqSwapProtocolModel implements ProtocolModel { + transient private Provider provider; + transient private TradeManager tradeManager; + transient private Offer offer; + @Setter + transient private TradeMessage tradeMessage; + @Nullable + @Setter + transient private NodeAddress tempTradingPeerNodeAddress; + @Nullable + private transient Transaction transaction; + + private final BsqSwapTradePeer tradePeer; + private final PubKeyRing pubKeyRing; + + @Setter + @Nullable + private String btcAddress; + @Setter + @Nullable + private String bsqAddress; + @Setter + @Nullable + private List inputs; + @Setter + private long change; + @Setter + private long payout; + @Setter + @Nullable + private byte[] tx; + @Setter + private long txFee; + + public BsqSwapProtocolModel(PubKeyRing pubKeyRing) { + this(pubKeyRing, new BsqSwapTradePeer()); + } + + public BsqSwapProtocolModel(PubKeyRing pubKeyRing, BsqSwapTradePeer tradePeer) { + this.pubKeyRing = pubKeyRing; + this.tradePeer = tradePeer != null ? tradePeer : new BsqSwapTradePeer(); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public protobuf.BsqSwapProtocolModel toProtoMessage() { + final protobuf.BsqSwapProtocolModel.Builder builder = protobuf.BsqSwapProtocolModel.newBuilder() + .setChange(change) + .setPayout(payout) + .setTradePeer(tradePeer.toProtoMessage()) + .setPubKeyRing(pubKeyRing.toProtoMessage()) + .setTxFee(txFee); + Optional.ofNullable(btcAddress).ifPresent(builder::setBtcAddress); + Optional.ofNullable(bsqAddress).ifPresent(builder::setBsqAddress); + Optional.ofNullable(inputs).ifPresent(e -> builder.addAllInputs( + ProtoUtil.collectionToProto(e, protobuf.RawTransactionInput.class))); + Optional.ofNullable(tx).ifPresent(e -> builder.setTx(ByteString.copyFrom(e))); + return builder.build(); + } + + public static BsqSwapProtocolModel fromProto(protobuf.BsqSwapProtocolModel proto) { + BsqSwapProtocolModel bsqSwapProtocolModel = new BsqSwapProtocolModel( + PubKeyRing.fromProto(proto.getPubKeyRing()), + BsqSwapTradePeer.fromProto(proto.getTradePeer())); + bsqSwapProtocolModel.setChange(proto.getChange()); + bsqSwapProtocolModel.setPayout(proto.getPayout()); + + bsqSwapProtocolModel.setBtcAddress(ProtoUtil.stringOrNullFromProto(proto.getBtcAddress())); + bsqSwapProtocolModel.setBsqAddress(ProtoUtil.stringOrNullFromProto(proto.getBsqAddress())); + List inputs = proto.getInputsList().isEmpty() ? + null : + proto.getInputsList().stream() + .map(RawTransactionInput::fromProto) + .collect(Collectors.toList()); + bsqSwapProtocolModel.setInputs(inputs); + bsqSwapProtocolModel.setTx(ProtoUtil.byteArrayOrNullFromProto(proto.getTx())); + bsqSwapProtocolModel.setTxFee(proto.getTxFee()); + return bsqSwapProtocolModel; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // TradeProtocolModel + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void applyTransient(Provider provider, + TradeManager tradeManager, + Offer offer) { + this.offer = offer; + this.provider = provider; + this.tradeManager = tradeManager; + } + + @Override + public P2PService getP2PService() { + return provider.getP2PService(); + } + + @Override + public NodeAddress getMyNodeAddress() { + return getP2PService().getAddress(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void onComplete() { + } + + public void applyTransaction(Transaction transaction) { + this.transaction = transaction; + tx = transaction.bitcoinSerialize(); + } + + @Nullable + public Transaction getTransaction() { + if (tx == null) { + return null; + } + if (transaction == null) { + Transaction deSerializedTx = getBsqWalletService().getTxFromSerializedTx(tx); + transaction = getBsqWalletService().getTransaction(deSerializedTx.getTxId()); + } + return transaction; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Delegates + /////////////////////////////////////////////////////////////////////////////////////////// + + public BsqWalletService getBsqWalletService() { + return provider.getBsqWalletService(); + } + + public BtcWalletService getBtcWalletService() { + return provider.getBtcWalletService(); + } + + public TradeWalletService getTradeWalletService() { + return provider.getTradeWalletService(); + } + + public WalletsManager getWalletsManager() { + return provider.getWalletsManager(); + } + + public DaoFacade getDaoFacade() { + return provider.getDaoFacade(); + } + + public KeyRing getKeyRing() { + return provider.getKeyRing(); + } + + public FilterManager getFilterManager() { + return provider.getFilterManager(); + } + + public OpenOfferManager getOpenOfferManager() { + return provider.getOpenOfferManager(); + } + + public String getOfferId() { + return offer.getId(); + } + + + @Override + public String toString() { + return "BsqSwapProtocolModel{" + + ",\r\n offer=" + offer + + ",\r\n tradeMessage=" + tradeMessage + + ",\r\n tempTradingPeerNodeAddress=" + tempTradingPeerNodeAddress + + ",\r\n transaction=" + getTransaction() + + ",\r\n tradePeer=" + tradePeer + + ",\r\n pubKeyRing=" + pubKeyRing + + ",\r\n btcAddress='" + btcAddress + '\'' + + ",\r\n bsqAddress='" + bsqAddress + '\'' + + ",\r\n inputs=" + inputs + + ",\r\n change=" + change + + ",\r\n payout=" + payout + + ",\r\n tx=" + Utilities.encodeToHex(tx) + + ",\r\n txFee=" + txFee + + "\r\n}"; + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/model/BsqSwapTradePeer.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/model/BsqSwapTradePeer.java new file mode 100644 index 0000000000..d946c1c114 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/model/BsqSwapTradePeer.java @@ -0,0 +1,115 @@ +/* + * 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.core.trade.protocol.bsq_swap.model; + +import bisq.core.btc.model.RawTransactionInput; +import bisq.core.trade.protocol.TradePeer; + +import bisq.common.crypto.PubKeyRing; +import bisq.common.proto.ProtoUtil; +import bisq.common.util.Utilities; + +import com.google.protobuf.ByteString; + +import org.bitcoinj.core.TransactionInput; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.Nullable; + +@Slf4j +@Getter +@Setter +public final class BsqSwapTradePeer implements TradePeer { + @Nullable + private PubKeyRing pubKeyRing; + @Nullable + private String btcAddress; + @Nullable + private String bsqAddress; + + @Nullable + private List inputs; + private long change; + private long payout; + @Nullable + @Setter + private byte[] tx; + @Nullable + transient private List transactionInputs; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public protobuf.BsqSwapTradePeer toProtoMessage() { + final protobuf.BsqSwapTradePeer.Builder builder = protobuf.BsqSwapTradePeer.newBuilder() + .setChange(change) + .setPayout(payout); + Optional.ofNullable(pubKeyRing).ifPresent(e -> builder.setPubKeyRing(e.toProtoMessage())); + Optional.ofNullable(btcAddress).ifPresent(builder::setBtcAddress); + Optional.ofNullable(bsqAddress).ifPresent(builder::setBsqAddress); + Optional.ofNullable(inputs).ifPresent(e -> builder.addAllInputs( + ProtoUtil.collectionToProto(e, protobuf.RawTransactionInput.class))); + Optional.ofNullable(tx).ifPresent(e -> builder.setTx(ByteString.copyFrom(e))); + return builder.build(); + } + + public static BsqSwapTradePeer fromProto(protobuf.BsqSwapTradePeer proto) { + if (proto.getDefaultInstanceForType().equals(proto)) { + return null; + } else { + BsqSwapTradePeer bsqSwapTradePeer = new BsqSwapTradePeer(); + bsqSwapTradePeer.setPubKeyRing(proto.hasPubKeyRing() ? PubKeyRing.fromProto(proto.getPubKeyRing()) : null); + bsqSwapTradePeer.setChange(proto.getChange()); + bsqSwapTradePeer.setPayout(proto.getPayout()); + bsqSwapTradePeer.setBtcAddress(proto.getBtcAddress()); + bsqSwapTradePeer.setBsqAddress(proto.getBsqAddress()); + List inputs = proto.getInputsList().isEmpty() ? + null : + proto.getInputsList().stream() + .map(RawTransactionInput::fromProto) + .collect(Collectors.toList()); + bsqSwapTradePeer.setInputs(inputs); + bsqSwapTradePeer.setTx(ProtoUtil.byteArrayOrNullFromProto(proto.getTx())); + return bsqSwapTradePeer; + } + } + + + @Override + public String toString() { + return "BsqSwapTradePeer{" + + "\r\n pubKeyRing=" + pubKeyRing + + ",\r\n btcAddress='" + btcAddress + '\'' + + ",\r\n bsqAddress='" + bsqAddress + '\'' + + ",\r\n inputs=" + inputs + + ",\r\n change=" + change + + ",\r\n payout=" + payout + + ",\r\n tx=" + Utilities.encodeToHex(tx) + + "\r\n}"; + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/package-info.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/package-info.java new file mode 100644 index 0000000000..c4b0fa3a25 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/package-info.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.core.trade.protocol.bsq_swap; + +/* + * There are 2 possible pairs of protocols: + * 1. BuyerAsTaker + SellerAsMaker + * 2. SellerAsTaker + BuyerAsMaker + * + * The Taker always initiates the protocol and sends some basic data like amount, date, + * feeRate,... to the maker. + * + * For the tx creation the buyer/seller perspective is the relevant view. + * + * 1. BuyerAsTaker + SellerAsMaker: + * + * BuyerAsTaker: + * - ApplyFilter + * - BuyerAsTakerCreatesBsqInputsAndChange + * - SendBuyersBsqSwapRequest + * + * SellerAsMaker: + * - ApplyFilter + * - ProcessBuyersBsqSwapRequest + * - SellerAsMakerCreatesAndSignsTx + * - SellerAsMakerSetupTxListener + * - SendBsqSwapFinalizeTxRequest + * + * BuyerAsTaker: + * - BuyerAsTakerProcessBsqSwapFinalizeTxRequest + * - BuyerAsTakerCreatesAndSignsFinalizedTx + * - BuyerPublishesTx + * - PublishTradeStatistics + * - SendFinalizedTxMessage + * + * SellerAsMaker: + * - SellerAsMakerProcessBsqSwapFinalizedTxMessage + * - SellerMaybePublishesTx + * + * + * 2. SellerAsTaker + BuyerAsMaker: + * + * SellerAsTaker: + * - ApplyFilter + * - SendSellersBsqSwapRequest + * + * BuyerAsMaker + * - ApplyFilter + * - ProcessSellersBsqSwapRequest + * - BuyerAsMakerCreatesBsqInputsAndChange + * - SendBsqSwapTxInputsMessage + * + * SellerAsTaker: + * - ProcessBsqSwapTxInputsMessage + * - SellerAsTakerCreatesAndSignsTx + * - SellerAsTakerSetupTxListener + * - SendBsqSwapFinalizeTxRequest + * + * BuyerAsMaker: + * - BuyerAsMakerProcessBsqSwapFinalizeTxRequest + * - BuyerAsMakerCreatesAndSignsFinalizedTx + * - BuyerPublishesTx + * - BuyerAsMakerRemoveOpenOffer + * - PublishTradeStatistics + * - SendFinalizedTxMessage + * + * SellerAsTaker: + * - SellerAsTakerProcessBsqSwapFinalizedTxMessage + * - SellerMaybePublishesTx + * + */ diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/ApplyFilter.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/ApplyFilter.java new file mode 100644 index 0000000000..41da92f28d --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/ApplyFilter.java @@ -0,0 +1,51 @@ +/* + * 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.core.trade.protocol.bsq_swap.tasks; + +import bisq.core.trade.bisq_v1.TradeUtil; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; + +import bisq.common.taskrunner.TaskRunner; + +import java.util.Objects; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ApplyFilter extends BsqSwapTask { + public ApplyFilter(TaskRunner taskHandler, BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + TradeUtil.applyFilter(trade, + protocolModel.getFilterManager(), + Objects.requireNonNull(trade.getTradingPeerNodeAddress()), + null, + this::complete, + this::failed); + } catch (Throwable t) { + failed(t); + } + } +} + diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/BsqSwapTask.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/BsqSwapTask.java new file mode 100644 index 0000000000..ffbf7bd7c2 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/BsqSwapTask.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.core.trade.protocol.bsq_swap.tasks; + +import bisq.core.trade.model.TradeModel; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.model.BsqSwapProtocolModel; + +import bisq.common.taskrunner.Task; +import bisq.common.taskrunner.TaskRunner; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public abstract class BsqSwapTask extends Task { + protected final BsqSwapProtocolModel protocolModel; + protected final BsqSwapTrade trade; + + protected BsqSwapTask(TaskRunner taskHandler, BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + + this.trade = bsqSwapTrade; + protocolModel = bsqSwapTrade.getBsqSwapProtocolModel(); + } + + @Override + protected void complete() { + protocolModel.getTradeManager().requestPersistence(); + + super.complete(); + } + + @Override + protected void failed() { + trade.setErrorMessage(errorMessage); + protocolModel.getTradeManager().requestPersistence(); + + super.failed(); + } + + @Override + protected void failed(String message) { + appendToErrorMessage(message); + trade.setErrorMessage(errorMessage); + protocolModel.getTradeManager().requestPersistence(); + + super.failed(); + } + + @Override + protected void failed(Throwable t) { + t.printStackTrace(); + appendExceptionToErrorMessage(t); + trade.setErrorMessage(errorMessage); + protocolModel.getTradeManager().requestPersistence(); + + super.failed(); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/SendBsqSwapMessageTask.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/SendBsqSwapMessageTask.java new file mode 100644 index 0000000000..60a0398f5c --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/SendBsqSwapMessageTask.java @@ -0,0 +1,68 @@ +/* + * 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.core.trade.protocol.bsq_swap.tasks; + +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.TradeMessage; + +import bisq.network.p2p.NodeAddress; +import bisq.network.p2p.SendDirectMessageListener; + +import bisq.common.taskrunner.TaskRunner; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public abstract class SendBsqSwapMessageTask extends BsqSwapTask { + + @SuppressWarnings({"unused"}) + public SendBsqSwapMessageTask(TaskRunner taskHandler, BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + protected void send(TradeMessage message) { + NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress(); + log.info("Send {} to peer {}. tradeId={}, uid={}", + message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); + protocolModel.getP2PService().sendEncryptedDirectMessage( + peersNodeAddress, + protocolModel.getTradePeer().getPubKeyRing(), + message, + new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived at peer {}. tradeId={}, uid={}", + message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), + message.getUid()); + complete(); + } + + @Override + public void onFault(String errorMessage) { + log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}", + message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), + message.getUid(), errorMessage); + + appendToErrorMessage("Sending request failed: request=" + message + "\nerrorMessage=" + + errorMessage); + failed(); + } + } + ); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer/BuyerCreatesAndSignsFinalizedTx.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer/BuyerCreatesAndSignsFinalizedTx.java new file mode 100644 index 0000000000..9aa8747382 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer/BuyerCreatesAndSignsFinalizedTx.java @@ -0,0 +1,105 @@ +/* + * 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.core.trade.protocol.bsq_swap.tasks.buyer; + +import bisq.core.btc.model.RawTransactionInput; +import bisq.core.trade.bsq_swap.BsqSwapCalculation; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.model.BsqSwapTradePeer; +import bisq.core.trade.protocol.bsq_swap.tasks.BsqSwapTask; + +import bisq.common.taskrunner.TaskRunner; + +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.Transaction; +import org.bitcoinj.core.TransactionInput; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import lombok.extern.slf4j.Slf4j; + +import static com.google.common.base.Preconditions.checkArgument; + +@Slf4j +public abstract class BuyerCreatesAndSignsFinalizedTx extends BsqSwapTask { + @SuppressWarnings({"unused"}) + public BuyerCreatesAndSignsFinalizedTx(TaskRunner taskHandler, BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + BsqSwapTradePeer tradePeer = protocolModel.getTradePeer(); + + List buyersBsqInputs = Objects.requireNonNull(protocolModel.getInputs()); + List sellersBtcInputs = tradePeer.getTransactionInputs(); + + Coin sellersBsqPayoutAmount = BsqSwapCalculation.getSellersBsqPayoutValue(trade, getSellersTradeFee()); + String sellersBsqPayoutAddress = tradePeer.getBsqAddress(); + + Coin buyersBsqChangeAmount = Coin.valueOf(protocolModel.getChange()); + String buyersBsqChangeAddress = protocolModel.getBsqAddress(); + + Coin buyersBtcPayoutAmount = Coin.valueOf(protocolModel.getPayout()); + String buyersBtcPayoutAddress = protocolModel.getBtcAddress(); + + Coin sellersBtcChangeAmount = Coin.valueOf(tradePeer.getChange()); + String sellersBtcChangeAddress = tradePeer.getBtcAddress(); + + Transaction transaction = protocolModel.getTradeWalletService().buyerBuildBsqSwapTx( + buyersBsqInputs, + sellersBtcInputs, + sellersBsqPayoutAmount, + sellersBsqPayoutAddress, + buyersBsqChangeAmount, + buyersBsqChangeAddress, + buyersBtcPayoutAmount, + buyersBtcPayoutAddress, + sellersBtcChangeAmount, + sellersBtcChangeAddress); + + // We cross check if the peers tx matches ours. If not the tx would be invalid anyway as we would have + // signed different transactions. + checkArgument(Arrays.equals(transaction.bitcoinSerialize(), tradePeer.getTx()), + "Buyers unsigned transaction does not match the sellers tx"); + + // Sign my inputs + int buyersInputSize = buyersBsqInputs.size(); + List myInputs = transaction.getInputs().stream() + .filter(input -> input.getIndex() < buyersInputSize) + .collect(Collectors.toList()); + protocolModel.getBsqWalletService().signBsqSwapTransaction(transaction, myInputs); + + log.info("Fully signed BSQ swap transaction {}", transaction); + protocolModel.applyTransaction(transaction); + protocolModel.getTradeManager().requestPersistence(); + + complete(); + } catch (Throwable t) { + failed(t); + } + } + + protected abstract long getSellersTradeFee(); + + protected abstract long getBuyersTradeFee(); +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer/BuyerCreatesBsqInputsAndChange.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer/BuyerCreatesBsqInputsAndChange.java new file mode 100644 index 0000000000..fc86eb37f3 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer/BuyerCreatesBsqInputsAndChange.java @@ -0,0 +1,84 @@ +/* + * 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.core.trade.protocol.bsq_swap.tasks.buyer; + +import bisq.core.btc.model.RawTransactionInput; +import bisq.core.btc.wallet.BsqWalletService; +import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.Restrictions; +import bisq.core.trade.bsq_swap.BsqSwapCalculation; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.tasks.BsqSwapTask; + +import bisq.common.taskrunner.TaskRunner; +import bisq.common.util.Tuple2; + +import org.bitcoinj.core.Coin; + +import java.util.List; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public abstract class BuyerCreatesBsqInputsAndChange extends BsqSwapTask { + + @SuppressWarnings({"unused"}) + public BuyerCreatesBsqInputsAndChange(TaskRunner taskHandler, BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + BsqWalletService bsqWalletService = protocolModel.getBsqWalletService(); + BtcWalletService btcWalletService = protocolModel.getBtcWalletService(); + + long buyersTradeFee = getBuyersTradeFee(); + Coin required = BsqSwapCalculation.getBuyersBsqInputValue(trade, buyersTradeFee); + Tuple2, Coin> inputsAndChange = bsqWalletService.getBuyersBsqInputsForBsqSwapTx(required); + + List inputs = inputsAndChange.first; + protocolModel.setInputs(inputs); + + long change = inputsAndChange.second.value; + if (Restrictions.isDust(Coin.valueOf(change))) { + // If change would be dust we give spend it as miner fees + change = 0; + } + protocolModel.setChange(change); + + protocolModel.setBsqAddress(bsqWalletService.getUnusedAddress().toString()); + protocolModel.setBtcAddress(btcWalletService.getFreshAddressEntry().getAddressString()); + + int buyersTxSize = BsqSwapCalculation.getVBytesSize(inputs, change); + long btcPayout = BsqSwapCalculation.getBuyersBtcPayoutValue(trade, buyersTxSize, buyersTradeFee).getValue(); + protocolModel.setPayout(btcPayout); + + long buyersTxFee = BsqSwapCalculation.getAdjustedTxFee(trade.getTxFeePerVbyte(), buyersTxSize, buyersTradeFee); + protocolModel.setTxFee(buyersTxFee); + + complete(); + } catch (Throwable t) { + failed(t); + } + } + + protected abstract long getSellersTradeFee(); + + protected abstract long getBuyersTradeFee(); +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer/BuyerPublishesTx.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer/BuyerPublishesTx.java new file mode 100644 index 0000000000..3106591d93 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer/BuyerPublishesTx.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.core.trade.protocol.bsq_swap.tasks.buyer; + +import bisq.core.btc.exceptions.TxBroadcastException; +import bisq.core.btc.wallet.TxBroadcaster; +import bisq.core.dao.state.model.blockchain.TxType; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.tasks.BsqSwapTask; + +import bisq.common.taskrunner.TaskRunner; + +import org.bitcoinj.core.Transaction; + +import java.util.Objects; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class BuyerPublishesTx extends BsqSwapTask { + @SuppressWarnings({"unused"}) + public BuyerPublishesTx(TaskRunner taskHandler, BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + Transaction transaction = Objects.requireNonNull(protocolModel.getTransaction()); + Transaction walletTx = protocolModel.getTradeWalletService().getWalletTx(transaction.getTxId()); + if (walletTx != null) { + log.warn("We have received already the tx in our wallet. This is not expected."); + + if (trade.getState() == BsqSwapTrade.State.PREPARATION) { + trade.applyTransaction(walletTx); + trade.setState(BsqSwapTrade.State.COMPLETED); + protocolModel.getTradeManager().onBsqSwapTradeCompleted(trade); + protocolModel.getTradeManager().requestPersistence(); + } + + complete(); + return; + } + + protocolModel.getWalletsManager().publishAndCommitBsqTx(transaction, + TxType.TRANSFER_BSQ, + new TxBroadcaster.Callback() { + @Override + public void onSuccess(Transaction transaction) { + trade.applyTransaction(transaction); + trade.setState(BsqSwapTrade.State.COMPLETED); + protocolModel.getTradeManager().onBsqSwapTradeCompleted(trade); + protocolModel.getTradeManager().requestPersistence(); + + if (!completed) { + complete(); + } else { + log.warn("We got the onSuccess callback called after the timeout has been triggered a complete()."); + } + } + + @Override + public void onFailure(TxBroadcastException exception) { + if (!completed) { + failed(exception); + } else { + log.warn("We got the onFailure callback called after the timeout has been triggered a complete()."); + } + } + }); + complete(); + } catch (Throwable t) { + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer/ProcessBsqSwapFinalizeTxRequest.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer/ProcessBsqSwapFinalizeTxRequest.java new file mode 100644 index 0000000000..e7a4ecd9fa --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer/ProcessBsqSwapFinalizeTxRequest.java @@ -0,0 +1,145 @@ +/* + * 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.core.trade.protocol.bsq_swap.tasks.buyer; + +import bisq.core.btc.model.RawTransactionInput; +import bisq.core.btc.wallet.Restrictions; +import bisq.core.trade.bsq_swap.BsqSwapCalculation; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.messages.BsqSwapFinalizeTxRequest; +import bisq.core.trade.protocol.bsq_swap.model.BsqSwapTradePeer; +import bisq.core.trade.protocol.bsq_swap.tasks.BsqSwapTask; +import bisq.core.util.Validator; + +import bisq.common.taskrunner.TaskRunner; + +import org.bitcoinj.core.Address; +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.NetworkParameters; +import org.bitcoinj.core.Transaction; +import org.bitcoinj.core.TransactionInput; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import lombok.extern.slf4j.Slf4j; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * We cannot verify the sellers inputs if they really exist as we do not have the blockchain data for it. + * Worst case would be that the seller pays less for miner fee as expected and thus risks to get the tx never confirmed. + * The change output cannot be verified exactly due potential dust values and non-deterministic behaviour of the + * fee estimation. + * The important values for out BTC output and out BSQ change output are set already in BuyerCreatesBsqInputsAndChange + * and are not related to the data provided by the peer. If the peers inputs would not be sufficient the tx would + * fail anyway. + */ +@Slf4j +public abstract class ProcessBsqSwapFinalizeTxRequest extends BsqSwapTask { + @SuppressWarnings({"unused"}) + public ProcessBsqSwapFinalizeTxRequest(TaskRunner taskHandler, BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + BsqSwapFinalizeTxRequest request = checkNotNull((BsqSwapFinalizeTxRequest) protocolModel.getTradeMessage()); + checkNotNull(request); + Validator.checkTradeId(protocolModel.getOfferId(), request); + + // We will use only the sellers buyersBsqInputs from the tx so we do not verify anything else + byte[] tx = request.getTx(); + Transaction sellersTransaction = protocolModel.getBtcWalletService().getTxFromSerializedTx(tx); + List sellersRawBtcInputs = request.getBtcInputs(); + checkArgument(!sellersRawBtcInputs.isEmpty(), "Sellers BTC buyersBsqInputs must not be empty"); + + List buyersBsqInputs = protocolModel.getInputs(); + int buyersInputSize = Objects.requireNonNull(buyersBsqInputs).size(); + List sellersBtcInputs = sellersTransaction.getInputs().stream() + .filter(input -> input.getIndex() >= buyersInputSize) + .collect(Collectors.toList()); + checkArgument(sellersBtcInputs.size() == sellersRawBtcInputs.size(), + "Number of buyersBsqInputs in tx must match the number of sellersRawBtcInputs"); + + boolean hasUnSignedInputs = sellersBtcInputs.stream() + .anyMatch(input -> input.getScriptSig() == null && !input.hasWitness()); + checkArgument(!hasUnSignedInputs, "SellersBtcInputs from tx has unsigned inputs"); + + long change = request.getBtcChange(); + checkArgument(change == 0 || Restrictions.isAboveDust(Coin.valueOf(change)), + "BTC change must be 0 or above dust"); + + long sumInputs = sellersRawBtcInputs.stream().mapToLong(input -> input.value).sum(); + int sellersTxSize = BsqSwapCalculation.getVBytesSize(sellersRawBtcInputs, change); + long sellersBtcInputAmount = BsqSwapCalculation.getSellersBtcInputValue(trade, sellersTxSize, getSellersTradeFee()).getValue(); + // It can be that there have been dust change which got added to miner fees, so sumInputs could be a bit larger. + checkArgument(sumInputs >= sellersBtcInputAmount, + "Sellers BTC input amount do not match our calculated required BTC input amount"); + + int buyersTxSize = BsqSwapCalculation.getVBytesSize(buyersBsqInputs, protocolModel.getChange()); + long txFeePerVbyte = trade.getTxFeePerVbyte(); + long buyersTxFee = BsqSwapCalculation.getAdjustedTxFee(txFeePerVbyte, buyersTxSize, getBuyersTradeFee()); + long sellersTxFee = BsqSwapCalculation.getAdjustedTxFee(txFeePerVbyte, sellersTxSize, getSellersTradeFee()); + long buyersBtcPayout = protocolModel.getPayout(); + long expectedChange = sumInputs - buyersBtcPayout - sellersTxFee - buyersTxFee; + boolean isChangeAboveDust = Restrictions.isAboveDust(Coin.valueOf(expectedChange)); + if (expectedChange != change && isChangeAboveDust) { + log.warn("Sellers BTC change is not as expected. This can happen if fee estimation for buyersBsqInputs did not " + + "succeed (e.g. dust change, max. iterations reached,..."); + log.warn("buyersBtcPayout={}, sumInputs={}, sellersTxFee={}, buyersTxFee={}, expectedChange={}, change={}", + buyersBtcPayout, sumInputs, sellersTxFee, buyersTxFee, expectedChange, change); + } + // By enforcing that it must not be larger than expectedChange we guarantee that peer did not cheat on + // tx fees. + checkArgument(change <= expectedChange, + "Change must be smaller or equal to expectedChange"); + + NetworkParameters params = protocolModel.getBtcWalletService().getParams(); + String sellersBsqPayoutAddress = request.getBsqPayoutAddress(); + checkNotNull(sellersBsqPayoutAddress, "sellersBsqPayoutAddress must not be null"); + checkArgument(!sellersBsqPayoutAddress.isEmpty(), "sellersBsqPayoutAddress must not be empty"); + Address.fromString(params, sellersBsqPayoutAddress); // If address is not a BTC address it throws an exception + + String sellersBtcChangeAddress = request.getBtcChangeAddress(); + checkNotNull(sellersBtcChangeAddress, "sellersBtcChangeAddress must not be null"); + checkArgument(!sellersBtcChangeAddress.isEmpty(), "sellersBtcChangeAddress must not be empty"); + Address.fromString(params, sellersBtcChangeAddress); // If address is not a BTC address it throws an exception + + // Apply data + BsqSwapTradePeer tradePeer = protocolModel.getTradePeer(); + tradePeer.setTx(tx); + tradePeer.setTransactionInputs(sellersBtcInputs); + tradePeer.setInputs(sellersRawBtcInputs); + tradePeer.setChange(change); + tradePeer.setBtcAddress(sellersBtcChangeAddress); + tradePeer.setBsqAddress(sellersBsqPayoutAddress); + + complete(); + } catch (Throwable t) { + failed(t); + } + } + + protected abstract long getBuyersTradeFee(); + + protected abstract long getSellersTradeFee(); +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer/PublishTradeStatistics.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer/PublishTradeStatistics.java new file mode 100644 index 0000000000..f8b1d3d3cf --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer/PublishTradeStatistics.java @@ -0,0 +1,63 @@ +/* + * 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.core.trade.protocol.bsq_swap.tasks.buyer; + +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.tasks.BsqSwapTask; +import bisq.core.trade.statistics.TradeStatistics3; + +import bisq.common.app.Capability; +import bisq.common.taskrunner.TaskRunner; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class PublishTradeStatistics extends BsqSwapTask { + @SuppressWarnings({"unused"}) + public PublishTradeStatistics(TaskRunner taskHandler, BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + protocolModel.getP2PService().findPeersCapabilities(trade.getTradingPeerNodeAddress()) + .filter(capabilities -> capabilities.containsAll(Capability.TRADE_STATISTICS_3)) + .ifPresentOrElse(capabilities -> { + TradeStatistics3 tradeStatistics = TradeStatistics3.from(trade); + if (tradeStatistics.isValid()) { + log.info("Publishing trade statistics"); + protocolModel.getP2PService().addPersistableNetworkPayload(tradeStatistics, true); + } else { + log.warn("Trade statistics are invalid. We do not publish. {}", tradeStatistics); + } + + complete(); + }, + () -> { + log.info("Our peer does not has updated yet, so they will publish the trade statistics. " + + "To avoid duplicates we do not publish from our side."); + complete(); + }); + } catch (Throwable t) { + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer/SendFinalizedTxMessage.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer/SendFinalizedTxMessage.java new file mode 100644 index 0000000000..cada3e20f6 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer/SendFinalizedTxMessage.java @@ -0,0 +1,51 @@ +/* + * 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.core.trade.protocol.bsq_swap.tasks.buyer; + +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.messages.BsqSwapFinalizedTxMessage; +import bisq.core.trade.protocol.bsq_swap.tasks.SendBsqSwapMessageTask; + +import bisq.common.taskrunner.TaskRunner; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class SendFinalizedTxMessage extends SendBsqSwapMessageTask { + + @SuppressWarnings({"unused"}) + public SendFinalizedTxMessage(TaskRunner taskHandler, BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + BsqSwapFinalizedTxMessage message = new BsqSwapFinalizedTxMessage( + protocolModel.getOfferId(), + protocolModel.getMyNodeAddress(), + protocolModel.getTx()); + + send(message); + } catch (Throwable t) { + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer_as_maker/BuyerAsMakerCreatesAndSignsFinalizedTx.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer_as_maker/BuyerAsMakerCreatesAndSignsFinalizedTx.java new file mode 100644 index 0000000000..2308f5e5ec --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer_as_maker/BuyerAsMakerCreatesAndSignsFinalizedTx.java @@ -0,0 +1,55 @@ +/* + * 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.core.trade.protocol.bsq_swap.tasks.buyer_as_maker; + +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.tasks.buyer.BuyerCreatesAndSignsFinalizedTx; + +import bisq.common.taskrunner.TaskRunner; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class BuyerAsMakerCreatesAndSignsFinalizedTx extends BuyerCreatesAndSignsFinalizedTx { + @SuppressWarnings({"unused"}) + public BuyerAsMakerCreatesAndSignsFinalizedTx(TaskRunner taskHandler, + BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + super.run(); + } catch (Throwable t) { + failed(t); + } + } + + @Override + protected long getSellersTradeFee() { + return trade.getTakerFeeAsLong(); + } + + @Override + protected long getBuyersTradeFee() { + return trade.getMakerFeeAsLong(); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer_as_maker/BuyerAsMakerCreatesBsqInputsAndChange.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer_as_maker/BuyerAsMakerCreatesBsqInputsAndChange.java new file mode 100644 index 0000000000..589b06e7cb --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer_as_maker/BuyerAsMakerCreatesBsqInputsAndChange.java @@ -0,0 +1,55 @@ +/* + * 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.core.trade.protocol.bsq_swap.tasks.buyer_as_maker; + +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.tasks.buyer.BuyerCreatesBsqInputsAndChange; + +import bisq.common.taskrunner.TaskRunner; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class BuyerAsMakerCreatesBsqInputsAndChange extends BuyerCreatesBsqInputsAndChange { + + @SuppressWarnings({"unused"}) + public BuyerAsMakerCreatesBsqInputsAndChange(TaskRunner taskHandler, BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + super.run(); + } catch (Throwable t) { + failed(t); + } + } + + @Override + protected long getBuyersTradeFee() { + return trade.getMakerFeeAsLong(); + } + + @Override + protected long getSellersTradeFee() { + return trade.getTakerFeeAsLong(); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer_as_maker/BuyerAsMakerProcessBsqSwapFinalizeTxRequest.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer_as_maker/BuyerAsMakerProcessBsqSwapFinalizeTxRequest.java new file mode 100644 index 0000000000..541a1b007c --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer_as_maker/BuyerAsMakerProcessBsqSwapFinalizeTxRequest.java @@ -0,0 +1,55 @@ +/* + * 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.core.trade.protocol.bsq_swap.tasks.buyer_as_maker; + +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.tasks.buyer.ProcessBsqSwapFinalizeTxRequest; + +import bisq.common.taskrunner.TaskRunner; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class BuyerAsMakerProcessBsqSwapFinalizeTxRequest extends ProcessBsqSwapFinalizeTxRequest { + @SuppressWarnings({"unused"}) + public BuyerAsMakerProcessBsqSwapFinalizeTxRequest(TaskRunner taskHandler, + BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + super.run(); + } catch (Throwable t) { + failed(t); + } + } + + @Override + protected long getSellersTradeFee() { + return trade.getTakerFeeAsLong(); + } + + @Override + protected long getBuyersTradeFee() { + return trade.getMakerFeeAsLong(); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer_as_maker/BuyerAsMakerRemoveOpenOffer.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer_as_maker/BuyerAsMakerRemoveOpenOffer.java new file mode 100644 index 0000000000..71a6059fcd --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer_as_maker/BuyerAsMakerRemoveOpenOffer.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.core.trade.protocol.bsq_swap.tasks.buyer_as_maker; + +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.tasks.BsqSwapTask; + +import bisq.common.taskrunner.TaskRunner; + +import lombok.extern.slf4j.Slf4j; + +import static com.google.common.base.Preconditions.checkNotNull; + +@Slf4j +public class BuyerAsMakerRemoveOpenOffer extends BsqSwapTask { + public BuyerAsMakerRemoveOpenOffer(TaskRunner taskHandler, BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + protocolModel.getOpenOfferManager().closeOpenOffer(checkNotNull(trade.getOffer())); + + complete(); + } catch (Throwable t) { + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer_as_maker/ProcessSellersBsqSwapRequest.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer_as_maker/ProcessSellersBsqSwapRequest.java new file mode 100644 index 0000000000..cf89336891 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer_as_maker/ProcessSellersBsqSwapRequest.java @@ -0,0 +1,55 @@ +/* + * 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.core.trade.protocol.bsq_swap.tasks.buyer_as_maker; + +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.messages.SellersBsqSwapRequest; +import bisq.core.trade.protocol.bsq_swap.tasks.BsqSwapTask; + +import bisq.common.crypto.PubKeyRing; +import bisq.common.taskrunner.TaskRunner; + +import lombok.extern.slf4j.Slf4j; + +import static com.google.common.base.Preconditions.checkNotNull; + +@Slf4j +public class ProcessSellersBsqSwapRequest extends BsqSwapTask { + + @SuppressWarnings({"unused"}) + public ProcessSellersBsqSwapRequest(TaskRunner taskHandler, BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + // Trade data are already verified at BsqSwapTakeOfferRequestVerification + SellersBsqSwapRequest request = checkNotNull((SellersBsqSwapRequest) protocolModel.getTradeMessage()); + + PubKeyRing pubKeyRing = checkNotNull(request.getTakerPubKeyRing(), "pubKeyRing must not be null"); + protocolModel.getTradePeer().setPubKeyRing(pubKeyRing); + + complete(); + } catch (Throwable t) { + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer_as_maker/SendBsqSwapTxInputsMessage.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer_as_maker/SendBsqSwapTxInputsMessage.java new file mode 100644 index 0000000000..57b982a885 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer_as_maker/SendBsqSwapTxInputsMessage.java @@ -0,0 +1,54 @@ +/* + * 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.core.trade.protocol.bsq_swap.tasks.buyer_as_maker; + +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.messages.BsqSwapTxInputsMessage; +import bisq.core.trade.protocol.bsq_swap.tasks.SendBsqSwapMessageTask; + +import bisq.common.taskrunner.TaskRunner; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class SendBsqSwapTxInputsMessage extends SendBsqSwapMessageTask { + + @SuppressWarnings({"unused"}) + public SendBsqSwapTxInputsMessage(TaskRunner taskHandler, BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + BsqSwapTxInputsMessage message = new BsqSwapTxInputsMessage( + protocolModel.getOfferId(), + protocolModel.getMyNodeAddress(), + protocolModel.getInputs(), + protocolModel.getChange(), + protocolModel.getBtcAddress(), + protocolModel.getBsqAddress()); + + send(message); + } catch (Throwable t) { + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer_as_taker/BuyerAsTakerCreatesAndSignsFinalizedTx.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer_as_taker/BuyerAsTakerCreatesAndSignsFinalizedTx.java new file mode 100644 index 0000000000..6c42094713 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer_as_taker/BuyerAsTakerCreatesAndSignsFinalizedTx.java @@ -0,0 +1,54 @@ +/* + * 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.core.trade.protocol.bsq_swap.tasks.buyer_as_taker; + +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.tasks.buyer.BuyerCreatesAndSignsFinalizedTx; + +import bisq.common.taskrunner.TaskRunner; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class BuyerAsTakerCreatesAndSignsFinalizedTx extends BuyerCreatesAndSignsFinalizedTx { + @SuppressWarnings({"unused"}) + public BuyerAsTakerCreatesAndSignsFinalizedTx(TaskRunner taskHandler, BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + super.run(); + } catch (Throwable t) { + failed(t); + } + } + + @Override + protected long getSellersTradeFee() { + return trade.getMakerFeeAsLong(); + } + + @Override + protected long getBuyersTradeFee() { + return trade.getTakerFeeAsLong(); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer_as_taker/BuyerAsTakerCreatesBsqInputsAndChange.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer_as_taker/BuyerAsTakerCreatesBsqInputsAndChange.java new file mode 100644 index 0000000000..4b702c0436 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer_as_taker/BuyerAsTakerCreatesBsqInputsAndChange.java @@ -0,0 +1,55 @@ +/* + * 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.core.trade.protocol.bsq_swap.tasks.buyer_as_taker; + +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.tasks.buyer.BuyerCreatesBsqInputsAndChange; + +import bisq.common.taskrunner.TaskRunner; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class BuyerAsTakerCreatesBsqInputsAndChange extends BuyerCreatesBsqInputsAndChange { + + @SuppressWarnings({"unused"}) + public BuyerAsTakerCreatesBsqInputsAndChange(TaskRunner taskHandler, BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + super.run(); + } catch (Throwable t) { + failed(t); + } + } + + @Override + protected long getBuyersTradeFee() { + return trade.getTakerFeeAsLong(); + } + + @Override + protected long getSellersTradeFee() { + return trade.getMakerFeeAsLong(); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer_as_taker/BuyerAsTakerProcessBsqSwapFinalizeTxRequest.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer_as_taker/BuyerAsTakerProcessBsqSwapFinalizeTxRequest.java new file mode 100644 index 0000000000..be911487cd --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer_as_taker/BuyerAsTakerProcessBsqSwapFinalizeTxRequest.java @@ -0,0 +1,55 @@ +/* + * 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.core.trade.protocol.bsq_swap.tasks.buyer_as_taker; + +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.tasks.buyer.ProcessBsqSwapFinalizeTxRequest; + +import bisq.common.taskrunner.TaskRunner; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class BuyerAsTakerProcessBsqSwapFinalizeTxRequest extends ProcessBsqSwapFinalizeTxRequest { + @SuppressWarnings({"unused"}) + public BuyerAsTakerProcessBsqSwapFinalizeTxRequest(TaskRunner taskHandler, + BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + super.run(); + } catch (Throwable t) { + failed(t); + } + } + + @Override + protected long getSellersTradeFee() { + return trade.getMakerFeeAsLong(); + } + + @Override + protected long getBuyersTradeFee() { + return trade.getTakerFeeAsLong(); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer_as_taker/SendBuyersBsqSwapRequest.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer_as_taker/SendBuyersBsqSwapRequest.java new file mode 100644 index 0000000000..97e15e2e18 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/buyer_as_taker/SendBuyersBsqSwapRequest.java @@ -0,0 +1,60 @@ +/* + * 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.core.trade.protocol.bsq_swap.tasks.buyer_as_taker; + +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.messages.BuyersBsqSwapRequest; +import bisq.core.trade.protocol.bsq_swap.tasks.SendBsqSwapMessageTask; + +import bisq.common.taskrunner.TaskRunner; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class SendBuyersBsqSwapRequest extends SendBsqSwapMessageTask { + + @SuppressWarnings({"unused"}) + public SendBuyersBsqSwapRequest(TaskRunner taskHandler, BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + BuyersBsqSwapRequest request = new BuyersBsqSwapRequest( + protocolModel.getOfferId(), + protocolModel.getMyNodeAddress(), + protocolModel.getPubKeyRing(), + trade.getAmountAsLong(), + trade.getTxFeePerVbyte(), + trade.getMakerFeeAsLong(), + trade.getTakerFeeAsLong(), + trade.getTakeOfferDate(), + protocolModel.getInputs(), + protocolModel.getChange(), + protocolModel.getBtcAddress(), + protocolModel.getBsqAddress()); + + send(request); + } catch (Throwable t) { + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller/ProcessBsqSwapFinalizedTxMessage.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller/ProcessBsqSwapFinalizedTxMessage.java new file mode 100644 index 0000000000..3658b2dbb6 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller/ProcessBsqSwapFinalizedTxMessage.java @@ -0,0 +1,85 @@ +/* + * 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.core.trade.protocol.bsq_swap.tasks.seller; + +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.messages.BsqSwapFinalizedTxMessage; +import bisq.core.trade.protocol.bsq_swap.tasks.BsqSwapTask; +import bisq.core.util.Validator; + +import bisq.common.taskrunner.TaskRunner; + +import org.bitcoinj.core.Transaction; +import org.bitcoinj.core.TransactionWitness; + +import java.util.Arrays; +import java.util.Objects; + +import lombok.extern.slf4j.Slf4j; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +@Slf4j +public abstract class ProcessBsqSwapFinalizedTxMessage extends BsqSwapTask { + @SuppressWarnings({"unused"}) + public ProcessBsqSwapFinalizedTxMessage(TaskRunner taskHandler, BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + BsqSwapFinalizedTxMessage message = checkNotNull((BsqSwapFinalizedTxMessage) protocolModel.getTradeMessage()); + checkNotNull(message); + Validator.checkTradeId(protocolModel.getOfferId(), message); + + // We cross check if the tx matches our partially signed tx by removing the sigs from the buyers inputs + Transaction buyersTransactionWithoutSigs = protocolModel.getBtcWalletService().getTxFromSerializedTx(message.getTx()); + int buyersInputSize = Objects.requireNonNull(protocolModel.getTradePeer().getInputs()).size(); + Objects.requireNonNull(buyersTransactionWithoutSigs.getInputs()).stream() + .filter(input -> input.getIndex() < buyersInputSize) + .forEach(input -> { + input.clearScriptBytes(); + input.setWitness(TransactionWitness.EMPTY); + }); + byte[] sellersPartiallySignedTx = protocolModel.getTx(); + checkArgument(Arrays.equals(buyersTransactionWithoutSigs.bitcoinSerialize(), sellersPartiallySignedTx), + "Buyers unsigned transaction does not match the sellers tx"); + + if (trade.getTransaction(protocolModel.getBsqWalletService()) != null) { + // If we have the tx already set, we are done + complete(); + return; + } + + Transaction buyersTransaction = protocolModel.getBtcWalletService().getTxFromSerializedTx(message.getTx()); + trade.applyTransaction(buyersTransaction); + trade.setState(BsqSwapTrade.State.COMPLETED); + protocolModel.getTradeManager().onBsqSwapTradeCompleted(trade); + protocolModel.getTradeManager().requestPersistence(); + onTradeCompleted(); + + complete(); + } catch (Throwable t) { + failed(t); + } + } + + protected abstract void onTradeCompleted(); +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller/ProcessTxInputsMessage.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller/ProcessTxInputsMessage.java new file mode 100644 index 0000000000..b80a9d67a3 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller/ProcessTxInputsMessage.java @@ -0,0 +1,141 @@ +/* + * 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.core.trade.protocol.bsq_swap.tasks.seller; + +import bisq.core.btc.model.RawTransactionInput; +import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.Restrictions; +import bisq.core.dao.DaoFacade; +import bisq.core.dao.state.model.blockchain.TxOutputKey; +import bisq.core.trade.bsq_swap.BsqSwapCalculation; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.messages.TxInputsMessage; +import bisq.core.trade.protocol.bsq_swap.model.BsqSwapTradePeer; +import bisq.core.trade.protocol.bsq_swap.tasks.BsqSwapTask; + +import bisq.common.taskrunner.TaskRunner; + +import org.bitcoinj.core.Address; +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.NetworkParameters; + +import java.util.List; + +import lombok.extern.slf4j.Slf4j; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * We verify the BSQ inputs to match our calculation for the required inputs. As we have the BSQ inputs in our + * DAO state we can verify the inputs if they exist and matching the peers values. + * The change cannot be verified exactly as there are some scenarios with dust spent to miners which are not reflected + * by the calculations. + * + * The sellersBsqPayoutAmount is calculated here independent of the peers data. The BTC change output will be calculated + * in SellerCreatesAndSignsTx. + */ +@Slf4j +public abstract class ProcessTxInputsMessage extends BsqSwapTask { + public ProcessTxInputsMessage(TaskRunner taskHandler, BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + TxInputsMessage message = checkNotNull((TxInputsMessage) protocolModel.getTradeMessage()); + checkNotNull(message); + + List inputs = message.getBsqInputs(); + checkArgument(!inputs.isEmpty(), "Buyers BSQ inputs must not be empty"); + + long sumInputs = inputs.stream().mapToLong(rawTransactionInput -> rawTransactionInput.value).sum(); + long buyersBsqInputAmount = BsqSwapCalculation.getBuyersBsqInputValue(trade, getBuyersTradeFee()).getValue(); + checkArgument(sumInputs >= buyersBsqInputAmount, + "Buyers BSQ input amount do not match our calculated required BSQ input amount"); + + DaoFacade daoFacade = protocolModel.getDaoFacade(); + BtcWalletService btcWalletService = protocolModel.getBtcWalletService(); + + long sumValidBsqInputValue = inputs.stream() + .mapToLong(input -> daoFacade.getUnspentTxOutputValue( + new TxOutputKey(input.getParentTxId(btcWalletService), (int) input.index)) + ) + .sum(); + checkArgument(sumInputs == sumValidBsqInputValue, + "Buyers BSQ input amount must match input amount from unspentTxOutputMap in DAO state"); + + long numValidBsqInputs = inputs.stream() + .map(input -> new TxOutputKey(input.getParentTxId(btcWalletService), (int) input.index)) + .filter(daoFacade::isTxOutputSpendable) + .count(); + checkArgument(inputs.size() == numValidBsqInputs, + "Some of the buyers BSQ inputs are not from spendable BSQ utxo's according to our DAO state data."); + + long change = message.getBsqChange(); + checkArgument(change == 0 || Restrictions.isAboveDust(Coin.valueOf(change)), + "BSQ change must be 0 or above dust"); + + // sellersBsqPayoutAmount is not related to peers inputs but we need it in the following steps so we + // calculate and set it here. + Coin sellersBsqPayoutAmount = BsqSwapCalculation.getSellersBsqPayoutValue(trade, getSellersTradeFee()); + protocolModel.setPayout(sellersBsqPayoutAmount.getValue()); + + long expectedChange = sumInputs - sellersBsqPayoutAmount.getValue() - getBuyersTradeFee() - getSellersTradeFee(); + if (expectedChange != change) { + log.warn("Buyers BSQ change is not as expected. This can happen if change would be below dust. " + + "The change would be used as miner fee in such cases."); + log.warn("sellersBsqPayoutAmount={}, sumInputs={}, getBuyersTradeFee={}, " + + "getSellersTradeFee={}, expectedChange={},change={}", + sellersBsqPayoutAmount.value, sumInputs, getBuyersTradeFee(), + getSellersTradeFee(), expectedChange, change); + } + // By enforcing that it must not be larger than expectedChange we guarantee that peer did not cheat on + // trade fees. + checkArgument(change <= expectedChange, + "Change must be smaller or equal to expectedChange"); + + NetworkParameters params = btcWalletService.getParams(); + String buyersBtcPayoutAddress = message.getBuyersBtcPayoutAddress(); + checkNotNull(buyersBtcPayoutAddress, "buyersBtcPayoutAddress must not be null"); + checkArgument(!buyersBtcPayoutAddress.isEmpty(), "buyersBtcPayoutAddress must not be empty"); + Address.fromString(params, buyersBtcPayoutAddress); // If address is not a BTC address it throws an exception + + String buyersBsqChangeAddress = message.getBuyersBsqChangeAddress(); + checkNotNull(buyersBsqChangeAddress, "buyersBsqChangeAddress must not be null"); + checkArgument(!buyersBsqChangeAddress.isEmpty(), "buyersBsqChangeAddress must not be empty"); + Address.fromString(params, buyersBsqChangeAddress); // If address is not a BTC address it throws an exception + + // Apply data + BsqSwapTradePeer tradePeer = protocolModel.getTradePeer(); + tradePeer.setInputs(inputs); + tradePeer.setChange(change); + tradePeer.setBtcAddress(buyersBtcPayoutAddress); + tradePeer.setBsqAddress(buyersBsqChangeAddress); + + complete(); + } catch (Throwable t) { + failed(t); + } + } + + protected abstract long getBuyersTradeFee(); + + protected abstract long getSellersTradeFee(); +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller/SellerCreatesAndSignsTx.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller/SellerCreatesAndSignsTx.java new file mode 100644 index 0000000000..60666d2480 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller/SellerCreatesAndSignsTx.java @@ -0,0 +1,119 @@ +/* + * 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.core.trade.protocol.bsq_swap.tasks.seller; + +import bisq.core.btc.model.RawTransactionInput; +import bisq.core.btc.wallet.TradeWalletService; +import bisq.core.trade.bsq_swap.BsqSwapCalculation; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.model.BsqSwapTradePeer; +import bisq.core.trade.protocol.bsq_swap.tasks.BsqSwapTask; + +import bisq.common.taskrunner.TaskRunner; +import bisq.common.util.Tuple2; + +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.Transaction; +import org.bitcoinj.core.TransactionInput; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import lombok.extern.slf4j.Slf4j; + + +@Slf4j +public abstract class SellerCreatesAndSignsTx extends BsqSwapTask { + public SellerCreatesAndSignsTx(TaskRunner taskHandler, BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + BsqSwapTradePeer tradePeer = protocolModel.getTradePeer(); + TradeWalletService tradeWalletService = protocolModel.getTradeWalletService(); + + List buyersBsqInputs = Objects.requireNonNull(tradePeer.getInputs()); + + long sellersTradeFee = getSellersTradeFee(); + long txFeePerVbyte = trade.getTxFeePerVbyte(); + Tuple2, Coin> btcInputsAndChange = BsqSwapCalculation.getSellersBtcInputsAndChange( + protocolModel.getBtcWalletService(), + trade.getAmountAsLong(), + txFeePerVbyte, + sellersTradeFee); + List sellersBtcInputs = btcInputsAndChange.first; + protocolModel.setInputs(sellersBtcInputs); + + Coin sellersBsqPayoutAmount = Coin.valueOf(protocolModel.getPayout()); + String sellersBsqPayoutAddress = protocolModel.getBsqWalletService().getUnusedAddress().toString(); + protocolModel.setBsqAddress(sellersBsqPayoutAddress); + + Coin buyersBsqChangeAmount = Coin.valueOf(tradePeer.getChange()); + String buyersBsqChangeAddress = tradePeer.getBsqAddress(); + + int buyersTxSize = BsqSwapCalculation.getVBytesSize(buyersBsqInputs, buyersBsqChangeAmount.getValue()); + Coin buyersBtcPayoutAmount = BsqSwapCalculation.getBuyersBtcPayoutValue(trade, buyersTxSize, getBuyersTradeFee()); + tradePeer.setPayout(buyersBtcPayoutAmount.getValue()); + String buyersBtcPayoutAddress = tradePeer.getBtcAddress(); + + Coin sellersBtcChangeAmount = btcInputsAndChange.second; + protocolModel.setChange(sellersBtcChangeAmount.getValue()); + + String sellersBtcChangeAddress = protocolModel.getBtcWalletService().getFreshAddressEntry().getAddressString(); + protocolModel.setBtcAddress(sellersBtcChangeAddress); + + Transaction transaction = tradeWalletService.sellerBuildBsqSwapTx( + buyersBsqInputs, + sellersBtcInputs, + sellersBsqPayoutAmount, + sellersBsqPayoutAddress, + buyersBsqChangeAmount, + buyersBsqChangeAddress, + buyersBtcPayoutAmount, + buyersBtcPayoutAddress, + sellersBtcChangeAmount, + sellersBtcChangeAddress); + + // Sign my inputs + int buyersInputSize = buyersBsqInputs.size(); + List myInputs = transaction.getInputs().stream() + .filter(input -> input.getIndex() >= buyersInputSize) + .collect(Collectors.toList()); + tradeWalletService.signBsqSwapTransaction(transaction, myInputs); + + log.info("Sellers signed his inputs of transaction {}", transaction); + protocolModel.applyTransaction(transaction); + + int sellersTxSize = BsqSwapCalculation.getVBytesSize(sellersBtcInputs, sellersBtcChangeAmount.getValue()); + long sellersTxFee = BsqSwapCalculation.getAdjustedTxFee(txFeePerVbyte, sellersTxSize, sellersTradeFee); + protocolModel.setTxFee(sellersTxFee); + protocolModel.getTradeManager().requestPersistence(); + + complete(); + } catch (Throwable t) { + failed(t); + } + } + + protected abstract long getBuyersTradeFee(); + + protected abstract long getSellersTradeFee(); +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller/SellerMaybePublishesTx.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller/SellerMaybePublishesTx.java new file mode 100644 index 0000000000..0790171ceb --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller/SellerMaybePublishesTx.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.core.trade.protocol.bsq_swap.tasks.seller; + +import bisq.core.btc.exceptions.TxBroadcastException; +import bisq.core.btc.wallet.TxBroadcaster; +import bisq.core.dao.state.model.blockchain.TxType; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.tasks.BsqSwapTask; + +import bisq.common.taskrunner.TaskRunner; + +import org.bitcoinj.core.Transaction; + +import java.util.Objects; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class SellerMaybePublishesTx extends BsqSwapTask { + @SuppressWarnings({"unused"}) + public SellerMaybePublishesTx(TaskRunner taskHandler, BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + Transaction transaction = Objects.requireNonNull(trade.getTransaction(protocolModel.getBsqWalletService())); + Transaction walletTx = protocolModel.getTradeWalletService().getWalletTx(transaction.getTxId()); + if (walletTx != null) { + // This is expected if we have already received the tx from the network + complete(); + return; + } + + // We only publish if we do not have the tx already in our wallet received from the network + protocolModel.getWalletsManager().publishAndCommitBsqTx(transaction, + TxType.TRANSFER_BSQ, + new TxBroadcaster.Callback() { + @Override + public void onSuccess(Transaction transaction) { + if (!completed) { + trade.setState(BsqSwapTrade.State.COMPLETED); + protocolModel.getTradeManager().onBsqSwapTradeCompleted(trade); + + complete(); + } else { + log.warn("We got the onSuccess callback called after the timeout has been triggered a complete()."); + } + } + + @Override + public void onFailure(TxBroadcastException exception) { + if (!completed) { + failed(exception); + } else { + log.warn("We got the onFailure callback called after the timeout has been triggered a complete()."); + } + } + }); + } catch (Throwable t) { + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller/SellerSetupTxListener.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller/SellerSetupTxListener.java new file mode 100644 index 0000000000..90bfe9c7fc --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller/SellerSetupTxListener.java @@ -0,0 +1,147 @@ +/* + * 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.core.trade.protocol.bsq_swap.tasks.seller; + +import bisq.core.btc.listeners.TxConfidenceListener; +import bisq.core.btc.wallet.BsqWalletService; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.tasks.BsqSwapTask; + +import bisq.common.UserThread; +import bisq.common.taskrunner.TaskRunner; + +import org.bitcoinj.core.Transaction; +import org.bitcoinj.core.TransactionConfidence; + +import javafx.beans.value.ChangeListener; + +import java.util.Objects; + +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.Nullable; + +import static org.bitcoinj.core.TransactionConfidence.ConfidenceType.BUILDING; +import static org.bitcoinj.core.TransactionConfidence.ConfidenceType.PENDING; + +@Slf4j +public abstract class SellerSetupTxListener extends BsqSwapTask { + @Nullable + private TxConfidenceListener confidenceListener; + private ChangeListener stateListener; + + public SellerSetupTxListener(TaskRunner taskHandler, BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + if (trade.isCompleted()) { + complete(); + return; + } + + BsqWalletService walletService = protocolModel.getBsqWalletService(); + + // The confidence listener based on the txId only works if all buyers inputs are segWit inputs + // As we expect to receive anyway the buyers message with the finalized tx we can ignore the + // rare cases where an input is not segwit and therefore the txId not matching. + String txId = Objects.requireNonNull(protocolModel.getTransaction()).getTxId().toString(); + TransactionConfidence confidence = walletService.getConfidenceForTxId(txId); + + if (processConfidence(confidence)) { + complete(); + return; + } + + confidenceListener = new TxConfidenceListener(txId) { + @Override + public void onTransactionConfidenceChanged(TransactionConfidence confidence) { + if (processConfidence(confidence)) { + cleanup(); + } + } + }; + walletService.addTxConfidenceListener(confidenceListener); + + // In case we received the message from the peer with the tx we get the trade state set to completed + // and we stop listening on the network for the tx + stateListener = (observable, oldValue, newValue) -> { + if (newValue == BsqSwapTrade.State.COMPLETED) { + cleanup(); + } + }; + trade.stateProperty().addListener(stateListener); + + // We complete immediately, our object stays alive because the listener has a reference in the walletService + complete(); + } catch (Throwable t) { + failed(t); + } + } + + private boolean processConfidence(TransactionConfidence confidence) { + if (confidence == null) { + return false; + } + + if (trade.getTransaction(protocolModel.getBsqWalletService()) != null) { + // If we have the tx already set, we are done + return true; + } + + if (!isInNetwork(confidence)) { + return false; + } + + Transaction walletTx = protocolModel.getBsqWalletService().getTransaction(confidence.getTransactionHash()); + if (walletTx == null) { + return false; + } + + trade.applyTransaction(walletTx); + trade.setState(BsqSwapTrade.State.COMPLETED); + protocolModel.getTradeManager().onBsqSwapTradeCompleted(trade); + protocolModel.getTradeManager().requestPersistence(); + onTradeCompleted(); + + log.info("Received buyers tx from network {}", walletTx); + return true; + } + + + private boolean isInNetwork(TransactionConfidence confidence) { + return confidence != null && + (confidence.getConfidenceType().equals(BUILDING) || + confidence.getConfidenceType().equals(PENDING)); + } + + private void cleanup() { + UserThread.execute(() -> { + if (confidenceListener != null) { + protocolModel.getBsqWalletService().removeTxConfidenceListener(confidenceListener); + } + if (stateListener != null) { + trade.stateProperty().removeListener(stateListener); + } + }); + } + + protected abstract void onTradeCompleted(); +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller/SendBsqSwapFinalizeTxRequest.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller/SendBsqSwapFinalizeTxRequest.java new file mode 100644 index 0000000000..36f3f5619f --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller/SendBsqSwapFinalizeTxRequest.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.core.trade.protocol.bsq_swap.tasks.seller; + +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.messages.BsqSwapFinalizeTxRequest; +import bisq.core.trade.protocol.bsq_swap.tasks.SendBsqSwapMessageTask; + +import bisq.common.taskrunner.TaskRunner; + +import java.util.Objects; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class SendBsqSwapFinalizeTxRequest extends SendBsqSwapMessageTask { + + @SuppressWarnings({"unused"}) + public SendBsqSwapFinalizeTxRequest(TaskRunner taskHandler, BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + BsqSwapFinalizeTxRequest request = new BsqSwapFinalizeTxRequest( + protocolModel.getOfferId(), + protocolModel.getMyNodeAddress(), + Objects.requireNonNull(protocolModel.getTx()), + protocolModel.getInputs(), + protocolModel.getChange(), + protocolModel.getBsqAddress(), + protocolModel.getBtcAddress()); + + send(request); + } catch (Throwable t) { + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller_as_maker/ProcessBuyersBsqSwapRequest.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller_as_maker/ProcessBuyersBsqSwapRequest.java new file mode 100644 index 0000000000..4d72649599 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller_as_maker/ProcessBuyersBsqSwapRequest.java @@ -0,0 +1,62 @@ +/* + * 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.core.trade.protocol.bsq_swap.tasks.seller_as_maker; + +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.messages.BuyersBsqSwapRequest; +import bisq.core.trade.protocol.bsq_swap.tasks.seller.ProcessTxInputsMessage; + +import bisq.common.crypto.PubKeyRing; +import bisq.common.taskrunner.TaskRunner; + +import lombok.extern.slf4j.Slf4j; + +import static com.google.common.base.Preconditions.checkNotNull; + +@Slf4j +public class ProcessBuyersBsqSwapRequest extends ProcessTxInputsMessage { + @SuppressWarnings({"unused"}) + public ProcessBuyersBsqSwapRequest(TaskRunner taskHandler, BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + BuyersBsqSwapRequest request = checkNotNull((BuyersBsqSwapRequest) protocolModel.getTradeMessage()); + PubKeyRing pubKeyRing = checkNotNull(request.getTakerPubKeyRing(), "pubKeyRing must not be null"); + protocolModel.getTradePeer().setPubKeyRing(pubKeyRing); + + super.run(); + } catch (Throwable t) { + failed(t); + } + } + + @Override + protected long getBuyersTradeFee() { + return trade.getTakerFeeAsLong(); + } + + @Override + protected long getSellersTradeFee() { + return trade.getMakerFeeAsLong(); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller_as_maker/SellerAsMakerCreatesAndSignsTx.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller_as_maker/SellerAsMakerCreatesAndSignsTx.java new file mode 100644 index 0000000000..a984894412 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller_as_maker/SellerAsMakerCreatesAndSignsTx.java @@ -0,0 +1,53 @@ +/* + * 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.core.trade.protocol.bsq_swap.tasks.seller_as_maker; + +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.tasks.seller.SellerCreatesAndSignsTx; + +import bisq.common.taskrunner.TaskRunner; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class SellerAsMakerCreatesAndSignsTx extends SellerCreatesAndSignsTx { + public SellerAsMakerCreatesAndSignsTx(TaskRunner taskHandler, BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + super.run(); + } catch (Throwable t) { + failed(t); + } + } + + @Override + protected long getBuyersTradeFee() { + return trade.getTakerFeeAsLong(); + } + + @Override + protected long getSellersTradeFee() { + return trade.getMakerFeeAsLong(); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller_as_maker/SellerAsMakerProcessBsqSwapFinalizedTxMessage.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller_as_maker/SellerAsMakerProcessBsqSwapFinalizedTxMessage.java new file mode 100644 index 0000000000..8c4c50e182 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller_as_maker/SellerAsMakerProcessBsqSwapFinalizedTxMessage.java @@ -0,0 +1,52 @@ +/* + * 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.core.trade.protocol.bsq_swap.tasks.seller_as_maker; + +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.tasks.seller.ProcessBsqSwapFinalizedTxMessage; + +import bisq.common.taskrunner.TaskRunner; + +import lombok.extern.slf4j.Slf4j; + +import static com.google.common.base.Preconditions.checkNotNull; + +@Slf4j +public class SellerAsMakerProcessBsqSwapFinalizedTxMessage extends ProcessBsqSwapFinalizedTxMessage { + @SuppressWarnings({"unused"}) + public SellerAsMakerProcessBsqSwapFinalizedTxMessage(TaskRunner taskHandler, + BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + super.run(); + } catch (Throwable t) { + failed(t); + } + } + + @Override + protected void onTradeCompleted() { + protocolModel.getOpenOfferManager().closeOpenOffer(checkNotNull(trade.getOffer())); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller_as_maker/SellerAsMakerSetupTxListener.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller_as_maker/SellerAsMakerSetupTxListener.java new file mode 100644 index 0000000000..6b124a84b0 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller_as_maker/SellerAsMakerSetupTxListener.java @@ -0,0 +1,51 @@ +/* + * 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.core.trade.protocol.bsq_swap.tasks.seller_as_maker; + +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.tasks.seller.SellerSetupTxListener; + +import bisq.common.taskrunner.TaskRunner; + +import lombok.extern.slf4j.Slf4j; + +import static com.google.common.base.Preconditions.checkNotNull; + +@Slf4j +public class SellerAsMakerSetupTxListener extends SellerSetupTxListener { + + public SellerAsMakerSetupTxListener(TaskRunner taskHandler, BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + super.run(); + } catch (Throwable t) { + failed(t); + } + } + + @Override + protected void onTradeCompleted() { + protocolModel.getOpenOfferManager().closeOpenOffer(checkNotNull(trade.getOffer())); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller_as_taker/ProcessBsqSwapTxInputsMessage.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller_as_taker/ProcessBsqSwapTxInputsMessage.java new file mode 100644 index 0000000000..521cdfa5ad --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller_as_taker/ProcessBsqSwapTxInputsMessage.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.core.trade.protocol.bsq_swap.tasks.seller_as_taker; + +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.messages.BsqSwapTxInputsMessage; +import bisq.core.trade.protocol.bsq_swap.tasks.seller.ProcessTxInputsMessage; + +import bisq.common.taskrunner.TaskRunner; + +import lombok.extern.slf4j.Slf4j; + +import static com.google.common.base.Preconditions.checkArgument; + +@Slf4j +public class ProcessBsqSwapTxInputsMessage extends ProcessTxInputsMessage { + @SuppressWarnings({"unused"}) + public ProcessBsqSwapTxInputsMessage(TaskRunner taskHandler, BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + checkArgument(protocolModel.getTradeMessage() instanceof BsqSwapTxInputsMessage); + + super.run(); + } catch (Throwable t) { + failed(t); + } + } + + @Override + protected long getBuyersTradeFee() { + return trade.getMakerFeeAsLong(); + } + + @Override + protected long getSellersTradeFee() { + return trade.getTakerFeeAsLong(); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller_as_taker/SellerAsTakerCreatesAndSignsTx.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller_as_taker/SellerAsTakerCreatesAndSignsTx.java new file mode 100644 index 0000000000..b17a4b05f3 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller_as_taker/SellerAsTakerCreatesAndSignsTx.java @@ -0,0 +1,53 @@ +/* + * 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.core.trade.protocol.bsq_swap.tasks.seller_as_taker; + +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.tasks.seller.SellerCreatesAndSignsTx; + +import bisq.common.taskrunner.TaskRunner; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class SellerAsTakerCreatesAndSignsTx extends SellerCreatesAndSignsTx { + public SellerAsTakerCreatesAndSignsTx(TaskRunner taskHandler, BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + super.run(); + } catch (Throwable t) { + failed(t); + } + } + + @Override + protected long getBuyersTradeFee() { + return trade.getMakerFeeAsLong(); + } + + @Override + protected long getSellersTradeFee() { + return trade.getTakerFeeAsLong(); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller_as_taker/SellerAsTakerProcessBsqSwapFinalizedTxMessage.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller_as_taker/SellerAsTakerProcessBsqSwapFinalizedTxMessage.java new file mode 100644 index 0000000000..6c7dce99ec --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller_as_taker/SellerAsTakerProcessBsqSwapFinalizedTxMessage.java @@ -0,0 +1,49 @@ +/* + * 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.core.trade.protocol.bsq_swap.tasks.seller_as_taker; + +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.tasks.seller.ProcessBsqSwapFinalizedTxMessage; + +import bisq.common.taskrunner.TaskRunner; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class SellerAsTakerProcessBsqSwapFinalizedTxMessage extends ProcessBsqSwapFinalizedTxMessage { + @SuppressWarnings({"unused"}) + public SellerAsTakerProcessBsqSwapFinalizedTxMessage(TaskRunner taskHandler, + BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + super.run(); + } catch (Throwable t) { + failed(t); + } + } + + @Override + protected void onTradeCompleted() { + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller_as_taker/SellerAsTakerSetupTxListener.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller_as_taker/SellerAsTakerSetupTxListener.java new file mode 100644 index 0000000000..de4dc02685 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller_as_taker/SellerAsTakerSetupTxListener.java @@ -0,0 +1,48 @@ +/* + * 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.core.trade.protocol.bsq_swap.tasks.seller_as_taker; + +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.tasks.seller.SellerSetupTxListener; + +import bisq.common.taskrunner.TaskRunner; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class SellerAsTakerSetupTxListener extends SellerSetupTxListener { + + public SellerAsTakerSetupTxListener(TaskRunner taskHandler, BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + super.run(); + } catch (Throwable t) { + failed(t); + } + } + + @Override + protected void onTradeCompleted() { + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller_as_taker/SendSellersBsqSwapRequest.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller_as_taker/SendSellersBsqSwapRequest.java new file mode 100644 index 0000000000..ecf802de66 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/tasks/seller_as_taker/SendSellersBsqSwapRequest.java @@ -0,0 +1,56 @@ +/* + * 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.core.trade.protocol.bsq_swap.tasks.seller_as_taker; + +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.trade.protocol.bsq_swap.messages.SellersBsqSwapRequest; +import bisq.core.trade.protocol.bsq_swap.tasks.SendBsqSwapMessageTask; + +import bisq.common.taskrunner.TaskRunner; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class SendSellersBsqSwapRequest extends SendBsqSwapMessageTask { + + @SuppressWarnings({"unused"}) + public SendSellersBsqSwapRequest(TaskRunner taskHandler, BsqSwapTrade bsqSwapTrade) { + super(taskHandler, bsqSwapTrade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + SellersBsqSwapRequest request = new SellersBsqSwapRequest( + protocolModel.getOfferId(), + protocolModel.getMyNodeAddress(), + protocolModel.getPubKeyRing(), + trade.getAmountAsLong(), + trade.getTxFeePerVbyte(), + trade.getMakerFeeAsLong(), + trade.getTakerFeeAsLong(), + trade.getTakeOfferDate()); + + send(request); + } catch (Throwable t) { + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ApplyFilter.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ApplyFilter.java deleted file mode 100644 index f3a2d34159..0000000000 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/ApplyFilter.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bisq is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bisq. If not, see . - */ - -package bisq.core.trade.protocol.tasks; - -import bisq.core.filter.FilterManager; -import bisq.core.payment.payload.PaymentAccountPayload; -import bisq.core.trade.Trade; - -import bisq.network.p2p.NodeAddress; - -import bisq.common.taskrunner.TaskRunner; - -import lombok.extern.slf4j.Slf4j; - -import javax.annotation.Nullable; - -import static com.google.common.base.Preconditions.checkNotNull; - -@Slf4j -public class ApplyFilter extends TradeTask { - public ApplyFilter(TaskRunner taskHandler, Trade trade) { - super(taskHandler, trade); - } - - @Override - protected void run() { - try { - runInterceptHook(); - - NodeAddress nodeAddress = checkNotNull(processModel.getTempTradingPeerNodeAddress()); - @Nullable - PaymentAccountPayload paymentAccountPayload = processModel.getTradingPeer().getPaymentAccountPayload(); - - FilterManager filterManager = processModel.getFilterManager(); - if (filterManager.isNodeAddressBanned(nodeAddress)) { - failed("Other trader is banned by their node address.\n" + - "tradingPeerNodeAddress=" + nodeAddress); - } else if (filterManager.isOfferIdBanned(trade.getId())) { - failed("Offer ID is banned.\n" + - "Offer ID=" + trade.getId()); - } else if (trade.getOffer() != null && filterManager.isCurrencyBanned(trade.getOffer().getCurrencyCode())) { - failed("Currency is banned.\n" + - "Currency code=" + trade.getOffer().getCurrencyCode()); - } else if (filterManager.isPaymentMethodBanned(checkNotNull(trade.getOffer()).getPaymentMethod())) { - failed("Payment method is banned.\n" + - "Payment method=" + trade.getOffer().getPaymentMethod().getId()); - } else if (paymentAccountPayload != null && filterManager.arePeersPaymentAccountDataBanned(paymentAccountPayload)) { - failed("Other trader is banned by their trading account data.\n" + - "paymentAccountPayload=" + paymentAccountPayload.getPaymentDetails()); - } else if (filterManager.requireUpdateToNewVersionForTrading()) { - failed("Your version of Bisq is not compatible for trading anymore. " + - "Please update to the latest Bisq version at https://bisq.network/downloads."); - } else { - complete(); - } - } catch (Throwable t) { - failed(t); - } - } -} - diff --git a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java index 864b08f967..5640bb8cd4 100644 --- a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java +++ b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java @@ -22,8 +22,10 @@ import bisq.core.monetary.AltcoinExchangeRate; import bisq.core.monetary.Price; import bisq.core.monetary.Volume; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload; -import bisq.core.trade.Trade; +import bisq.core.offer.OfferDirection; +import bisq.core.offer.bisq_v1.OfferPayload; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.util.JsonUtil; import bisq.core.util.VolumeUtil; import bisq.network.p2p.NodeAddress; @@ -53,7 +55,8 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; -import lombok.Value; +import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; @@ -67,7 +70,8 @@ import static com.google.common.base.Preconditions.checkNotNull; */ @Deprecated @Slf4j -@Value +@EqualsAndHashCode +@Getter public final class TradeStatistics2 implements ProcessOncePersistableNetworkPayload, PersistableNetworkPayload, CapabilityRequiringPayload, Comparable { @@ -92,10 +96,11 @@ public final class TradeStatistics2 implements ProcessOncePersistableNetworkPayl Offer offer = trade.getOffer(); checkNotNull(offer, "offer must not ne null"); - checkNotNull(trade.getTradeAmount(), "trade.getTradeAmount() must not ne null"); - return new TradeStatistics2(offer.getOfferPayload(), - trade.getTradePrice(), - trade.getTradeAmount(), + checkNotNull(trade.getAmount(), "trade.getTradeAmount() must not ne null"); + OfferPayload offerPayload = offer.getOfferPayload().orElseThrow(); + return new TradeStatistics2(offerPayload, + trade.getPrice(), + trade.getAmount(), trade.getDate(), trade.getDepositTxId(), extraDataMap); @@ -106,7 +111,7 @@ public final class TradeStatistics2 implements ProcessOncePersistableNetworkPayl @SuppressWarnings("SpellCheckingInspection") public static final String REFUND_AGENT_ADDRESS = "refAddr"; - private final OfferPayload.Direction direction; + private final OfferDirection direction; private final String baseCurrency; private final String counterCurrency; private final String offerPaymentMethod; @@ -165,7 +170,7 @@ public final class TradeStatistics2 implements ProcessOncePersistableNetworkPayl // PROTO BUFFER /////////////////////////////////////////////////////////////////////////////////////////// - public TradeStatistics2(OfferPayload.Direction direction, + public TradeStatistics2(OfferDirection direction, String baseCurrency, String counterCurrency, String offerPaymentMethod, @@ -204,12 +209,12 @@ public final class TradeStatistics2 implements ProcessOncePersistableNetworkPayl // We create hash from all fields excluding hash itself. We use json as simple data serialisation. // TradeDate is different for both peers so we ignore it for hash. ExtraDataMap is ignored as well as at // software updates we might have different entries which would cause a different hash. - return Hash.getSha256Ripemd160hash(Utilities.objectToJson(this).getBytes(Charsets.UTF_8)); + return Hash.getSha256Ripemd160hash(JsonUtil.objectToJson(this).getBytes(Charsets.UTF_8)); } private protobuf.TradeStatistics2.Builder getBuilder() { final protobuf.TradeStatistics2.Builder builder = protobuf.TradeStatistics2.newBuilder() - .setDirection(OfferPayload.Direction.toProtoMessage(direction)) + .setDirection(OfferDirection.toProtoMessage(direction)) .setBaseCurrency(baseCurrency) .setCounterCurrency(counterCurrency) .setPaymentMethodId(offerPaymentMethod) @@ -239,7 +244,7 @@ public final class TradeStatistics2 implements ProcessOncePersistableNetworkPayl public static TradeStatistics2 fromProto(protobuf.TradeStatistics2 proto) { return new TradeStatistics2( - OfferPayload.Direction.fromProto(proto.getDirection()), + OfferDirection.fromProto(proto.getDirection()), proto.getBaseCurrency(), proto.getCounterCurrency(), proto.getPaymentMethodId(), diff --git a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics3.java b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics3.java index fee029654b..bac91b3b28 100644 --- a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics3.java +++ b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics3.java @@ -22,8 +22,10 @@ import bisq.core.monetary.AltcoinExchangeRate; import bisq.core.monetary.Price; import bisq.core.monetary.Volume; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload; -import bisq.core.trade.Trade; +import bisq.core.offer.bisq_v1.OfferPayload; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.util.JsonUtil; import bisq.core.util.VolumeUtil; import bisq.network.p2p.NodeAddress; @@ -104,15 +106,28 @@ public final class TradeStatistics3 implements ProcessOncePersistableNetworkPayl Offer offer = checkNotNull(trade.getOffer()); return new TradeStatistics3(offer.getCurrencyCode(), - trade.getTradePrice().getValue(), - trade.getTradeAmountAsLong(), + trade.getPrice().getValue(), + trade.getAmountAsLong(), offer.getPaymentMethod().getId(), - trade.getTakeOfferDate().getTime(), + trade.getDate().getTime(), truncatedMediatorNodeAddress, truncatedRefundAgentNodeAddress, extraDataMap); } + public static TradeStatistics3 from(BsqSwapTrade bsqSwapTrade) { + Offer offer = checkNotNull(bsqSwapTrade.getOffer()); + return new TradeStatistics3(offer.getCurrencyCode(), + bsqSwapTrade.getPrice().getValue(), + bsqSwapTrade.getAmountAsLong(), + offer.getPaymentMethod().getId(), + bsqSwapTrade.getTakeOfferDate(), + null, + null, + null, + null); + } + // This enum must not change the order as we use the ordinal for storage to reduce data size. // The payment method string can be quite long and would consume 15% more space. // When we get a new payment method we can add it to the enum at the end. Old users would add it as string if not @@ -291,7 +306,7 @@ public final class TradeStatistics3 implements ProcessOncePersistableNetworkPayl // We create hash from all fields excluding hash itself. We use json as simple data serialisation. // TradeDate is different for both peers so we ignore it for hash. ExtraDataMap is ignored as well as at // software updates we might have different entries which would cause a different hash. - return Hash.getSha256Ripemd160hash(Utilities.objectToJson(this).getBytes(Charsets.UTF_8)); + return Hash.getSha256Ripemd160hash(JsonUtil.objectToJson(this).getBytes(Charsets.UTF_8)); } private protobuf.TradeStatistics3.Builder getBuilder() { diff --git a/core/src/main/java/bisq/core/trade/statistics/TradeStatisticsManager.java b/core/src/main/java/bisq/core/trade/statistics/TradeStatisticsManager.java index 044018c018..6049e67e38 100644 --- a/core/src/main/java/bisq/core/trade/statistics/TradeStatisticsManager.java +++ b/core/src/main/java/bisq/core/trade/statistics/TradeStatisticsManager.java @@ -21,8 +21,10 @@ import bisq.core.locale.CurrencyTuple; import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; import bisq.core.provider.price.PriceFeedService; -import bisq.core.trade.BuyerTrade; -import bisq.core.trade.Trade; +import bisq.core.trade.model.TradeModel; +import bisq.core.trade.model.bisq_v1.BuyerTrade; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.util.JsonUtil; import bisq.network.p2p.P2PService; import bisq.network.p2p.storage.P2PDataStorage; @@ -30,7 +32,6 @@ import bisq.network.p2p.storage.persistence.AppendOnlyDataStoreService; import bisq.common.config.Config; import bisq.common.file.JsonFileManager; -import bisq.common.util.Utilities; import com.google.inject.Inject; @@ -131,13 +132,13 @@ public class TradeStatisticsManager { ArrayList fiatCurrencyList = CurrencyUtil.getAllSortedFiatCurrencies().stream() .map(e -> new CurrencyTuple(e.getCode(), e.getName(), 8)) .collect(Collectors.toCollection(ArrayList::new)); - jsonFileManager.writeToDiscThreaded(Utilities.objectToJson(fiatCurrencyList), "fiat_currency_list"); + jsonFileManager.writeToDiscThreaded(JsonUtil.objectToJson(fiatCurrencyList), "fiat_currency_list"); ArrayList cryptoCurrencyList = CurrencyUtil.getAllSortedCryptoCurrencies().stream() .map(e -> new CurrencyTuple(e.getCode(), e.getName(), 8)) .collect(Collectors.toCollection(ArrayList::new)); cryptoCurrencyList.add(0, new CurrencyTuple(Res.getBaseCurrencyCode(), Res.getBaseCurrencyName(), 8)); - jsonFileManager.writeToDiscThreaded(Utilities.objectToJson(cryptoCurrencyList), "crypto_currency_list"); + jsonFileManager.writeToDiscThreaded(JsonUtil.objectToJson(cryptoCurrencyList), "crypto_currency_list"); Instant yearAgo = Instant.ofEpochSecond(Instant.now().getEpochSecond() - TimeUnit.DAYS.toSeconds(365)); Set activeCurrencies = observableTradeStatisticsSet.stream() @@ -149,13 +150,13 @@ public class TradeStatisticsManager { .filter(e -> activeCurrencies.contains(e.code)) .map(e -> new CurrencyTuple(e.code, e.name, 8)) .collect(Collectors.toCollection(ArrayList::new)); - jsonFileManager.writeToDiscThreaded(Utilities.objectToJson(activeFiatCurrencyList), "active_fiat_currency_list"); + jsonFileManager.writeToDiscThreaded(JsonUtil.objectToJson(activeFiatCurrencyList), "active_fiat_currency_list"); ArrayList activeCryptoCurrencyList = cryptoCurrencyList.stream() .filter(e -> activeCurrencies.contains(e.code)) .map(e -> new CurrencyTuple(e.code, e.name, 8)) .collect(Collectors.toCollection(ArrayList::new)); - jsonFileManager.writeToDiscThreaded(Utilities.objectToJson(activeCryptoCurrencyList), "active_crypto_currency_list"); + jsonFileManager.writeToDiscThreaded(JsonUtil.objectToJson(activeCryptoCurrencyList), "active_crypto_currency_list"); } List list = observableTradeStatisticsSet.stream() @@ -164,49 +165,54 @@ public class TradeStatisticsManager { .collect(Collectors.toList()); TradeStatisticsForJson[] array = new TradeStatisticsForJson[list.size()]; list.toArray(array); - jsonFileManager.writeToDiscThreaded(Utilities.objectToJson(array), "trade_statistics"); + jsonFileManager.writeToDiscThreaded(JsonUtil.objectToJson(array), "trade_statistics"); } - public void maybeRepublishTradeStatistics(Set trades, + public void maybeRepublishTradeStatistics(Set trades, @Nullable String referralId, boolean isTorNetworkNode) { long ts = System.currentTimeMillis(); Set hashes = tradeStatistics3StorageService.getMapOfAllData().keySet(); - trades.forEach(trade -> { - if (trade instanceof BuyerTrade) { - log.debug("Trade: {} is a buyer trade, we only republish we have been seller.", - trade.getShortId()); - return; - } + trades.stream() + .filter(tradable -> tradable instanceof Trade) + .forEach(tradable -> { + Trade trade = (Trade) tradable; + if (trade instanceof BuyerTrade) { + log.debug("Trade: {} is a buyer trade, we only republish we have been seller.", + trade.getShortId()); + return; + } - TradeStatistics3 tradeStatistics3 = TradeStatistics3.from(trade, referralId, isTorNetworkNode); - boolean hasTradeStatistics3 = hashes.contains(new P2PDataStorage.ByteArray(tradeStatistics3.getHash())); - if (hasTradeStatistics3) { - log.debug("Trade: {}. We have already a tradeStatistics matching the hash of tradeStatistics3.", - trade.getShortId()); - return; - } + TradeStatistics3 tradeStatistics3 = TradeStatistics3.from(trade, referralId, isTorNetworkNode); + boolean hasTradeStatistics3 = hashes.contains(new P2PDataStorage.ByteArray(tradeStatistics3.getHash())); + if (hasTradeStatistics3) { + log.debug("Trade: {}. We have already a tradeStatistics matching the hash of tradeStatistics3.", + trade.getShortId()); + return; + } - // If we did not find a TradeStatistics3 we look up if we find a TradeStatistics3 converted from - // TradeStatistics2 where we used the original hash, which is not the native hash of the - // TradeStatistics3 but of TradeStatistics2. - TradeStatistics2 tradeStatistics2 = TradeStatistics2.from(trade, referralId, isTorNetworkNode); - boolean hasTradeStatistics2 = hashes.contains(new P2PDataStorage.ByteArray(tradeStatistics2.getHash())); - if (hasTradeStatistics2) { - log.debug("Trade: {}. We have already a tradeStatistics matching the hash of tradeStatistics2. ", - trade.getShortId()); - return; - } + // If we did not find a TradeStatistics3 we look up if we find a TradeStatistics3 converted from + // TradeStatistics2 where we used the original hash, which is not the native hash of the + // TradeStatistics3 but of TradeStatistics2. + if (!trade.isBsqSwap()) { + TradeStatistics2 tradeStatistics2 = TradeStatistics2.from(trade, referralId, isTorNetworkNode); + boolean hasTradeStatistics2 = hashes.contains(new P2PDataStorage.ByteArray(tradeStatistics2.getHash())); + if (hasTradeStatistics2) { + log.debug("Trade: {}. We have already a tradeStatistics matching the hash of tradeStatistics2. ", + trade.getShortId()); + return; + } + } - if (!tradeStatistics3.isValid()) { - log.warn("Trade: {}. Trade statistics is invalid. We do not publish it.", tradeStatistics3); - return; - } + if (!tradeStatistics3.isValid()) { + log.warn("Trade: {}. Trade statistics is invalid. We do not publish it.", tradeStatistics3); + return; + } - log.info("Trade: {}. We republish tradeStatistics3 as we did not find it in the existing trade statistics. ", - trade.getShortId()); - p2PService.addPersistableNetworkPayload(tradeStatistics3, true); - }); + log.info("Trade: {}. We republish tradeStatistics3 as we did not find it in the existing trade statistics. ", + trade.getShortId()); + p2PService.addPersistableNetworkPayload(tradeStatistics3, true); + }); log.info("maybeRepublishTradeStatistics took {} ms. Number of tradeStatistics: {}. Number of own trades: {}", System.currentTimeMillis() - ts, hashes.size(), trades.size()); } diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofModel.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofModel.java index 6d386437be..fa8f8075ce 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofModel.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofModel.java @@ -20,14 +20,12 @@ package bisq.core.trade.txproof.xmr; import bisq.core.monetary.Volume; import bisq.core.payment.payload.AssetsAccountPayload; import bisq.core.payment.payload.PaymentAccountPayload; -import bisq.core.trade.Trade; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.trade.txproof.AssetTxProofModel; import bisq.core.user.AutoConfirmSettings; import bisq.common.app.DevEnv; -import org.bitcoinj.core.Coin; - import com.google.common.annotations.VisibleForTesting; import java.util.Date; @@ -60,7 +58,7 @@ public class XmrTxProofModel implements AssetTxProofModel { this.serviceAddress = serviceAddress; this.autoConfirmSettings = autoConfirmSettings; - Volume volume = trade.getTradeVolume(); + Volume volume = trade.getVolume(); amount = DevEnv.isDevMode() ? XmrTxProofModel.DEV_AMOUNT : // For dev testing we need to add the matching address to the dev tx key and dev view key volume != null ? volume.getValue() * 10000L : 0L; // XMR satoshis have 12 decimal places vs. bitcoin's 8 diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java index 2d0efe887d..2560d71a11 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java @@ -22,7 +22,7 @@ import bisq.core.locale.Res; import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.mediation.MediationManager; import bisq.core.support.dispute.refund.RefundManager; -import bisq.core.trade.Trade; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.trade.txproof.AssetTxProofRequestsPerTrade; import bisq.core.trade.txproof.AssetTxProofResult; import bisq.core.user.AutoConfirmSettings; @@ -327,7 +327,7 @@ class XmrTxProofRequestsPerTrade implements AssetTxProofRequestsPerTrade { /////////////////////////////////////////////////////////////////////////////////////////// private boolean isTradeAmountAboveLimit(Trade trade) { - Coin tradeAmount = trade.getTradeAmount(); + Coin tradeAmount = trade.getAmount(); Coin tradeLimit = Coin.valueOf(autoConfirmSettings.getTradeLimit()); if (tradeAmount != null && tradeAmount.isGreaterThan(tradeLimit)) { log.warn("Trade amount {} is higher than limit from auto-conf setting {}.", diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java index 469b1b4fc4..775b774599 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java @@ -22,12 +22,13 @@ import bisq.core.filter.FilterManager; import bisq.core.locale.Res; import bisq.core.support.dispute.mediation.MediationManager; import bisq.core.support.dispute.refund.RefundManager; -import bisq.core.trade.SellerTrade; -import bisq.core.trade.Trade; +import bisq.core.trade.ClosedTradableManager; import bisq.core.trade.TradeManager; -import bisq.core.trade.closed.ClosedTradableManager; -import bisq.core.trade.failed.FailedTradesManager; -import bisq.core.trade.protocol.SellerProtocol; +import bisq.core.trade.bisq_v1.FailedTradesManager; +import bisq.core.trade.model.Tradable; +import bisq.core.trade.model.bisq_v1.SellerTrade; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.SellerProtocol; import bisq.core.trade.txproof.AssetTxProofResult; import bisq.core.trade.txproof.AssetTxProofService; import bisq.core.user.AutoConfirmSettings; @@ -181,7 +182,7 @@ public class XmrTxProofService implements AssetTxProofService { // We listen on new trades ObservableList tradableList = tradeManager.getObservableList(); - tradableList.addListener((ListChangeListener) c -> { + tradableList.addListener((ListChangeListener) c -> { c.next(); if (c.wasAdded()) { processTrades(c.getAddedSubList()); @@ -192,7 +193,7 @@ public class XmrTxProofService implements AssetTxProofService { processTrades(tradableList); } - private void processTrades(List trades) { + private void processTrades(List trades) { trades.stream() .filter(trade -> trade instanceof SellerTrade) .map(trade -> (SellerTrade) trade) @@ -204,7 +205,7 @@ public class XmrTxProofService implements AssetTxProofService { // Basic requirements are fulfilled. // We process further if we are in the expected state or register a listener private void processTradeOrAddListener(SellerTrade trade) { - if (isExpectedTradeState(trade.getState())) { + if (isExpectedTradeState(trade.getTradeState())) { startRequestsIfValid(trade); } else { // We are expecting SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG in the future, so listen on changes @@ -372,7 +373,9 @@ public class XmrTxProofService implements AssetTxProofService { // We need to prevent that a user tries to scam by reusing a txKey and txHash of a previous XMR trade with // the same user (same address) and same amount. We check only for the txKey as a same txHash but different // txKey is not possible to get a valid result at proof. - Stream failedAndOpenTrades = Stream.concat(activeTrades.stream(), failedTradesManager.getObservableList().stream()); + Stream failedAndOpenTrades = Stream.concat( + activeTrades.stream(), + failedTradesManager.getObservableList().stream()); Stream closedTrades = closedTradableManager.getObservableList().stream() .filter(tradable -> tradable instanceof Trade) .map(tradable -> (Trade) tradable); diff --git a/core/src/main/java/bisq/core/user/User.java b/core/src/main/java/bisq/core/user/User.java index 04edaf82b5..22555eef91 100644 --- a/core/src/main/java/bisq/core/user/User.java +++ b/core/src/main/java/bisq/core/user/User.java @@ -19,10 +19,13 @@ package bisq.core.user; import bisq.core.alert.Alert; import bisq.core.filter.Filter; +import bisq.core.locale.CryptoCurrency; import bisq.core.locale.LanguageUtil; +import bisq.core.locale.Res; import bisq.core.locale.TradeCurrency; import bisq.core.notifications.alerts.market.MarketAlertFilter; import bisq.core.notifications.alerts.price.PriceAlertFilter; +import bisq.core.payment.BsqSwapAccount; import bisq.core.payment.PaymentAccount; import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator; import bisq.core.support.dispute.mediation.mediator.Mediator; @@ -127,9 +130,26 @@ public class User implements PersistedDataHost { requestPersistence(); }); + // We create a default placeholder account for BSQ swaps. The account has not content, it is just used + // so that the BsqSwap use case fits into the current domain + addBsqSwapAccount(); + requestPersistence(); } + private void addBsqSwapAccount() { + checkNotNull(userPayload.getPaymentAccounts(), "userPayload.getPaymentAccounts() must not be null"); + if (userPayload.getPaymentAccounts().stream() + .anyMatch(paymentAccount -> paymentAccount instanceof BsqSwapAccount)) + return; + + var account = new BsqSwapAccount(); + account.init(); + account.setAccountName(Res.get("BSQ_SWAP")); + account.setSingleTradeCurrency(new CryptoCurrency("BSQ", "BSQ")); + addPaymentAccount(account); + } + public void requestPersistence() { if (persistenceManager != null) persistenceManager.requestPersistence(); diff --git a/core/src/main/java/bisq/core/util/JsonUtil.java b/core/src/main/java/bisq/core/util/JsonUtil.java new file mode 100644 index 0000000000..5ae440ba29 --- /dev/null +++ b/core/src/main/java/bisq/core/util/JsonUtil.java @@ -0,0 +1,53 @@ +/* + * 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.core.util; + +import bisq.core.offer.bisq_v1.OfferPayload; +import bisq.core.trade.model.bisq_v1.Contract; + +import bisq.common.util.JsonExclude; + +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; +import com.google.gson.GsonBuilder; + + +public class JsonUtil { + public static String objectToJson(Object object) { + GsonBuilder gsonBuilder = new GsonBuilder() + .setExclusionStrategies(new AnnotationExclusionStrategy()) + .setPrettyPrinting(); + if (object instanceof Contract || object instanceof OfferPayload) { + gsonBuilder.registerTypeAdapter(OfferPayload.class, + new OfferPayload.JsonSerializer()); + } + return gsonBuilder.create().toJson(object); + } + + private static class AnnotationExclusionStrategy implements ExclusionStrategy { + @Override + public boolean shouldSkipField(FieldAttributes f) { + return f.getAnnotation(JsonExclude.class) != null; + } + + @Override + public boolean shouldSkipClass(Class clazz) { + return false; + } + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/PriceUtil.java b/core/src/main/java/bisq/core/util/PriceUtil.java similarity index 92% rename from desktop/src/main/java/bisq/desktop/main/PriceUtil.java rename to core/src/main/java/bisq/core/util/PriceUtil.java index 7901c4689c..977420a835 100644 --- a/desktop/src/main/java/bisq/desktop/main/PriceUtil.java +++ b/core/src/main/java/bisq/core/util/PriceUtil.java @@ -15,26 +15,22 @@ * along with Bisq. If not, see . */ -package bisq.desktop.main; - -import bisq.desktop.util.validation.AltcoinValidator; -import bisq.desktop.util.validation.FiatPriceValidator; -import bisq.desktop.util.validation.MonetaryValidator; +package bisq.core.util; import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; import bisq.core.monetary.Altcoin; import bisq.core.monetary.Price; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferDirection; import bisq.core.provider.price.MarketPrice; import bisq.core.provider.price.PriceFeedService; import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.user.Preferences; -import bisq.core.util.AveragePriceUtil; -import bisq.core.util.FormattingUtils; -import bisq.core.util.ParsingUtils; +import bisq.core.util.validation.AltcoinValidator; +import bisq.core.util.validation.FiatPriceValidator; import bisq.core.util.validation.InputValidator; +import bisq.core.util.validation.MonetaryValidator; import bisq.common.util.MathUtils; @@ -45,11 +41,13 @@ import javax.inject.Singleton; import java.util.Optional; +import lombok.extern.slf4j.Slf4j; + import javax.annotation.Nullable; -import static bisq.desktop.main.shared.ChatView.log; import static com.google.common.base.Preconditions.checkNotNull; +@Slf4j @Singleton public class PriceUtil { private final PriceFeedService priceFeedService; @@ -139,7 +137,7 @@ public class PriceUtil { } public Optional getMarketBasedPrice(Offer offer, - OfferPayload.Direction direction) { + OfferDirection direction) { if (offer.isUseMarketBasedPrice()) { return Optional.of(offer.getMarketPriceMargin()); } @@ -167,9 +165,9 @@ public class PriceUtil { return calculatePercentage(offer, marketPriceAsDouble, direction); } - public Optional calculatePercentage(Offer offer, - double marketPrice, - OfferPayload.Direction direction) { + public static Optional calculatePercentage(Offer offer, + double marketPrice, + OfferDirection direction) { // If the offer did not use % price we calculate % from current market price String currencyCode = offer.getCurrencyCode(); Price price = offer.getPrice(); @@ -179,7 +177,7 @@ public class PriceUtil { long priceAsLong = checkNotNull(price).getValue(); double scaled = MathUtils.scaleDownByPowerOf10(priceAsLong, precision); double value; - if (direction == OfferPayload.Direction.SELL) { + if (direction == OfferDirection.SELL) { if (CurrencyUtil.isFiatCurrency(currencyCode)) { if (marketPrice == 0) { return Optional.empty(); diff --git a/core/src/main/java/bisq/core/util/Validator.java b/core/src/main/java/bisq/core/util/Validator.java index 52da492774..98cb013c71 100644 --- a/core/src/main/java/bisq/core/util/Validator.java +++ b/core/src/main/java/bisq/core/util/Validator.java @@ -17,7 +17,7 @@ package bisq.core.util; -import bisq.core.trade.messages.TradeMessage; +import bisq.core.trade.protocol.TradeMessage; import org.bitcoinj.core.Coin; diff --git a/core/src/main/java/bisq/core/util/coin/CoinUtil.java b/core/src/main/java/bisq/core/util/coin/CoinUtil.java index 3f8b16bda0..9b74821363 100644 --- a/core/src/main/java/bisq/core/util/coin/CoinUtil.java +++ b/core/src/main/java/bisq/core/util/coin/CoinUtil.java @@ -98,12 +98,22 @@ public class CoinUtil { */ @Nullable public static Coin getMakerFee(boolean isCurrencyForMakerFeeBtc, @Nullable Coin amount) { - if (amount != null) { - Coin feePerBtc = getFeePerBtc(FeeService.getMakerFeePerBtc(isCurrencyForMakerFeeBtc), amount); - return maxCoin(feePerBtc, FeeService.getMinMakerFee(isCurrencyForMakerFeeBtc)); - } else { + if (amount == null) { return null; } + Coin feePerBtc = getFeePerBtc(FeeService.getMakerFeePerBtc(isCurrencyForMakerFeeBtc), amount); + Coin minMakerFee = FeeService.getMinMakerFee(isCurrencyForMakerFeeBtc); + return maxCoin(feePerBtc, minMakerFee); + } + + @Nullable + public static Coin getTakerFee(boolean isCurrencyForTakerFeeBtc, @Nullable Coin amount) { + if (amount == null) { + return null; + } + Coin feePerBtc = getFeePerBtc(FeeService.getTakerFeePerBtc(isCurrencyForTakerFeeBtc), amount); + Coin minTakerFee = FeeService.getMinTakerFee(isCurrencyForTakerFeeBtc); + return maxCoin(feePerBtc, minTakerFee); } /** diff --git a/desktop/src/main/java/bisq/desktop/util/validation/AltcoinValidator.java b/core/src/main/java/bisq/core/util/validation/AltcoinValidator.java similarity index 96% rename from desktop/src/main/java/bisq/desktop/util/validation/AltcoinValidator.java rename to core/src/main/java/bisq/core/util/validation/AltcoinValidator.java index a3991093a0..81666b2244 100644 --- a/desktop/src/main/java/bisq/desktop/util/validation/AltcoinValidator.java +++ b/core/src/main/java/bisq/core/util/validation/AltcoinValidator.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.desktop.util.validation; +package bisq.core.util.validation; import javax.inject.Inject; diff --git a/desktop/src/main/java/bisq/desktop/util/validation/FiatPriceValidator.java b/core/src/main/java/bisq/core/util/validation/FiatPriceValidator.java similarity index 96% rename from desktop/src/main/java/bisq/desktop/util/validation/FiatPriceValidator.java rename to core/src/main/java/bisq/core/util/validation/FiatPriceValidator.java index 6979f2a196..3623ad5fb1 100644 --- a/desktop/src/main/java/bisq/desktop/util/validation/FiatPriceValidator.java +++ b/core/src/main/java/bisq/core/util/validation/FiatPriceValidator.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.desktop.util.validation; +package bisq.core.util.validation; import javax.inject.Inject; diff --git a/desktop/src/main/java/bisq/desktop/util/validation/MonetaryValidator.java b/core/src/main/java/bisq/core/util/validation/MonetaryValidator.java similarity index 70% rename from desktop/src/main/java/bisq/desktop/util/validation/MonetaryValidator.java rename to core/src/main/java/bisq/core/util/validation/MonetaryValidator.java index 47c9d50545..08a0b321c2 100644 --- a/desktop/src/main/java/bisq/desktop/util/validation/MonetaryValidator.java +++ b/core/src/main/java/bisq/core/util/validation/MonetaryValidator.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.desktop.util.validation; +package bisq.core.util.validation; import bisq.core.locale.Res; @@ -33,8 +33,8 @@ public abstract class MonetaryValidator extends NumberValidator { } @Override - public ValidationResult validate(String input) { - ValidationResult result = validateIfNotEmpty(input); + public InputValidator.ValidationResult validate(String input) { + InputValidator.ValidationResult result = validateIfNotEmpty(input); if (result.isValid) { input = cleanInput(input); result = validateIfNumber(input); @@ -51,19 +51,19 @@ public abstract class MonetaryValidator extends NumberValidator { return result; } - protected ValidationResult validateIfNotExceedsMinValue(String input) { + protected InputValidator.ValidationResult validateIfNotExceedsMinValue(String input) { double d = Double.parseDouble(input); if (d < getMinValue()) - return new ValidationResult(false, Res.get("validation.fiat.toSmall")); + return new InputValidator.ValidationResult(false, Res.get("validation.fiat.toSmall")); else - return new ValidationResult(true); + return new InputValidator.ValidationResult(true); } - protected ValidationResult validateIfNotExceedsMaxValue(String input) { + protected InputValidator.ValidationResult validateIfNotExceedsMaxValue(String input) { double d = Double.parseDouble(input); if (d > getMaxValue()) - return new ValidationResult(false, Res.get("validation.fiat.toLarge")); + return new InputValidator.ValidationResult(false, Res.get("validation.fiat.toLarge")); else - return new ValidationResult(true); + return new InputValidator.ValidationResult(true); } } diff --git a/desktop/src/main/java/bisq/desktop/util/validation/NumberValidator.java b/core/src/main/java/bisq/core/util/validation/NumberValidator.java similarity index 95% rename from desktop/src/main/java/bisq/desktop/util/validation/NumberValidator.java rename to core/src/main/java/bisq/core/util/validation/NumberValidator.java index 1aaa4162fb..7383ba4dd0 100644 --- a/desktop/src/main/java/bisq/desktop/util/validation/NumberValidator.java +++ b/core/src/main/java/bisq/core/util/validation/NumberValidator.java @@ -15,11 +15,10 @@ * along with Bisq. If not, see . */ -package bisq.desktop.util.validation; +package bisq.core.util.validation; import bisq.core.locale.Res; import bisq.core.util.ParsingUtils; -import bisq.core.util.validation.InputValidator; /** * NumberValidator for validating basic number values. diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index d6106e5559..04f2a585ac 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -428,6 +428,7 @@ offerbook.warning.requireUpdateToNewVersion=Your version of Bisq is not compatib Please update to the latest Bisq version at [HYPERLINK:https://bisq.network/downloads]. offerbook.warning.offerWasAlreadyUsedInTrade=You cannot take this offer because you already took it earlier. \ It could be that your previous take-offer attempt resulted in a failed trade. +offerbook.warning.hideBsqSwapsDueDaoDeactivated=You cannot take this offer because you have deactivated the DAO offerbook.info.sellAtMarketPrice=You will sell at market price (updated every minute). offerbook.info.buyAtMarketPrice=You will buy at market price (updated every minute). @@ -440,6 +441,9 @@ offerbook.info.sellAtFixedPrice=You will sell at this fixed price. offerbook.info.noArbitrationInUserLanguage=In case of a dispute, please note that arbitration for this offer will be handled in {0}. Language is currently set to {1}. offerbook.info.roundedFiatVolume=The amount was rounded to increase the privacy of your trade. +offerbook.bsqSwap.createOffer=Create Bsq swap offer + + #################################################################### # Offerbook / Create offer #################################################################### @@ -517,6 +521,19 @@ createOffer.securityDepositInfo=Your buyer''s security deposit will be {0} createOffer.securityDepositInfoAsBuyer=Your security deposit as buyer will be {0} createOffer.minSecurityDepositUsed=Min. buyer security deposit is used +createOffer.bsqSwap.missingFunds.maker=Your {0} wallet does not have sufficient funds for creating this offer.\n\n\ + Missing: {1}.\n\n\ + You still can create the offer, but it will be disabled until your wallet is sufficiently funded. +createOffer.bsqSwap.missingFunds.taker=Your {0} wallet does not have sufficient funds for taking this offer.\n\ + Missing: {1}. +createOffer.bsqSwap.offerVisibility=BSQ swap offers follow a different trade protocol than regular Bisq offers.\n\n\ + Specifically, when creating a BSQ swap offer, no maker fee transaction is created and no funds are reserved. \ + The offer is visible on the network as long there are sufficient funds in the maker's wallet to be able to fund the \ + trade. For BSQ, only confirmed BSQ are counted.\n\n\ + In case the maker's wallet does not have enough funds, the offer is automatically \ + removed from the offer book and added back once the wallet has sufficient funds again. + +createOffer.bsqSwap.mintingPow=Creating proof of work... #################################################################### # Offerbook / Take offer @@ -543,6 +560,9 @@ takeOffer.success.headline=You have successfully taken an offer. takeOffer.success.info=You can see the status of your trade at \"Portfolio/Open trades\". takeOffer.error.message=An error occurred when taking the offer.\n\n{0} +takeOffer.bsqSwap.success.headline=Your BSQ swap trade is completed +takeOffer.bsqSwap.success.info=You can see your completed trade at \"Portfolio/Unconfirmed BSQ Swaps\" + # new entries takeOffer.takeOfferButton=Review: Take offer to {0} bitcoin takeOffer.noPriceFeedAvailable=You cannot take that offer as it uses a percentage price based on the market price but there is no price feed available. @@ -578,6 +598,8 @@ openOffer.triggerPrice=Trigger price {0} openOffer.triggered=The offer has been deactivated because the market price reached your trigger price.\n\ Please edit the offer to define a new trigger price +openOffer.bsqSwap.missingFunds=Open BSQ swap offer is disabled because there are not sufficient funds in the wallet + editOffer.setPrice=Set price editOffer.confirmEdit=Confirm: Edit offer editOffer.publishOffer=Publishing your offer. @@ -585,6 +607,29 @@ editOffer.failed=Editing of offer failed:\n{0} editOffer.success=Your offer has been successfully edited. editOffer.invalidDeposit=The buyer's security deposit is not within the constraints defined by the Bisq DAO and can no longer be edited. + +#################################################################### +# BSQ Swap offer +#################################################################### + +bsqSwapOffer.amounts.headline=Fee details +bsqSwapOffer.estimated={0} estimated +bsqSwapOffer.inputAmount=Required input amount +bsqSwapOffer.payoutAmount=Receive payout amount +bsqSwapOffer.inputAmount.details.buyer=(= {0} trade amount + {1} trade fee) +bsqSwapOffer.inputAmount.details.seller=(= {0} trade amount + {1} mining fee) +bsqSwapOffer.outputAmount.details.buyer=(= {0} trade amount - {1} mining fee) +bsqSwapOffer.outputAmount.details.seller=(= {0} trade amount - {1} trade fee) +bsqSwapOffer.feeHandling=Fee handling for BSQ swaps is different from normal Bisq trades.\n\ + To avoid additional transaction inputs for the trade fee or for the miner fee, we subtract the relevant fee from \ + the expected transaction output. This is different for buyers and sellers:\n\ + - BTC buyers have only BSQ inputs which include the BSQ trade fee and receive the BTC trade amount \ + with the miner fee subtracted.\n\ + - BTC sellers have only BTC inputs which include the BTC miner fee and receive the \ + BSQ trade amount with the BSQ trade fee subtracted.\n\n\ + To learn more about BSQ swaps please see documentation [HYPERLINK:https://bisq.wiki/BSQ_swaps]. + + #################################################################### # Portfolio #################################################################### @@ -592,6 +637,7 @@ editOffer.invalidDeposit=The buyer's security deposit is not within the constrai portfolio.tab.openOffers=My open offers portfolio.tab.pendingTrades=Open trades portfolio.tab.history=History +portfolio.tab.bsqSwap=Unconfirmed BSQ swaps portfolio.tab.failed=Failed portfolio.tab.editOpenOffer=Edit offer portfolio.tab.duplicateOffer=Duplicate offer @@ -600,7 +646,7 @@ portfolio.context.notYourOffer=You can only duplicate offers where you were the portfolio.closedTrades.deviation.help=Percentage price deviation from market -portfolio.pending.invalidTx=There is an issue with a missing or invalid transaction.\n\n\ +portfolio.pending.invalidTx=There is an issue wi~th a missing or invalid transaction.\n\n\ Please do NOT send the fiat or altcoin payment.\n\n\ Open a support ticket to get assistance from a Mediator.\n\n\ Error message: {0} @@ -1102,6 +1148,11 @@ funds.tx.dustAttackTx.popup=This transaction is sending a very small BTC amount To protect your privacy the Bisq wallet ignores such dust outputs for spending purposes and in the balance display. \ You can set the threshold amount when an output is considered dust in the settings. +funds.tx.bsqSwapBuy=Bought BTC: +funds.tx.bsqSwapSell=Sold BTC: +funds.tx.bsqSwapTx=BSQ Swap trade: {0} + + #################################################################### # Support #################################################################### @@ -2372,6 +2423,11 @@ dao.tx.issuanceFromCompReq.tooltip=Compensation request which led to an issuance dao.tx.issuanceFromReimbursement=Reimbursement request/issuance dao.tx.issuanceFromReimbursement.tooltip=Reimbursement request which led to an issuance of new BSQ.\n\ Issuance date: {0} + +dao.tx.bsqSwapTx=BSQ Swap transaction +dao.tx.bsqSwapTrade=BSQ Swap trade: {0} + + dao.proposal.create.missingBsqFunds=You don''t have sufficient BSQ funds for creating the proposal. If you have an \ unconfirmed BSQ transaction you need to wait for a blockchain confirmation because BSQ is validated only if it is \ included in a block.\n\ @@ -2691,6 +2747,8 @@ filterWindow.remove=Remove filter filterWindow.btcFeeReceiverAddresses=BTC fee receiver addresses filterWindow.disableApi=Disable API filterWindow.disableMempoolValidation=Disable Mempool Validation +filterWindow.disablePowMessage=Disable messages requiring Proof of Work +filterWindow.powDifficulty=Proof of work difficulty (BSQ swap offers) offerDetailsWindow.minBtcAmount=Min. BTC amount offerDetailsWindow.min=(min. {0}) @@ -2762,6 +2820,9 @@ tradeDetailsWindow.tradeState=Trade state tradeDetailsWindow.agentAddresses=Arbitrator/Mediator tradeDetailsWindow.detailData=Detail data +tradeDetailsWindow.bsqSwap.txId=BSQ swap transaction ID +tradeDetailsWindow.bsqSwap.headline=BSQ swap trade + txDetailsWindow.headline=Transaction Details txDetailsWindow.btc.note=You have sent BTC. txDetailsWindow.bsq.note=You have sent BSQ funds. \ @@ -2835,7 +2896,7 @@ popup.reportError={0}\n\nTo help us to improve the software please report this b The above error message will be copied to the clipboard when you click either of the buttons below.\n\ It will make debugging easier if you include the bisq.log file by pressing "Open log file", saving a copy, and attaching it to your bug report. -popup.error.tryRestart=Please try to restart your application and check your network connection to see if you can resolve the issue. +popup.error.tryRestart=Please restart your application and check your network connection to see if you can resolve the issue. popup.error.takeOfferRequestFailed=An error occurred when someone tried to take one of your offers:\n{0} error.spvFileCorrupted=An error occurred when reading the SPV chain file.\nIt might be that the SPV chain file is corrupted.\n\nError message: {0}\n\nDo you want to delete it and start a resync? @@ -3043,7 +3104,10 @@ notification.walletUpdate.msg=Your trading wallet is sufficiently funded.\nAmoun notification.takeOffer.walletUpdate.msg=Your trading wallet was already sufficiently funded from an earlier take offer attempt.\nAmount: {0} notification.tradeCompleted.headline=Trade completed notification.tradeCompleted.msg=You can withdraw your funds now to your external Bitcoin wallet or transfer it to the Bisq wallet. - +notification.bsqSwap.maker.headline=BSQ swap completed +notification.bsqSwap.maker.tradeCompleted=Your offer with ID ''{0}'' has been taken. +notification.bsqSwap.confirmed.headline=BSQ swap transaction confirmed +notification.bsqSwap.confirmed.text=The BSQ swap transaction for trade with ID ''{0}'' is confirmed. #################################################################### # System Tray @@ -3115,6 +3179,7 @@ confidence.unknown=Unknown transaction status confidence.seen=Seen by {0} peer(s) / 0 confirmations confidence.confirmed=Confirmed in {0} block(s) confidence.invalid=Transaction is invalid +confidence.confirmed.short=Confirmed peerInfo.title=Peer info peerInfo.nrOfTrades=Number of completed trades @@ -3150,6 +3215,8 @@ navigation.funds.availableForWithdrawal=\"Funds/Send funds\" navigation.portfolio.myOpenOffers=\"Portfolio/My open offers\" navigation.portfolio.pending=\"Portfolio/Open trades\" navigation.portfolio.closedTrades=\"Portfolio/History\" +navigation.portfolio.bsqSwapTrades=\"Portfolio/Unconfirmed BSQ Swaps\" +navigation.portfolio.bsqSwapTrades.short=\"Unconfirmed BSQ Swaps\" navigation.funds.depositFunds=\"Funds/Receive funds\" navigation.settings.preferences=\"Settings/Preferences\" # suppress inspection "UnusedProperty" @@ -3287,6 +3354,11 @@ payment.altcoin.tradeInstant.popup=For instant trading it is required that both those offers under the 'Portfolio' screen. payment.altcoin=Altcoin payment.select.altcoin=Select or search Altcoin +payment.select.altcoin.bsq.warning=You can also trade BSQ with the new BSQ Swap protocol.\n\n\ + This has many benefits compared to the normal Bisq protocol for altcoins. \ + For example: trades are instant, riskless, cheaper, and there is no account setup needed.\n\n\ + See more about BSQ swaps in documentation [HYPERLINK:https://bisq.wiki/BSQ_swaps]. + payment.secret=Secret question payment.answer=Answer payment.wallet=Wallet ID @@ -3860,6 +3932,8 @@ VERSE=Verse STRIKE=Strike # suppress inspection "UnusedProperty" SWIFT=SWIFT International Wire Transfer +# suppress inspection "UnusedProperty" +BSQ_SWAP=BSQ Swap # Deprecated: Cannot be deleted as it would break old trade history entries # suppress inspection "UnusedProperty" @@ -3946,6 +4020,8 @@ VERSE_SHORT=Verse STRIKE_SHORT=Strike # suppress inspection "UnusedProperty" SWIFT_SHORT=SWIFT +# suppress inspection "UnusedProperty" +BSQ_SWAP_SHORT=BSQ Swap # Deprecated: Cannot be deleted as it would break old trade history entries # suppress inspection "UnusedProperty" @@ -4037,3 +4113,15 @@ validation.phone.invalidDialingCode=Country dialing code for number {0} is inval The correct dialing code is {2}. validation.invalidAddressList=Must be comma separated list of valid addresses validation.capitual.invalidFormat=Must be a valid CAP code of format: CAP-XXXXXX (6 alphanumeric characters) + + +#################################################################### +# News +#################################################################### + +news.bsqSwap.title=New trade protocol: BSQ SWAPS +news.bsqSwap.description=BSQ swaps is a new trade protocol for atomically swapping BSQ and BTC in a single \ + transaction.\n\n\ + This saves miner fees, allows instant trades, removes counterparty risk, and does not require \ + mediation or arbitration support. No account setup is required either.\n\n\ + See more about BSQ swaps in documentation [HYPERLINK:https://bisq.wiki/BSQ_swaps]. diff --git a/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java b/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java index cf47c712d4..a3df97d74c 100644 --- a/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java +++ b/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java @@ -21,7 +21,7 @@ import bisq.core.account.sign.SignedWitness; import bisq.core.account.sign.SignedWitnessService; import bisq.core.filter.FilterManager; import bisq.core.locale.CountryUtil; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.bisq_v1.OfferPayload; import bisq.core.payment.ChargeBackRisk; import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.payment.payload.PaymentMethod; @@ -31,7 +31,7 @@ import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.DisputeResult; import bisq.core.support.dispute.arbitration.TraderDataItem; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; -import bisq.core.trade.Contract; +import bisq.core.trade.model.bisq_v1.Contract; import bisq.network.p2p.P2PService; import bisq.network.p2p.storage.persistence.AppendOnlyDataStoreService; diff --git a/core/src/test/java/bisq/core/offer/OfferMaker.java b/core/src/test/java/bisq/core/offer/OfferMaker.java index aa9d294c2e..2e89ec24fb 100644 --- a/core/src/test/java/bisq/core/offer/OfferMaker.java +++ b/core/src/test/java/bisq/core/offer/OfferMaker.java @@ -17,6 +17,8 @@ package bisq.core.offer; +import bisq.core.offer.bisq_v1.OfferPayload; + import com.natpryce.makeiteasy.Instantiator; import com.natpryce.makeiteasy.Maker; import com.natpryce.makeiteasy.Property; @@ -30,7 +32,7 @@ public class OfferMaker { public static final Property amount = new Property<>(); public static final Property baseCurrencyCode = new Property<>(); public static final Property counterCurrencyCode = new Property<>(); - public static final Property direction = new Property<>(); + public static final Property direction = new Property<>(); public static final Property useMarketBasedPrice = new Property<>(); public static final Property marketPriceMargin = new Property<>(); public static final Property id = new Property<>(); @@ -40,7 +42,7 @@ public class OfferMaker { 0L, null, null, - lookup.valueOf(direction, OfferPayload.Direction.BUY), + lookup.valueOf(direction, OfferDirection.BUY), lookup.valueOf(price, 100000L), lookup.valueOf(marketPriceMargin, 0.0), lookup.valueOf(useMarketBasedPrice, false), diff --git a/core/src/test/java/bisq/core/offer/OfferTest.java b/core/src/test/java/bisq/core/offer/OfferTest.java index 9300c01574..4d2b03ff7b 100644 --- a/core/src/test/java/bisq/core/offer/OfferTest.java +++ b/core/src/test/java/bisq/core/offer/OfferTest.java @@ -17,6 +17,8 @@ package bisq.core.offer; +import bisq.core.offer.bisq_v1.OfferPayload; + import org.junit.Test; import static org.junit.Assert.assertFalse; diff --git a/core/src/test/java/bisq/core/offer/OpenOfferManagerTest.java b/core/src/test/java/bisq/core/offer/OpenOfferManagerTest.java index 63b4e406bb..d961f6c256 100644 --- a/core/src/test/java/bisq/core/offer/OpenOfferManagerTest.java +++ b/core/src/test/java/bisq/core/offer/OpenOfferManagerTest.java @@ -1,7 +1,8 @@ package bisq.core.offer; import bisq.core.api.CoreContext; -import bisq.core.trade.TradableList; +import bisq.core.offer.bisq_v1.OfferPayload; +import bisq.core.trade.model.TradableList; import bisq.network.p2p.P2PService; import bisq.network.p2p.peers.PeerManager; @@ -46,10 +47,8 @@ public class OpenOfferManagerTest { public void testStartEditOfferForActiveOffer() { P2PService p2PService = mock(P2PService.class); OfferBookService offerBookService = mock(OfferBookService.class); - when(p2PService.getPeerManager()).thenReturn(mock(PeerManager.class)); - - final OpenOfferManager manager = new OpenOfferManager(coreContext, + OpenOfferManager manager = new OpenOfferManager(coreContext, null, null, null, @@ -68,7 +67,8 @@ public class OpenOfferManagerTest { null, null, null, - persistenceManager); + persistenceManager + ); AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false); @@ -95,8 +95,7 @@ public class OpenOfferManagerTest { P2PService p2PService = mock(P2PService.class); OfferBookService offerBookService = mock(OfferBookService.class); when(p2PService.getPeerManager()).thenReturn(mock(PeerManager.class)); - - final OpenOfferManager manager = new OpenOfferManager(coreContext, + OpenOfferManager manager = new OpenOfferManager(coreContext, null, null, null, @@ -115,7 +114,8 @@ public class OpenOfferManagerTest { null, null, null, - persistenceManager); + persistenceManager + ); AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false); @@ -133,10 +133,8 @@ public class OpenOfferManagerTest { public void testStartEditOfferForOfferThatIsCurrentlyEdited() { P2PService p2PService = mock(P2PService.class); OfferBookService offerBookService = mock(OfferBookService.class); - when(p2PService.getPeerManager()).thenReturn(mock(PeerManager.class)); - - final OpenOfferManager manager = new OpenOfferManager(coreContext, + OpenOfferManager manager = new OpenOfferManager(coreContext, null, null, null, @@ -155,7 +153,8 @@ public class OpenOfferManagerTest { null, null, null, - persistenceManager); + persistenceManager + ); AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false); diff --git a/core/src/test/java/bisq/core/trade/TradableListTest.java b/core/src/test/java/bisq/core/trade/TradableListTest.java index e6a41bca6a..5933fc0faf 100644 --- a/core/src/test/java/bisq/core/trade/TradableListTest.java +++ b/core/src/test/java/bisq/core/trade/TradableListTest.java @@ -18,8 +18,9 @@ package bisq.core.trade; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload; import bisq.core.offer.OpenOffer; +import bisq.core.offer.bisq_v1.OfferPayload; +import bisq.core.trade.model.TradableList; import org.junit.Test; diff --git a/core/src/test/java/bisq/core/user/UserPayloadModelVOTest.java b/core/src/test/java/bisq/core/user/UserPayloadModelVOTest.java index 7d98e95923..e6340678ea 100644 --- a/core/src/test/java/bisq/core/user/UserPayloadModelVOTest.java +++ b/core/src/test/java/bisq/core/user/UserPayloadModelVOTest.java @@ -69,7 +69,9 @@ public class UserPayloadModelVOTest { Lists.newArrayList(), new HashSet<>(), false, - false)); + false, + false, + 0)); vo.setRegisteredArbitrator(ArbitratorTest.getArbitratorMock()); vo.setRegisteredMediator(MediatorTest.getMediatorMock()); diff --git a/core/src/test/java/bisq/core/util/FeeReceiverSelectorTest.java b/core/src/test/java/bisq/core/util/FeeReceiverSelectorTest.java index fc6a1f534d..a5632e7207 100644 --- a/core/src/test/java/bisq/core/util/FeeReceiverSelectorTest.java +++ b/core/src/test/java/bisq/core/util/FeeReceiverSelectorTest.java @@ -142,6 +142,8 @@ public class FeeReceiverSelectorTest { Lists.newArrayList(), new HashSet<>(), false, - false); + false, + false, + 0); } } diff --git a/core/src/test/java/bisq/core/util/ProtoUtilTest.java b/core/src/test/java/bisq/core/util/ProtoUtilTest.java index dee3b4a249..1f11899bde 100644 --- a/core/src/test/java/bisq/core/util/ProtoUtilTest.java +++ b/core/src/test/java/bisq/core/util/ProtoUtilTest.java @@ -17,12 +17,11 @@ package bisq.core.util; +import bisq.core.offer.OfferDirection; import bisq.core.offer.OpenOffer; import bisq.common.proto.ProtoUtil; -import protobuf.OfferPayload; - import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -34,10 +33,10 @@ public class ProtoUtilTest { //TODO Use NetworkProtoResolver, PersistenceProtoResolver or ProtoResolver which are all in bisq.common. @Test public void testEnum() { - OfferPayload.Direction direction = OfferPayload.Direction.SELL; - OfferPayload.Direction direction2 = OfferPayload.Direction.BUY; - OfferPayload.Direction realDirection = getDirection(direction); - OfferPayload.Direction realDirection2 = getDirection(direction2); + OfferDirection direction = OfferDirection.SELL; + OfferDirection direction2 = OfferDirection.BUY; + OfferDirection realDirection = getDirection(direction); + OfferDirection realDirection2 = getDirection(direction2); assertEquals("SELL", realDirection.name()); assertEquals("BUY", realDirection2.name()); } @@ -63,7 +62,7 @@ public class ProtoUtilTest { } } - public static OfferPayload.Direction getDirection(OfferPayload.Direction direction) { - return OfferPayload.Direction.valueOf(direction.name()); + public static OfferDirection getDirection(OfferDirection direction) { + return OfferDirection.valueOf(direction.name()); } } diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcErrorMessageHandler.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcErrorMessageHandler.java index 4c139e1709..d5e33036ea 100644 --- a/daemon/src/main/java/bisq/daemon/grpc/GrpcErrorMessageHandler.java +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcErrorMessageHandler.java @@ -134,7 +134,7 @@ public class GrpcErrorMessageHandler implements ErrorMessageHandler { } private String getAvailabilityResultDescription(AvailabilityResult proto) { - return bisq.core.offer.AvailabilityResult.fromProto(proto).description(); + return bisq.core.offer.availability.AvailabilityResult.fromProto(proto).description(); } private boolean takeOfferWasCalled() { diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java index e31828bbfb..bafd4c17d0 100644 --- a/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java @@ -18,16 +18,23 @@ package bisq.daemon.grpc; import bisq.core.api.CoreApi; +import bisq.core.api.model.BsqSwapOfferInfo; import bisq.core.api.model.OfferInfo; import bisq.core.offer.Offer; import bisq.core.offer.OpenOffer; import bisq.proto.grpc.CancelOfferReply; import bisq.proto.grpc.CancelOfferRequest; +import bisq.proto.grpc.CreateBsqSwapOfferReply; +import bisq.proto.grpc.CreateBsqSwapOfferRequest; import bisq.proto.grpc.CreateOfferReply; import bisq.proto.grpc.CreateOfferRequest; import bisq.proto.grpc.EditOfferReply; import bisq.proto.grpc.EditOfferRequest; +import bisq.proto.grpc.GetBsqSwapOfferReply; +import bisq.proto.grpc.GetBsqSwapOffersReply; +import bisq.proto.grpc.GetMyBsqSwapOfferReply; +import bisq.proto.grpc.GetMyBsqSwapOffersReply; import bisq.proto.grpc.GetMyOfferReply; import bisq.proto.grpc.GetMyOfferRequest; import bisq.proto.grpc.GetMyOffersReply; @@ -49,6 +56,7 @@ import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; +import static bisq.core.api.model.BsqSwapOfferInfo.toBsqSwapOfferInfo; import static bisq.core.api.model.OfferInfo.toOfferInfo; import static bisq.core.api.model.OfferInfo.toPendingOfferInfo; import static bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig.getCustomRateMeteringInterceptor; @@ -73,6 +81,21 @@ class GrpcOffersService extends OffersImplBase { this.exceptionHandler = exceptionHandler; } + @Override + public void getBsqSwapOffer(GetOfferRequest req, + StreamObserver responseObserver) { + try { + Offer offer = coreApi.getOffer(req.getId()); + var reply = GetBsqSwapOfferReply.newBuilder() + .setBsqSwapOffer(toBsqSwapOfferInfo(offer).toProtoMessage()) + .build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } catch (Throwable cause) { + exceptionHandler.handleException(log, cause, responseObserver); + } + } + @Override public void getOffer(GetOfferRequest req, StreamObserver responseObserver) { @@ -88,6 +111,21 @@ class GrpcOffersService extends OffersImplBase { } } + @Override + public void getMyBsqSwapOffer(GetMyOfferRequest req, + StreamObserver responseObserver) { + try { + Offer offer = coreApi.getMyBsqSwapOffer(req.getId()); + var reply = GetMyBsqSwapOfferReply.newBuilder() + .setBsqSwapOffer(toBsqSwapOfferInfo(offer /* TODO support triggerPrice */).toProtoMessage()) + .build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } catch (Throwable cause) { + exceptionHandler.handleException(log, cause, responseObserver); + } + } + @Override public void getMyOffer(GetMyOfferRequest req, StreamObserver responseObserver) { @@ -103,6 +141,25 @@ class GrpcOffersService extends OffersImplBase { } } + @Override + public void getBsqSwapOffers(GetOffersRequest req, + StreamObserver responseObserver) { + try { + List result = coreApi.getBsqSwapOffers(req.getDirection()) + .stream().map(BsqSwapOfferInfo::toBsqSwapOfferInfo) + .collect(Collectors.toList()); + var reply = GetBsqSwapOffersReply.newBuilder() + .addAllBsqSwapOffers(result.stream() + .map(BsqSwapOfferInfo::toProtoMessage) + .collect(Collectors.toList())) + .build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } catch (Throwable cause) { + exceptionHandler.handleException(log, cause, responseObserver); + } + } + @Override public void getOffers(GetOffersRequest req, StreamObserver responseObserver) { @@ -122,6 +179,25 @@ class GrpcOffersService extends OffersImplBase { } } + @Override + public void getMyBsqSwapOffers(GetMyOffersRequest req, + StreamObserver responseObserver) { + try { + List result = coreApi.getMyBsqSwapOffers(req.getDirection()) + .stream().map(BsqSwapOfferInfo::toBsqSwapOfferInfo) + .collect(Collectors.toList()); + var reply = GetMyBsqSwapOffersReply.newBuilder() + .addAllBsqSwapOffers(result.stream() + .map(BsqSwapOfferInfo::toProtoMessage) + .collect(Collectors.toList())) + .build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } catch (Throwable cause) { + exceptionHandler.handleException(log, cause, responseObserver); + } + } + @Override public void getMyOffers(GetMyOffersRequest req, StreamObserver responseObserver) { @@ -142,11 +218,35 @@ class GrpcOffersService extends OffersImplBase { } } + @Override + public void createBsqSwapOffer(CreateBsqSwapOfferRequest req, + StreamObserver responseObserver) { + try { + //todo PaymentAccount for bsq swap not needed as its just a dummy account + coreApi.createAndPlaceBsqSwapOffer( + req.getDirection(), + req.getAmount(), + req.getMinAmount(), + req.getPrice(), + /* req.getPaymentAccountId(),*/ + offer -> { + BsqSwapOfferInfo bsqSwapOfferInfo = toBsqSwapOfferInfo(offer); + CreateBsqSwapOfferReply reply = CreateBsqSwapOfferReply.newBuilder() + .setBsqSwapOffer(bsqSwapOfferInfo.toProtoMessage()) + .build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + }); + } catch (Throwable cause) { + exceptionHandler.handleException(log, cause, responseObserver); + } + } + @Override public void createOffer(CreateOfferRequest req, StreamObserver responseObserver) { try { - coreApi.createAnPlaceOffer( + coreApi.createAndPlaceOffer( req.getCurrencyCode(), req.getDirection(), req.getPrice(), diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcPaymentAccountsService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcPaymentAccountsService.java index 9ac400d100..1473f30fed 100644 --- a/daemon/src/main/java/bisq/daemon/grpc/GrpcPaymentAccountsService.java +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcPaymentAccountsService.java @@ -136,7 +136,8 @@ class GrpcPaymentAccountsService extends PaymentAccountsImplBase { PaymentAccount paymentAccount = coreApi.createCryptoCurrencyPaymentAccount(req.getAccountName(), req.getCurrencyCode(), req.getAddress(), - req.getTradeInstant()); + req.getTradeInstant(), + req.getIsBsqSwap()); var reply = CreateCryptoCurrencyPaymentAccountReply.newBuilder() .setPaymentAccount(paymentAccount.toProtoMessage()) .build(); diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcTradesService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcTradesService.java index 7ffea95e6a..5e712e253e 100644 --- a/daemon/src/main/java/bisq/daemon/grpc/GrpcTradesService.java +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcTradesService.java @@ -18,17 +18,21 @@ package bisq.daemon.grpc; import bisq.core.api.CoreApi; +import bisq.core.api.model.BsqSwapTradeInfo; import bisq.core.api.model.TradeInfo; -import bisq.core.trade.Trade; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.proto.grpc.ConfirmPaymentReceivedReply; import bisq.proto.grpc.ConfirmPaymentReceivedRequest; import bisq.proto.grpc.ConfirmPaymentStartedReply; import bisq.proto.grpc.ConfirmPaymentStartedRequest; +import bisq.proto.grpc.GetBsqSwapTradeReply; import bisq.proto.grpc.GetTradeReply; import bisq.proto.grpc.GetTradeRequest; import bisq.proto.grpc.KeepFundsReply; import bisq.proto.grpc.KeepFundsRequest; +import bisq.proto.grpc.TakeBsqSwapOfferReply; +import bisq.proto.grpc.TakeBsqSwapOfferRequest; import bisq.proto.grpc.TakeOfferReply; import bisq.proto.grpc.TakeOfferRequest; import bisq.proto.grpc.WithdrawFundsReply; @@ -44,6 +48,7 @@ import java.util.Optional; import lombok.extern.slf4j.Slf4j; +import static bisq.core.api.model.BsqSwapTradeInfo.toBsqSwapTradeInfo; import static bisq.core.api.model.TradeInfo.toNewTradeInfo; import static bisq.core.api.model.TradeInfo.toTradeInfo; import static bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig.getCustomRateMeteringInterceptor; @@ -68,6 +73,51 @@ class GrpcTradesService extends TradesImplBase { this.exceptionHandler = exceptionHandler; } + @Override + public void getBsqSwapTrade(GetTradeRequest req, + StreamObserver responseObserver) { + try { + var bsqSwapTrade = coreApi.getBsqSwapTrade(req.getTradeId()); + // String role = coreApi.getBsqSwapTradeRole(req.getTradeId()); + var reply = GetBsqSwapTradeReply.newBuilder() + .setBsqSwapTrade(toBsqSwapTradeInfo(bsqSwapTrade).toProtoMessage()) + .build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } catch (IllegalArgumentException cause) { + // Offer makers may call 'gettrade' many times before a trade exists. + // Log a 'trade not found' warning instead of a full stack trace. + exceptionHandler.handleExceptionAsWarning(log, "getBsqSwapTrade", cause, responseObserver); + } catch (Throwable cause) { + exceptionHandler.handleException(log, cause, responseObserver); + } + } + + @Override + public void takeBsqSwapOffer(TakeBsqSwapOfferRequest req, + StreamObserver responseObserver) { + GrpcErrorMessageHandler errorMessageHandler = + new GrpcErrorMessageHandler(getTakeOfferMethod().getFullMethodName(), + responseObserver, + exceptionHandler, + log); + coreApi.takeBsqSwapOffer(req.getOfferId(), + req.getPaymentAccountId(), + req.getTakerFeeCurrencyCode(), + bsqSwapTrade -> { + BsqSwapTradeInfo bsqSwapTradeInfo = toBsqSwapTradeInfo(bsqSwapTrade); + var reply = TakeBsqSwapOfferReply.newBuilder() + .setBsqSwapTrade(bsqSwapTradeInfo.toProtoMessage()) + .build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + }, + errorMessage -> { + if (!errorMessageHandler.isErrorHandled()) + errorMessageHandler.handleErrorMessage(errorMessage); + }); + } + @Override public void getTrade(GetTradeRequest req, StreamObserver responseObserver) { diff --git a/desktop/src/main/java/bisq/desktop/bisq.css b/desktop/src/main/java/bisq/desktop/bisq.css index 8b0b830f90..d8aac0472d 100644 --- a/desktop/src/main/java/bisq/desktop/bisq.css +++ b/desktop/src/main/java/bisq/desktop/bisq.css @@ -2097,6 +2097,11 @@ textfield */ -fx-text-fill: -bs-color-green-3; } +.dao-tx-type-bsq-swap-icon, +.dao-tx-type-bsq-swap-icon:hover { + -fx-text-fill: -bs-color-blue-4; +} + .dao-accepted-icon { -fx-text-fill: -bs-color-primary; } diff --git a/desktop/src/main/java/bisq/desktop/components/PeerInfoIcon.java b/desktop/src/main/java/bisq/desktop/components/PeerInfoIcon.java index d3ae4b4fba..f1705774cc 100644 --- a/desktop/src/main/java/bisq/desktop/components/PeerInfoIcon.java +++ b/desktop/src/main/java/bisq/desktop/components/PeerInfoIcon.java @@ -23,7 +23,7 @@ import bisq.desktop.util.DisplayUtils; import bisq.core.alert.PrivateNotificationManager; import bisq.core.locale.Res; import bisq.core.offer.Offer; -import bisq.core.trade.Trade; +import bisq.core.trade.model.TradeModel; import bisq.core.user.Preferences; import bisq.network.p2p.NodeAddress; @@ -147,7 +147,7 @@ public class PeerInfoIcon extends Group { protected void addMouseListener(int numTrades, PrivateNotificationManager privateNotificationManager, - @Nullable Trade trade, + @Nullable TradeModel tradeModel, Offer offer, Preferences preferences, boolean useDevPrivilegeKeys, @@ -170,7 +170,7 @@ public class PeerInfoIcon extends Group { Res.get("peerInfo.unknownAge") : null; - setOnMouseClicked(e -> new PeerInfoWithTagEditor(privateNotificationManager, trade, offer, preferences, useDevPrivilegeKeys) + setOnMouseClicked(e -> new PeerInfoWithTagEditor(privateNotificationManager, tradeModel, offer, preferences, useDevPrivilegeKeys) .fullAddress(fullAddress) .numTrades(numTrades) .accountAge(accountAgeFormatted) diff --git a/desktop/src/main/java/bisq/desktop/components/PeerInfoIconSmall.java b/desktop/src/main/java/bisq/desktop/components/PeerInfoIconSmall.java index 4fa2a2b2a6..32f70cf996 100644 --- a/desktop/src/main/java/bisq/desktop/components/PeerInfoIconSmall.java +++ b/desktop/src/main/java/bisq/desktop/components/PeerInfoIconSmall.java @@ -3,7 +3,7 @@ package bisq.desktop.components; import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.alert.PrivateNotificationManager; import bisq.core.offer.Offer; -import bisq.core.trade.Trade; +import bisq.core.trade.model.TradeModel; import bisq.core.user.Preferences; import bisq.network.p2p.NodeAddress; @@ -36,7 +36,7 @@ public class PeerInfoIconSmall extends PeerInfoIconTrading { @Override protected void addMouseListener(int numTrades, PrivateNotificationManager privateNotificationManager, - @Nullable Trade trade, + @Nullable TradeModel tradeModel, Offer offer, Preferences preferences, boolean useDevPrivilegeKeys, diff --git a/desktop/src/main/java/bisq/desktop/components/PeerInfoIconTrading.java b/desktop/src/main/java/bisq/desktop/components/PeerInfoIconTrading.java index fa063a2bf6..43a2c559e2 100644 --- a/desktop/src/main/java/bisq/desktop/components/PeerInfoIconTrading.java +++ b/desktop/src/main/java/bisq/desktop/components/PeerInfoIconTrading.java @@ -23,7 +23,8 @@ import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; import bisq.core.offer.Offer; import bisq.core.payment.payload.PaymentMethod; -import bisq.core.trade.Trade; +import bisq.core.trade.model.TradeModel; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.user.Preferences; import bisq.network.p2p.NodeAddress; @@ -40,7 +41,10 @@ import lombok.extern.slf4j.Slf4j; import javax.annotation.Nullable; -import static bisq.desktop.util.Colors.*; +import static bisq.desktop.util.Colors.AVATAR_BLUE; +import static bisq.desktop.util.Colors.AVATAR_GREEN; +import static bisq.desktop.util.Colors.AVATAR_ORANGE; +import static bisq.desktop.util.Colors.AVATAR_RED; import static com.google.common.base.Preconditions.checkNotNull; @Slf4j @@ -71,7 +75,7 @@ public class PeerInfoIconTrading extends PeerInfoIcon { String role, int numTrades, PrivateNotificationManager privateNotificationManager, - Trade trade, + TradeModel tradeModel, Preferences preferences, AccountAgeWitnessService accountAgeWitnessService, boolean useDevPrivilegeKeys) { @@ -79,8 +83,8 @@ public class PeerInfoIconTrading extends PeerInfoIcon { role, numTrades, privateNotificationManager, - trade.getOffer(), - trade, + tradeModel.getOffer(), + tradeModel, preferences, accountAgeWitnessService, useDevPrivilegeKeys); @@ -91,7 +95,7 @@ public class PeerInfoIconTrading extends PeerInfoIcon { int numTrades, PrivateNotificationManager privateNotificationManager, @Nullable Offer offer, - @Nullable Trade trade, + @Nullable TradeModel tradeModel, Preferences preferences, AccountAgeWitnessService accountAgeWitnessService, boolean useDevPrivilegeKeys) { @@ -99,17 +103,21 @@ public class PeerInfoIconTrading extends PeerInfoIcon { this.numTrades = numTrades; this.accountAgeWitnessService = accountAgeWitnessService; if (offer == null) { - checkNotNull(trade, "Trade must not be null if offer is null."); - offer = trade.getOffer(); + checkNotNull(tradeModel, "Trade must not be null if offer is null."); + offer = tradeModel.getOffer(); } checkNotNull(offer, "Offer must not be null"); - isFiatCurrency = offer != null && CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()); - initialize(role, offer, trade, privateNotificationManager, useDevPrivilegeKeys); + isFiatCurrency = CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()); + initialize(role, offer, tradeModel, privateNotificationManager, useDevPrivilegeKeys); } - protected void initialize(String role, Offer offer, Trade trade, PrivateNotificationManager privateNotificationManager, boolean useDevPrivilegeKeys) { + protected void initialize(String role, + Offer offer, + TradeModel tradeModel, + PrivateNotificationManager privateNotificationManager, + boolean useDevPrivilegeKeys) { boolean hasTraded = numTrades > 0; - Tuple5 peersAccount = getPeersAccountAge(trade, offer); + Tuple5 peersAccount = getPeersAccountAge(tradeModel, offer); Long accountAge = peersAccount.first; Long signAge = peersAccount.second; @@ -118,8 +126,8 @@ public class PeerInfoIconTrading extends PeerInfoIcon { Res.get("peerInfoIcon.tooltip.trade.traded", role, fullAddress, numTrades, getAccountAgeTooltip(accountAge)) : Res.get("peerInfoIcon.tooltip.trade.notTraded", role, fullAddress, getAccountAgeTooltip(accountAge)); - createAvatar(getRingColor(offer, trade, accountAge, signAge)); - addMouseListener(numTrades, privateNotificationManager, trade, offer, preferences, useDevPrivilegeKeys, + createAvatar(getRingColor(offer, tradeModel, accountAge, signAge)); + addMouseListener(numTrades, privateNotificationManager, tradeModel, offer, preferences, useDevPrivilegeKeys, isFiatCurrency, accountAge, signAge, peersAccount.third, peersAccount.fourth, peersAccount.fifth); } @@ -127,12 +135,12 @@ public class PeerInfoIconTrading extends PeerInfoIcon { return isFiatCurrency ? super.getAccountAgeTooltip(accountAge) : ""; } - protected Color getRingColor(Offer offer, Trade trade, Long accountAge, Long signAge) { + protected Color getRingColor(Offer offer, TradeModel tradeModel, Long accountAge, Long signAge) { // outer circle // for altcoins we always display green Color ringColor = AVATAR_GREEN; if (isFiatCurrency) { - switch (accountAgeWitnessService.getPeersAccountAgeCategory(hasChargebackRisk(trade, offer) ? signAge : accountAge)) { + switch (accountAgeWitnessService.getPeersAccountAgeCategory(hasChargebackRisk(tradeModel, offer) ? signAge : accountAge)) { case TWO_MONTHS_OR_MORE: ringColor = AVATAR_GREEN; break; @@ -152,25 +160,28 @@ public class PeerInfoIconTrading extends PeerInfoIcon { } /** - * @param trade Open trade for trading peer info to be shown + * @param tradeModel Open trade for trading peer info to be shown * @param offer Open offer for trading peer info to be shown * @return account age, sign age, account info, sign info, sign state */ - private Tuple5 getPeersAccountAge(@Nullable Trade trade, + private Tuple5 getPeersAccountAge(@Nullable TradeModel tradeModel, @Nullable Offer offer) { - AccountAgeWitnessService.SignState signState; + AccountAgeWitnessService.SignState signState = null; long signAge = -1L; long accountAge = -1L; - if (trade != null) { - offer = trade.getOffer(); + if (tradeModel != null) { + offer = tradeModel.getOffer(); if (offer == null) { // unexpected return new Tuple5<>(signAge, accountAge, Res.get("peerInfo.age.noRisk"), null, null); } - signState = accountAgeWitnessService.getSignState(trade); - signAge = accountAgeWitnessService.getWitnessSignAge(trade, new Date()); - accountAge = accountAgeWitnessService.getAccountAge(trade); + if (tradeModel instanceof Trade) { + Trade trade = (Trade) tradeModel; + signState = accountAgeWitnessService.getSignState(trade); + signAge = accountAgeWitnessService.getWitnessSignAge(trade, new Date()); + accountAge = accountAgeWitnessService.getAccountAge(trade); + } } else { checkNotNull(offer, "Offer must not be null if trade is null."); signState = accountAgeWitnessService.getSignState(offer); @@ -178,19 +189,20 @@ public class PeerInfoIconTrading extends PeerInfoIcon { accountAge = accountAgeWitnessService.getAccountAge(offer); } - if (hasChargebackRisk(trade, offer)) { + if (signState != null && hasChargebackRisk(tradeModel, offer)) { String signAgeInfo = Res.get("peerInfo.age.chargeBackRisk"); String accountSigningState = StringUtils.capitalize(signState.getDisplayString()); - if (signState.equals(AccountAgeWitnessService.SignState.UNSIGNED)) + if (signState.equals(AccountAgeWitnessService.SignState.UNSIGNED)) { signAgeInfo = null; + } return new Tuple5<>(accountAge, signAge, Res.get("peerInfo.age.noRisk"), signAgeInfo, accountSigningState); } return new Tuple5<>(accountAge, signAge, Res.get("peerInfo.age.noRisk"), null, null); } - private static boolean hasChargebackRisk(@Nullable Trade trade, @Nullable Offer offer) { - Offer offerToCheck = trade != null ? trade.getOffer() : offer; + private static boolean hasChargebackRisk(@Nullable TradeModel tradeModel, @Nullable Offer offer) { + Offer offerToCheck = tradeModel != null ? tradeModel.getOffer() : offer; return offerToCheck != null && PaymentMethod.hasChargebackRisk(offerToCheck.getPaymentMethod(), offerToCheck.getCurrencyCode()); diff --git a/desktop/src/main/java/bisq/desktop/components/paymentmethods/AssetsForm.java b/desktop/src/main/java/bisq/desktop/components/paymentmethods/AssetsForm.java index 2933099ee1..2a72811dea 100644 --- a/desktop/src/main/java/bisq/desktop/components/paymentmethods/AssetsForm.java +++ b/desktop/src/main/java/bisq/desktop/components/paymentmethods/AssetsForm.java @@ -236,8 +236,13 @@ public class AssetsForm extends PaymentMethodForm { ((AutocompleteComboBox) currencyComboBox).setOnChangeConfirmed(e -> { addressInputTextField.resetValidation(); addressInputTextField.validate(); - paymentAccount.setSingleTradeCurrency(currencyComboBox.getSelectionModel().getSelectedItem()); + TradeCurrency tradeCurrency = currencyComboBox.getSelectionModel().getSelectedItem(); + paymentAccount.setSingleTradeCurrency(tradeCurrency); updateFromInputs(); + + if (tradeCurrency != null && tradeCurrency.getCode().equals("BSQ")) { + new Popup().information(Res.get("payment.select.altcoin.bsq.warning")).show(); + } }); } } diff --git a/desktop/src/main/java/bisq/desktop/components/paymentmethods/PaymentMethodForm.java b/desktop/src/main/java/bisq/desktop/components/paymentmethods/PaymentMethodForm.java index 3aa4ba9363..df2a8fd2d3 100644 --- a/desktop/src/main/java/bisq/desktop/components/paymentmethods/PaymentMethodForm.java +++ b/desktop/src/main/java/bisq/desktop/components/paymentmethods/PaymentMethodForm.java @@ -34,7 +34,7 @@ import bisq.core.locale.FiatCurrency; import bisq.core.locale.Res; import bisq.core.locale.TradeCurrency; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferDirection; import bisq.core.payment.AssetAccount; import bisq.core.payment.PaymentAccount; import bisq.core.payment.payload.PaymentMethod; @@ -187,14 +187,14 @@ public abstract class PaymentMethodForm { Res.get("payment.maxPeriodAndLimitCrypto", getTimeText(hours), formatter.formatCoinWithCode(Coin.valueOf(accountAgeWitnessService.getMyTradeLimit( - paymentAccount, tradeCurrency.getCode(), OfferPayload.Direction.BUY)))) + paymentAccount, tradeCurrency.getCode(), OfferDirection.BUY)))) : Res.get("payment.maxPeriodAndLimit", getTimeText(hours), formatter.formatCoinWithCode(Coin.valueOf(accountAgeWitnessService.getMyTradeLimit( - paymentAccount, tradeCurrency.getCode(), OfferPayload.Direction.BUY))), + paymentAccount, tradeCurrency.getCode(), OfferDirection.BUY))), formatter.formatCoinWithCode(Coin.valueOf(accountAgeWitnessService.getMyTradeLimit( - paymentAccount, tradeCurrency.getCode(), OfferPayload.Direction.SELL))), + paymentAccount, tradeCurrency.getCode(), OfferDirection.SELL))), DisplayUtils.formatAccountAge(accountAge)); return limitationsText; } diff --git a/desktop/src/main/java/bisq/desktop/components/paymentmethods/SwiftForm.java b/desktop/src/main/java/bisq/desktop/components/paymentmethods/SwiftForm.java index 769cf10dd1..13c6bcd264 100644 --- a/desktop/src/main/java/bisq/desktop/components/paymentmethods/SwiftForm.java +++ b/desktop/src/main/java/bisq/desktop/components/paymentmethods/SwiftForm.java @@ -19,7 +19,6 @@ package bisq.desktop.components.paymentmethods; import bisq.desktop.components.AutoTooltipButton; import bisq.desktop.components.AutoTooltipCheckBox; import bisq.desktop.components.InputTextField; -import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.main.overlays.windows.SwiftPaymentDetails; import bisq.desktop.util.GUIUtil; import bisq.desktop.util.Layout; @@ -33,7 +32,7 @@ import bisq.core.payment.PaymentAccount; import bisq.core.payment.SwiftAccount; import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.payment.payload.SwiftAccountPayload; -import bisq.core.trade.Trade; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.util.coin.CoinFormatter; import bisq.core.util.validation.InputValidator; diff --git a/desktop/src/main/java/bisq/desktop/main/MainView.java b/desktop/src/main/java/bisq/desktop/main/MainView.java index 9b3f23ab90..259f1439a7 100644 --- a/desktop/src/main/java/bisq/desktop/main/MainView.java +++ b/desktop/src/main/java/bisq/desktop/main/MainView.java @@ -190,6 +190,7 @@ public class MainView extends InitializableView JFXBadge portfolioButtonWithBadge = new JFXBadge(portfolioButton); JFXBadge supportButtonWithBadge = new JFXBadge(supportButton); JFXBadge settingsButtonWithBadge = new JFXBadge(settingsButton); + JFXBadge daoButtonWithBadge = new JFXBadge(daoButton); Locale locale = GlobalSettings.getLocale(); DecimalFormat currencyFormat = (DecimalFormat) NumberFormat.getNumberInstance(locale); @@ -321,7 +322,7 @@ public class MainView extends InitializableView HBox.setHgrow(primaryNav, Priority.SOMETIMES); HBox secondaryNav = new HBox(supportButtonWithBadge, getNavigationSpacer(), settingsButtonWithBadge, - getNavigationSpacer(), accountButton, getNavigationSpacer(), daoButton); + getNavigationSpacer(), accountButton, getNavigationSpacer(), daoButtonWithBadge); secondaryNav.getStyleClass().add("nav-secondary"); HBox.setHgrow(secondaryNav, Priority.SOMETIMES); @@ -364,7 +365,12 @@ public class MainView extends InitializableView setupBadge(portfolioButtonWithBadge, model.getNumPendingTrades(), model.getShowPendingTradesNotification()); setupBadge(supportButtonWithBadge, model.getNumOpenSupportTickets(), model.getShowOpenSupportTicketsNotification()); + setupBadge(settingsButtonWithBadge, new SimpleStringProperty(Res.get("shared.new")), model.getShowSettingsUpdatesNotification()); + settingsButtonWithBadge.getStyleClass().add("new"); + + setupBadge(daoButtonWithBadge, new SimpleStringProperty(Res.get("shared.new")), model.getShowDaoUpdatesNotification()); + daoButtonWithBadge.getStyleClass().add("new"); navigation.addListener((viewPath, data) -> { if (viewPath.size() != 2 || viewPath.indexOf(MainView.class) != 0) diff --git a/desktop/src/main/java/bisq/desktop/main/MainViewModel.java b/desktop/src/main/java/bisq/desktop/main/MainViewModel.java index 7f29647f86..17256fb2aa 100644 --- a/desktop/src/main/java/bisq/desktop/main/MainViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/MainViewModel.java @@ -25,6 +25,7 @@ import bisq.desktop.components.TxIdTextField; import bisq.desktop.main.account.AccountView; import bisq.desktop.main.account.content.backup.BackupView; import bisq.desktop.main.overlays.Overlay; +import bisq.desktop.main.overlays.notifications.Notification; import bisq.desktop.main.overlays.notifications.NotificationCenter; import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.main.overlays.windows.DisplayAlertMessageWindow; @@ -34,6 +35,9 @@ import bisq.desktop.main.overlays.windows.UpdateAmazonGiftCardAccountWindow; import bisq.desktop.main.overlays.windows.UpdateRevolutAccountWindow; import bisq.desktop.main.overlays.windows.WalletPasswordWindow; import bisq.desktop.main.overlays.windows.downloadupdate.DisplayUpdateDownloadWindow; +import bisq.desktop.main.portfolio.PortfolioView; +import bisq.desktop.main.portfolio.bsqswaps.UnconfirmedBsqSwapsView; +import bisq.desktop.main.portfolio.closedtrades.ClosedTradesView; import bisq.desktop.main.presentation.AccountPresentation; import bisq.desktop.main.presentation.DaoPresentation; import bisq.desktop.main.presentation.MarketPricePresentation; @@ -48,6 +52,7 @@ import bisq.core.alert.PrivateNotificationManager; import bisq.core.app.BisqSetup; import bisq.core.btc.nodes.LocalBitcoinNode; import bisq.core.btc.setup.WalletsSetup; +import bisq.core.btc.wallet.BsqWalletService; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.locale.CryptoCurrency; import bisq.core.locale.CurrencyUtil; @@ -64,6 +69,7 @@ import bisq.core.presentation.TradePresentation; import bisq.core.provider.fee.FeeService; import bisq.core.provider.price.PriceFeedService; import bisq.core.trade.TradeManager; +import bisq.core.trade.bsq_swap.BsqSwapTradeManager; import bisq.core.user.DontShowAgainLookup; import bisq.core.user.Preferences; import bisq.core.user.User; @@ -82,6 +88,8 @@ import bisq.common.file.CorruptedStorageFileHandler; import bisq.common.util.Hex; import bisq.common.util.Tuple2; +import org.bitcoinj.core.TransactionConfidence; + import com.google.inject.Inject; import com.google.common.base.Charsets; @@ -119,6 +127,7 @@ import lombok.extern.slf4j.Slf4j; public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener { private final BisqSetup bisqSetup; private final WalletsSetup walletsSetup; + private final BsqWalletService bsqWalletService; private final User user; private final BalancePresentation balancePresentation; private final TradePresentation tradePresentation; @@ -129,6 +138,7 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener { private final SettingsPresentation settingsPresentation; private final P2PService p2PService; private final TradeManager tradeManager; + private final BsqSwapTradeManager bsqSwapTradeManager; private final OpenOfferManager openOfferManager; @Getter private final Preferences preferences; @@ -167,6 +177,7 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener { public MainViewModel(BisqSetup bisqSetup, WalletsSetup walletsSetup, BtcWalletService btcWalletService, + BsqWalletService bsqWalletService, User user, BalancePresentation balancePresentation, TradePresentation tradePresentation, @@ -177,6 +188,7 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener { SettingsPresentation settingsPresentation, P2PService p2PService, TradeManager tradeManager, + BsqSwapTradeManager bsqSwapTradeManager, OpenOfferManager openOfferManager, Preferences preferences, PrivateNotificationManager privateNotificationManager, @@ -194,6 +206,7 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener { Navigation navigation) { this.bisqSetup = bisqSetup; this.walletsSetup = walletsSetup; + this.bsqWalletService = bsqWalletService; this.user = user; this.balancePresentation = balancePresentation; this.tradePresentation = tradePresentation; @@ -204,6 +217,7 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener { this.settingsPresentation = settingsPresentation; this.p2PService = p2PService; this.tradeManager = tradeManager; + this.bsqSwapTradeManager = bsqSwapTradeManager; this.openOfferManager = openOfferManager; this.preferences = preferences; this.privateNotificationManager = privateNotificationManager; @@ -245,34 +259,53 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener { if (newValue) { tradeManager.applyTradePeriodState(); - tradeManager.getObservableList().forEach(trade -> { - Date maxTradePeriodDate = trade.getMaxTradePeriodDate(); - String key; - switch (trade.getTradePeriodState()) { - case FIRST_HALF: - break; - case SECOND_HALF: - key = "displayHalfTradePeriodOver" + trade.getId(); - if (DontShowAgainLookup.showAgain(key)) { - DontShowAgainLookup.dontShowAgain(key, true); - new Popup().warning(Res.get("popup.warning.tradePeriod.halfReached", - trade.getShortId(), - DisplayUtils.formatDateTime(maxTradePeriodDate))) - .show(); + tradeManager.getObservableList() + .forEach(trade -> { + Date maxTradePeriodDate = trade.getMaxTradePeriodDate(); + String key; + switch (trade.getTradePeriodState()) { + case FIRST_HALF: + break; + case SECOND_HALF: + key = "displayHalfTradePeriodOver" + trade.getId(); + if (DontShowAgainLookup.showAgain(key)) { + DontShowAgainLookup.dontShowAgain(key, true); + new Popup().warning(Res.get("popup.warning.tradePeriod.halfReached", + trade.getShortId(), + DisplayUtils.formatDateTime(maxTradePeriodDate))) + .show(); + } + break; + case TRADE_PERIOD_OVER: + key = "displayTradePeriodOver" + trade.getId(); + if (DontShowAgainLookup.showAgain(key)) { + DontShowAgainLookup.dontShowAgain(key, true); + new Popup().warning(Res.get("popup.warning.tradePeriod.ended", + trade.getShortId(), + DisplayUtils.formatDateTime(maxTradePeriodDate))) + .show(); + } + break; } - break; - case TRADE_PERIOD_OVER: - key = "displayTradePeriodOver" + trade.getId(); - if (DontShowAgainLookup.showAgain(key)) { - DontShowAgainLookup.dontShowAgain(key, true); - new Popup().warning(Res.get("popup.warning.tradePeriod.ended", - trade.getShortId(), - DisplayUtils.formatDateTime(maxTradePeriodDate))) - .show(); - } - break; + }); + + bsqSwapTradeManager.getCompletedBsqSwapTrade().addListener((observable1, oldValue1, bsqSwapTrade) -> { + if (bsqSwapTrade == null) { + return; + } + if (bsqSwapTrade.getOffer().isMyOffer(tradeManager.getKeyRing())) { + new Notification() + .headLine(Res.get("notification.bsqSwap.maker.headline")) + .notification(Res.get("notification.bsqSwap.maker.tradeCompleted", bsqSwapTrade.getShortId())) + .actionButtonTextWithGoTo("navigation.portfolio.bsqSwapTrades.short") + .onAction(() -> navigation.navigateTo(MainView.class, PortfolioView.class, UnconfirmedBsqSwapsView.class)) + .show(); + bsqSwapTradeManager.resetCompletedBsqSwapTrade(); } }); + + maybeNotifyBsqSwapTxConfirmed(); + bsqWalletService.getWallet().addTransactionConfidenceEventListener((wallet, tx) -> maybeNotifyBsqSwapTxConfirmed()); } }); @@ -293,6 +326,25 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener { getShowAppScreen().set(true); } + private void maybeNotifyBsqSwapTxConfirmed() { + bsqSwapTradeManager.getObservableList().stream() + .filter(bsqSwapTrade -> bsqSwapTrade.getTxId() != null) + .filter(bsqSwapTrade -> DontShowAgainLookup.showAgain(bsqSwapTrade.getTxId())) + .filter(bsqSwapTrade -> { + TransactionConfidence confidenceForTxId = bsqWalletService.getConfidenceForTxId(bsqSwapTrade.getTxId()); + return confidenceForTxId != null && confidenceForTxId.getDepthInBlocks() > 0; + }) + .forEach(bsqSwapTrade -> { + DontShowAgainLookup.dontShowAgain(bsqSwapTrade.getTxId(), true); + new Notification() + .headLine(Res.get("notification.bsqSwap.confirmed.headline")) + .notification(Res.get("notification.bsqSwap.confirmed.text", bsqSwapTrade.getShortId())) + .actionButtonTextWithGoTo("navigation.portfolio.closedTrades") + .onAction(() -> navigation.navigateTo(MainView.class, PortfolioView.class, ClosedTradesView.class)) + .show(); + }); + } + /////////////////////////////////////////////////////////////////////////////////////////// // UI handlers @@ -813,9 +865,6 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener { return marketPricePresentation.getPriceFeedComboBoxItems(); } - // We keep daoPresentation and accountPresentation support even it is not used atm. But if we add a new feature and - // add a badge again it will be needed. - @SuppressWarnings({"unused"}) public BooleanProperty getShowDaoUpdatesNotification() { return daoPresentation.getShowDaoUpdatesNotification(); } diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/altcoinaccounts/AltCoinAccountsDataModel.java b/desktop/src/main/java/bisq/desktop/main/account/content/altcoinaccounts/AltCoinAccountsDataModel.java index 04883b53ac..da91488357 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/altcoinaccounts/AltCoinAccountsDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/altcoinaccounts/AltCoinAccountsDataModel.java @@ -87,7 +87,7 @@ class AltCoinAccountsDataModel extends ActivatableDataModel { private void fillAndSortPaymentAccounts() { if (user.getPaymentAccounts() != null) { paymentAccounts.setAll(user.getPaymentAccounts().stream() - .filter(paymentAccount -> paymentAccount.getPaymentMethod().isAsset()) + .filter(paymentAccount -> paymentAccount.getPaymentMethod().isBlockchain()) .collect(Collectors.toList())); paymentAccounts.sort(Comparator.comparing(PaymentAccount::getAccountName)); } @@ -127,17 +127,17 @@ class AltCoinAccountsDataModel extends ActivatableDataModel { } public boolean onDeleteAccount(PaymentAccount paymentAccount) { - boolean isPaymentAccountUsed = openOfferManager.getObservableList().stream() - .filter(o -> o.getOffer().getMakerPaymentAccountId().equals(paymentAccount.getId())) - .findAny() - .isPresent(); - isPaymentAccountUsed = isPaymentAccountUsed || tradeManager.getObservableList().stream() - .filter(t -> t.getOffer().getMakerPaymentAccountId().equals(paymentAccount.getId()) || - paymentAccount.getId().equals(t.getTakerPaymentAccountId())) - .findAny() - .isPresent(); - if (!isPaymentAccountUsed) + boolean usedInOpenOffers = openOfferManager.getObservableList().stream() + .anyMatch(openOffer -> openOffer.getOffer().getMakerPaymentAccountId().equals(paymentAccount.getId())); + + boolean usedInTrades = tradeManager.getObservableList().stream() + .anyMatch(trade -> trade.getOffer().getMakerPaymentAccountId().equals(paymentAccount.getId()) || + paymentAccount.getId().equals(trade.getTakerPaymentAccountId())); + boolean isPaymentAccountUsed = usedInOpenOffers || usedInTrades; + + if (!isPaymentAccountUsed) { user.removePaymentAccount(paymentAccount); + } return isPaymentAccountUsed; } diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/fiataccounts/FiatAccountsDataModel.java b/desktop/src/main/java/bisq/desktop/main/account/content/fiataccounts/FiatAccountsDataModel.java index 0a45a88384..f3c30abc36 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/fiataccounts/FiatAccountsDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/fiataccounts/FiatAccountsDataModel.java @@ -88,7 +88,7 @@ class FiatAccountsDataModel extends ActivatableDataModel { private void fillAndSortPaymentAccounts() { if (user.getPaymentAccounts() != null) { List list = user.getPaymentAccounts().stream() - .filter(paymentAccount -> !paymentAccount.getPaymentMethod().isAsset()) + .filter(paymentAccount -> paymentAccount.getPaymentMethod().isFiat()) .collect(Collectors.toList()); paymentAccounts.setAll(list); paymentAccounts.sort(Comparator.comparing(PaymentAccount::getAccountName)); @@ -134,13 +134,17 @@ class FiatAccountsDataModel extends ActivatableDataModel { } public boolean onDeleteAccount(PaymentAccount paymentAccount) { - boolean isPaymentAccountUsed = openOfferManager.getObservableList().stream() - .anyMatch(o -> o.getOffer().getMakerPaymentAccountId().equals(paymentAccount.getId())); - isPaymentAccountUsed = isPaymentAccountUsed || tradeManager.getObservableList().stream() - .anyMatch(t -> t.getOffer().getMakerPaymentAccountId().equals(paymentAccount.getId()) || - paymentAccount.getId().equals(t.getTakerPaymentAccountId())); - if (!isPaymentAccountUsed) + boolean usedInOpenOffers = openOfferManager.getObservableList().stream() + .anyMatch(openOffer -> openOffer.getOffer().getMakerPaymentAccountId().equals(paymentAccount.getId())); + + boolean usedInTrades = tradeManager.getObservableList().stream() + .anyMatch(trade -> trade.getOffer().getMakerPaymentAccountId().equals(paymentAccount.getId()) || + paymentAccount.getId().equals(trade.getTakerPaymentAccountId())); + boolean isPaymentAccountUsed = usedInOpenOffers || usedInTrades; + + if (!isPaymentAccountUsed) { user.removePaymentAccount(paymentAccount); + } return isPaymentAccountUsed; } diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/fiataccounts/FiatAccountsView.java b/desktop/src/main/java/bisq/desktop/main/account/content/fiataccounts/FiatAccountsView.java index 3e6d3a6180..baf3dd625f 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/fiataccounts/FiatAccountsView.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/fiataccounts/FiatAccountsView.java @@ -42,7 +42,9 @@ import bisq.desktop.components.paymentmethods.MoneyGramForm; import bisq.desktop.components.paymentmethods.NationalBankForm; import bisq.desktop.components.paymentmethods.NeftForm; import bisq.desktop.components.paymentmethods.NequiForm; +import bisq.desktop.components.paymentmethods.PaxumForm; import bisq.desktop.components.paymentmethods.PaymentMethodForm; +import bisq.desktop.components.paymentmethods.PayseraForm; import bisq.desktop.components.paymentmethods.PaytmForm; import bisq.desktop.components.paymentmethods.PerfectMoneyForm; import bisq.desktop.components.paymentmethods.PixForm; @@ -59,8 +61,6 @@ import bisq.desktop.components.paymentmethods.StrikeForm; import bisq.desktop.components.paymentmethods.SwiftForm; import bisq.desktop.components.paymentmethods.SwishForm; import bisq.desktop.components.paymentmethods.TransferwiseForm; -import bisq.desktop.components.paymentmethods.PayseraForm; -import bisq.desktop.components.paymentmethods.PaxumForm; import bisq.desktop.components.paymentmethods.USPostalMoneyOrderForm; import bisq.desktop.components.paymentmethods.UpholdForm; import bisq.desktop.components.paymentmethods.UpiForm; @@ -84,6 +84,7 @@ import bisq.desktop.util.validation.HalCashValidator; import bisq.desktop.util.validation.IBANValidator; import bisq.desktop.util.validation.InteracETransferValidator; import bisq.desktop.util.validation.JapanBankTransferValidator; +import bisq.desktop.util.validation.LengthValidator; import bisq.desktop.util.validation.MoneyBeamValidator; import bisq.desktop.util.validation.PerfectMoneyValidator; import bisq.desktop.util.validation.PopmoneyValidator; @@ -94,7 +95,6 @@ import bisq.desktop.util.validation.TransferwiseValidator; import bisq.desktop.util.validation.USPostalMoneyOrderValidator; import bisq.desktop.util.validation.UpholdValidator; import bisq.desktop.util.validation.WeChatPayValidator; -import bisq.desktop.util.validation.LengthValidator; import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.locale.Res; @@ -418,7 +418,7 @@ public class FiatAccountsView extends PaymentAccountsView list = PaymentMethod.getPaymentMethods().stream() - .filter(paymentMethod -> !paymentMethod.isAsset()) + .filter(PaymentMethod::isFiat) .sorted() .collect(Collectors.toList()); paymentMethodComboBox.setItems(FXCollections.observableArrayList(list)); diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/notifications/MobileNotificationsView.java b/desktop/src/main/java/bisq/desktop/main/account/content/notifications/MobileNotificationsView.java index ca2bdec89d..da4190307a 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/notifications/MobileNotificationsView.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/notifications/MobileNotificationsView.java @@ -21,13 +21,10 @@ import bisq.desktop.common.view.ActivatableView; import bisq.desktop.common.view.FxmlView; import bisq.desktop.components.InfoInputTextField; import bisq.desktop.components.InputTextField; -import bisq.desktop.main.PriceUtil; import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.util.FormBuilder; import bisq.desktop.util.GUIUtil; import bisq.desktop.util.Layout; -import bisq.desktop.util.validation.AltcoinValidator; -import bisq.desktop.util.validation.FiatPriceValidator; import bisq.desktop.util.validation.PercentageNumberValidator; import bisq.core.locale.CurrencyUtil; @@ -48,6 +45,9 @@ import bisq.core.user.Preferences; import bisq.core.user.User; import bisq.core.util.FormattingUtils; import bisq.core.util.ParsingUtils; +import bisq.core.util.PriceUtil; +import bisq.core.util.validation.AltcoinValidator; +import bisq.core.util.validation.FiatPriceValidator; import bisq.core.util.validation.InputValidator; import bisq.common.UserThread; diff --git a/desktop/src/main/java/bisq/desktop/main/dao/DaoView.java b/desktop/src/main/java/bisq/desktop/main/dao/DaoView.java index 40b5f8082a..b9f33a5e38 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/DaoView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/DaoView.java @@ -145,8 +145,12 @@ public class DaoView extends ActivatableView { protected void activate() { if (DevEnv.isDaoActivated()) { - // Hide dao new badge if user saw this section - preferences.dontShowAgain(DaoPresentation.DAO_NEWS, true); + if (preferences.showAgain(DaoPresentation.DAO_NEWS)) { + preferences.dontShowAgain(DaoPresentation.DAO_NEWS, true); + new Popup().headLine(Res.get("news.bsqSwap.title")) + .information(Res.get("news.bsqSwap.description")) + .show(); + } navigation.addListener(navigationListener); root.getSelectionModel().selectedItemProperty().addListener(tabChangeListener); diff --git a/desktop/src/main/java/bisq/desktop/main/dao/bonding/reputation/MyReputationView.java b/desktop/src/main/java/bisq/desktop/main/dao/bonding/reputation/MyReputationView.java index 764d073abb..787a31cf0e 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/bonding/reputation/MyReputationView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/bonding/reputation/MyReputationView.java @@ -38,8 +38,8 @@ import bisq.core.dao.governance.bond.BondState; import bisq.core.dao.governance.bond.reputation.MyBondedReputation; import bisq.core.locale.Res; import bisq.core.user.Preferences; -import bisq.core.util.coin.BsqFormatter; import bisq.core.util.ParsingUtils; +import bisq.core.util.coin.BsqFormatter; import bisq.core.util.validation.HexStringValidator; import bisq.core.util.validation.IntegerValidator; @@ -217,14 +217,14 @@ public class MyReputationView extends ActivatableView implements /////////////////////////////////////////////////////////////////////////////////////////// @Override - public void onUpdateBalances(Coin availableConfirmedBalance, + public void onUpdateBalances(Coin availableBalance, Coin availableNonBsqBalance, Coin unverifiedBalance, Coin unconfirmedChangeBalance, Coin lockedForVotingBalance, Coin lockupBondsBalance, Coin unlockingBondsBalance) { - bsqValidator.setAvailableBalance(availableConfirmedBalance); + bsqValidator.setAvailableBalance(availableBalance); } diff --git a/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/assetfee/AssetFeeView.java b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/assetfee/AssetFeeView.java index 0d95572770..e0c4ec30be 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/assetfee/AssetFeeView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/assetfee/AssetFeeView.java @@ -176,7 +176,7 @@ public class AssetFeeView extends ActivatableView implements Bsq assetService.updateAssetStates(); updateList(); - onUpdateAvailableConfirmedBalance(bsqWalletService.getAvailableConfirmedBalance()); + onUpdateAvailableBalance(bsqWalletService.getAvailableBalance()); payFeeButton.setOnAction((event) -> { Coin listingFee = getListingFee(); @@ -235,7 +235,7 @@ public class AssetFeeView extends ActivatableView implements Bsq /////////////////////////////////////////////////////////////////////////////////////////// @Override - public void onUpdateBalances(Coin availableConfirmedBalance, + public void onUpdateBalances(Coin availableBalance, Coin availableNonBsqBalance, Coin unverifiedBalance, Coin unconfirmedChangeBalance, @@ -243,7 +243,7 @@ public class AssetFeeView extends ActivatableView implements Bsq Coin lockupBondsBalance, Coin unlockingBondsBalance) { - onUpdateAvailableConfirmedBalance(availableConfirmedBalance); + onUpdateAvailableBalance(availableBalance); } @@ -278,8 +278,8 @@ public class AssetFeeView extends ActivatableView implements Bsq }; } - private void onUpdateAvailableConfirmedBalance(Coin availableConfirmedBalance) { - bsqValidator.setAvailableBalance(availableConfirmedBalance); + private void onUpdateAvailableBalance(Coin availableBalance) { + bsqValidator.setAvailableBalance(availableBalance); updateButtonState(); } diff --git a/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/proofofburn/ProofOfBurnView.java b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/proofofburn/ProofOfBurnView.java index d1dbc15d75..3a0de6d79b 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/proofofburn/ProofOfBurnView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/proofofburn/ProofOfBurnView.java @@ -164,7 +164,7 @@ public class ProofOfBurnView extends ActivatableView implements proofOfBurnService.getUpdateFlag().addListener(updateListener); bsqWalletService.addBsqBalanceListener(this); - onUpdateAvailableConfirmedBalance(bsqWalletService.getAvailableConfirmedBalance()); + onUpdateAvailableBalance(bsqWalletService.getAvailableBalance()); burnButton.setOnAction((event) -> { Coin amount = getAmountFee(); @@ -216,14 +216,14 @@ public class ProofOfBurnView extends ActivatableView implements /////////////////////////////////////////////////////////////////////////////////////////// @Override - public void onUpdateBalances(Coin availableConfirmedBalance, + public void onUpdateBalances(Coin availableBalance, Coin availableNonBsqBalance, Coin unverifiedBalance, Coin unconfirmedChangeBalance, Coin lockedForVotingBalance, Coin lockupBondsBalance, Coin unlockingBondsBalance) { - onUpdateAvailableConfirmedBalance(availableConfirmedBalance); + onUpdateAvailableBalance(availableBalance); } @@ -255,8 +255,8 @@ public class ProofOfBurnView extends ActivatableView implements updateListener = observable -> updateList(); } - private void onUpdateAvailableConfirmedBalance(Coin availableConfirmedBalance) { - bsqValidator.setAvailableBalance(availableConfirmedBalance); + private void onUpdateAvailableBalance(Coin availableBalance) { + bsqValidator.setAvailableBalance(availableBalance); updateButtonState(); } diff --git a/desktop/src/main/java/bisq/desktop/main/dao/governance/make/MakeProposalView.java b/desktop/src/main/java/bisq/desktop/main/dao/governance/make/MakeProposalView.java index 5b1a467968..e82b6c22a5 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/governance/make/MakeProposalView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/governance/make/MakeProposalView.java @@ -51,8 +51,8 @@ import bisq.core.dao.state.model.governance.Proposal; import bisq.core.dao.state.model.governance.Role; import bisq.core.locale.Res; import bisq.core.util.FormattingUtils; -import bisq.core.util.coin.BsqFormatter; import bisq.core.util.ParsingUtils; +import bisq.core.util.coin.BsqFormatter; import bisq.core.util.coin.CoinFormatter; import bisq.asset.Asset; @@ -291,7 +291,7 @@ public class MakeProposalView extends ActivatableView implements checkNotNull(proposalDisplay.bondedRoleTypeComboBox, "proposalDisplay.bondedRoleTypeComboBox must not be null"); BondedRoleType bondedRoleType = proposalDisplay.bondedRoleTypeComboBox.getSelectionModel().getSelectedItem(); long requiredBond = daoFacade.getRequiredBond(bondedRoleType); - long availableBalance = bsqWalletService.getAvailableConfirmedBalance().value; + long availableBalance = bsqWalletService.getAvailableBalance().value; if (requiredBond > availableBalance) { long missing = requiredBond - availableBalance; diff --git a/desktop/src/main/java/bisq/desktop/main/dao/governance/proposals/ProposalsView.java b/desktop/src/main/java/bisq/desktop/main/dao/governance/proposals/ProposalsView.java index d8763a94c6..1a53aa4090 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/governance/proposals/ProposalsView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/governance/proposals/ProposalsView.java @@ -222,7 +222,7 @@ public class ProposalsView extends ActivatableView implements Bs stakeInputTextField.textProperty().addListener(stakeListener); voteButton.setOnAction(e -> onVote()); - onUpdateBalances(bsqWalletService.getAvailableConfirmedBalance(), + onUpdateBalances(bsqWalletService.getAvailableBalance(), bsqWalletService.getAvailableNonBsqBalance(), bsqWalletService.getUnverifiedBalance(), bsqWalletService.getUnconfirmedChangeBalance(), @@ -272,7 +272,7 @@ public class ProposalsView extends ActivatableView implements Bs /////////////////////////////////////////////////////////////////////////////////////////// @Override - public void onUpdateBalances(Coin availableConfirmedBalance, + public void onUpdateBalances(Coin availableBalance, Coin availableNonBsqBalance, Coin unverifiedBalance, Coin unconfirmedChangeBalance, @@ -281,7 +281,7 @@ public class ProposalsView extends ActivatableView implements Bs Coin unlockingBondsBalance) { Coin blindVoteFee = BlindVoteConsensus.getFee(daoStateService, daoStateService.getChainHeight()); if (isBlindVotePhaseButNotLastBlock()) { - Coin availableForVoting = availableConfirmedBalance.subtract(blindVoteFee); + Coin availableForVoting = availableBalance.subtract(blindVoteFee); if (availableForVoting.isNegative()) availableForVoting = Coin.valueOf(0); stakeInputTextField.setPromptText(Res.get("dao.proposal.myVote.stake.prompt", diff --git a/desktop/src/main/java/bisq/desktop/main/dao/wallet/BsqBalanceUtil.java b/desktop/src/main/java/bisq/desktop/main/dao/wallet/BsqBalanceUtil.java index 5c0ce109ae..db95d762e2 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/wallet/BsqBalanceUtil.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/wallet/BsqBalanceUtil.java @@ -135,7 +135,7 @@ public class BsqBalanceUtil implements BsqBalanceListener, DaoStateListener { private void triggerUpdate() { - onUpdateBalances(bsqWalletService.getAvailableConfirmedBalance(), + onUpdateBalances(bsqWalletService.getAvailableBalance(), bsqWalletService.getAvailableNonBsqBalance(), bsqWalletService.getUnverifiedBalance(), bsqWalletService.getUnconfirmedChangeBalance(), diff --git a/desktop/src/main/java/bisq/desktop/main/dao/wallet/send/BsqSendView.java b/desktop/src/main/java/bisq/desktop/main/dao/wallet/send/BsqSendView.java index 15c52cde89..1f4f51eb33 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/wallet/send/BsqSendView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/wallet/send/BsqSendView.java @@ -242,14 +242,14 @@ public class BsqSendView extends ActivatableView implements BsqB } @Override - public void onUpdateBalances(Coin availableConfirmedBalance, + public void onUpdateBalances(Coin availableBalance, Coin availableNonBsqBalance, Coin unverifiedBalance, Coin unconfirmedChangeBalance, Coin lockedForVotingBalance, Coin lockupBondsBalance, Coin unlockingBondsBalance) { - updateBsqValidator(availableConfirmedBalance); + updateBsqValidator(availableBalance); updateBtcValidator(availableNonBsqBalance); setSendBtcGroupVisibleState(availableNonBsqBalance.isPositive()); @@ -260,15 +260,15 @@ public class BsqSendView extends ActivatableView implements BsqB receiversAddressInputTextField.setText(tuple.second); } - private void updateBsqValidator(Coin availableConfirmedBalance) { - bsqValidator.setAvailableBalance(availableConfirmedBalance); + private void updateBsqValidator(Coin availableBalance) { + bsqValidator.setAvailableBalance(availableBalance); boolean isValid = bsqAddressValidator.validate(receiversAddressInputTextField.getText()).isValid && bsqValidator.validate(amountInputTextField.getText()).isValid; sendBsqButton.setDisable(!isValid); } - private void updateBtcValidator(Coin availableConfirmedBalance) { - btcValidator.setMaxValue(availableConfirmedBalance); + private void updateBtcValidator(Coin availableBalance) { + btcValidator.setMaxValue(availableBalance); boolean isValid = btcAddressValidator.validate(receiversBtcAddressInputTextField.getText()).isValid && btcValidator.validate(btcAmountInputTextField.getText()).isValid; sendBtcButton.setDisable(!isValid); @@ -309,7 +309,7 @@ public class BsqSendView extends ActivatableView implements BsqB Transaction preparedSendTx = bsqWalletService.getPreparedSendBsqTx(receiversAddressString, receiverAmount, bsqUtxoCandidates); Transaction txWithBtcFee = btcWalletService.completePreparedSendBsqTx(preparedSendTx); - Transaction signedTx = bsqWalletService.signTx(txWithBtcFee); + Transaction signedTx = bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee); Coin miningFee = signedTx.getFee(); int txVsize = signedTx.getVsize(); showPublishTxPopup(receiverAmount, @@ -359,11 +359,11 @@ public class BsqSendView extends ActivatableView implements BsqB amountInputTextField.refreshValidation(); } - // We have used input selection it is the sum of our selected inputs, otherwise the availableConfirmedBalance + // We have used input selection it is the sum of our selected inputs, otherwise the availableBalance private Coin getSpendableBsqBalance() { return bsqUtxoCandidates != null ? Coin.valueOf(bsqUtxoCandidates.stream().mapToLong(e -> e.getValue().value).sum()) : - bsqWalletService.getAvailableConfirmedBalance(); + bsqWalletService.getAvailableBalance(); } private void setSendBtcGroupVisibleState(boolean visible) { @@ -438,7 +438,7 @@ public class BsqSendView extends ActivatableView implements BsqB try { Transaction preparedSendTx = bsqWalletService.getPreparedSendBtcTx(receiversAddressString, receiverAmount, btcUtxoCandidates); Transaction txWithBtcFee = btcWalletService.completePreparedSendBsqTx(preparedSendTx); - Transaction signedTx = bsqWalletService.signTx(txWithBtcFee); + Transaction signedTx = bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee); Coin miningFee = signedTx.getFee(); if (miningFee.getValue() >= receiverAmount.getValue()) diff --git a/desktop/src/main/java/bisq/desktop/main/dao/wallet/tx/BsqTxListItem.java b/desktop/src/main/java/bisq/desktop/main/dao/wallet/tx/BsqTxListItem.java index a3d2bdd783..7f52b5b829 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/wallet/tx/BsqTxListItem.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/wallet/tx/BsqTxListItem.java @@ -18,6 +18,7 @@ package bisq.desktop.main.dao.wallet.tx; import bisq.desktop.components.TxConfidenceListItem; +import bisq.desktop.main.funds.transactions.TradableRepository; import bisq.desktop.util.DisplayUtils; import bisq.core.btc.wallet.BsqWalletService; @@ -26,6 +27,7 @@ import bisq.core.btc.wallet.WalletService; import bisq.core.dao.DaoFacade; import bisq.core.dao.state.model.blockchain.TxType; import bisq.core.locale.Res; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; import bisq.core.util.coin.BsqFormatter; import org.bitcoinj.core.Address; @@ -34,6 +36,7 @@ import org.bitcoinj.core.Transaction; import org.bitcoinj.core.TransactionOutput; import java.util.Date; +import java.util.Optional; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -54,6 +57,7 @@ class BsqTxListItem extends TxConfidenceListItem { private final String address; private final String direction; private final Coin amount; + private final Optional optionalBsqTrade; private boolean received; BsqTxListItem(Transaction transaction, @@ -61,7 +65,8 @@ class BsqTxListItem extends TxConfidenceListItem { BtcWalletService btcWalletService, DaoFacade daoFacade, Date date, - BsqFormatter bsqFormatter) { + BsqFormatter bsqFormatter, + TradableRepository tradableRepository) { super(transaction, bsqWalletService); this.daoFacade = daoFacade; @@ -127,6 +132,13 @@ class BsqTxListItem extends TxConfidenceListItem { address = received ? receivedWithAddress : sendToAddress; else address = ""; + + + optionalBsqTrade = tradableRepository.getAll().stream() + .filter(tradable -> tradable instanceof BsqSwapTrade) + .map(tradable -> (BsqSwapTrade) tradable) + .filter(tradable -> txId.equals(tradable.getTxId())) + .findFirst(); } BsqTxListItem() { @@ -138,24 +150,29 @@ class BsqTxListItem extends TxConfidenceListItem { this.direction = null; this.amount = null; this.bsqFormatter = null; + optionalBsqTrade = Optional.empty(); } - public TxType getTxType() { + TxType getTxType() { return daoFacade.getTx(txId) .flatMap(tx -> daoFacade.getOptionalTxType(tx.getId())) .orElse(confirmations == 0 ? TxType.UNVERIFIED : TxType.UNDEFINED_TX_TYPE); } - public boolean isWithdrawalToBTCWallet() { + boolean isWithdrawalToBTCWallet() { return withdrawalToBTCWallet; } - public String getDateAsString() { + String getDateAsString() { return DisplayUtils.formatDateTime(date); } - public String getAmountAsString() { + String getAmountAsString() { return bsqFormatter.formatCoin(amount); } + + boolean isBsqSwapTx() { + return getOptionalBsqTrade().isPresent(); + } } diff --git a/desktop/src/main/java/bisq/desktop/main/dao/wallet/tx/BsqTxView.java b/desktop/src/main/java/bisq/desktop/main/dao/wallet/tx/BsqTxView.java index 22c6f5ef92..60b7462378 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/wallet/tx/BsqTxView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/wallet/tx/BsqTxView.java @@ -26,6 +26,8 @@ import bisq.desktop.components.AutoTooltipTableColumn; import bisq.desktop.components.ExternalHyperlink; import bisq.desktop.components.HyperlinkWithIcon; import bisq.desktop.main.dao.wallet.BsqBalanceUtil; +import bisq.desktop.main.funds.transactions.TradableRepository; +import bisq.desktop.main.overlays.windows.BsqTradeDetailsWindow; import bisq.desktop.util.DisplayUtils; import bisq.desktop.util.FormBuilder; import bisq.desktop.util.GUIUtil; @@ -40,6 +42,7 @@ import bisq.core.dao.state.model.blockchain.Block; import bisq.core.dao.state.model.blockchain.TxType; import bisq.core.dao.state.model.governance.IssuanceType; import bisq.core.locale.Res; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; import bisq.core.user.Preferences; import bisq.core.util.coin.BsqFormatter; @@ -103,6 +106,8 @@ public class BsqTxView extends ActivatableView implements BsqBal private final BtcWalletService btcWalletService; private final BsqBalanceUtil bsqBalanceUtil; private final Preferences preferences; + private final TradableRepository tradableRepository; + private final BsqTradeDetailsWindow bsqTradeDetailsWindow; private final ObservableList observableList = FXCollections.observableArrayList(); // Need to be DoubleProperty as we pass it as reference @@ -128,7 +133,9 @@ public class BsqTxView extends ActivatableView implements BsqBal Preferences preferences, BtcWalletService btcWalletService, BsqBalanceUtil bsqBalanceUtil, - BsqFormatter bsqFormatter) { + BsqFormatter bsqFormatter, + TradableRepository tradableRepository, + BsqTradeDetailsWindow bsqTradeDetailsWindow) { this.daoFacade = daoFacade; this.daoStateService = daoStateService; this.bsqFormatter = bsqFormatter; @@ -136,6 +143,8 @@ public class BsqTxView extends ActivatableView implements BsqBal this.preferences = preferences; this.btcWalletService = btcWalletService; this.bsqBalanceUtil = bsqBalanceUtil; + this.tradableRepository = tradableRepository; + this.bsqTradeDetailsWindow = bsqTradeDetailsWindow; } @Override @@ -268,7 +277,7 @@ public class BsqTxView extends ActivatableView implements BsqBal /////////////////////////////////////////////////////////////////////////////////////////// @Override - public void onUpdateBalances(Coin availableConfirmedBalance, + public void onUpdateBalances(Coin availableBalance, Coin availableNonBsqBalance, Coin unverifiedBalance, Coin unconfirmedChangeBalance, @@ -359,7 +368,8 @@ public class BsqTxView extends ActivatableView implements BsqBal daoFacade, // Use tx.getIncludedInBestChainAt() when available, otherwise use tx.getUpdateTime() transaction.getIncludedInBestChainAt() != null ? transaction.getIncludedInBestChainAt() : transaction.getUpdateTime(), - bsqFormatter); + bsqFormatter, + tradableRepository); }) .collect(Collectors.toList()); observableList.setAll(items); @@ -464,7 +474,7 @@ public class BsqTxView extends ActivatableView implements BsqBal } private void addInformationColumn() { - TableColumn column = new AutoTooltipTableColumn<>(Res.get("shared.information")); + TableColumn column = new AutoTooltipTableColumn<>(Res.get("shared.details")); column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue())); column.setMinWidth(160); column.setCellFactory( @@ -481,11 +491,21 @@ public class BsqTxView extends ActivatableView implements BsqBal public void updateItem(final BsqTxListItem item, boolean empty) { super.updateItem(item, empty); if (item != null && !empty) { - final TxType txType = item.getTxType(); + TxType txType = item.getTxType(); String labelString = Res.get("dao.tx.type.enum." + txType.name()); Label label; if (item.getConfirmations() > 0 && isValidType(txType)) { - if (txType == TxType.COMPENSATION_REQUEST && + if (item.getOptionalBsqTrade().isPresent()) { + if (field != null) + field.setOnAction(null); + + BsqSwapTrade bsqSwapTrade = item.getOptionalBsqTrade().get(); + String text = Res.get("dao.tx.bsqSwapTrade", bsqSwapTrade.getShortId()); + HyperlinkWithIcon hyperlinkWithIcon = new HyperlinkWithIcon(text, AwesomeIcon.INFO_SIGN); + hyperlinkWithIcon.setOnAction(e -> bsqTradeDetailsWindow.show(bsqSwapTrade)); + hyperlinkWithIcon.setTooltip(new Tooltip(Res.get("tooltip.openPopupForDetails"))); + setGraphic(hyperlinkWithIcon); + } else if (txType == TxType.COMPENSATION_REQUEST && daoFacade.isIssuanceTx(item.getTxId(), IssuanceType.COMPENSATION)) { if (field != null) field.setOnAction(null); @@ -523,7 +543,6 @@ public class BsqTxView extends ActivatableView implements BsqBal setGraphic(field); } } else { - if (item.isWithdrawalToBTCWallet()) labelString = Res.get("dao.tx.withdrawnFromWallet"); @@ -539,7 +558,6 @@ public class BsqTxView extends ActivatableView implements BsqBal }; } }); - tableView.getColumns().add(column); } @@ -664,8 +682,19 @@ public class BsqTxView extends ActivatableView implements BsqBal } break; case PAY_TRADE_FEE: - awesomeIcon = AwesomeIcon.LEAF; - style = "dao-tx-type-trade-fee-icon"; + // We do not detect a BSQ swap tx. It is considered a PAY_TRADE_FEE tx + // which is correct as well as it pays a trade fee. + // Locally we can derive the information to distinguish a BSQ swap tx + // by looking up our closed trades. Globally (like on the explorer) we do + // not have the data to make that distinction. + if (item.isBsqSwapTx()) { + awesomeIcon = AwesomeIcon.EXCHANGE; + style = "dao-tx-type-bsq-swap-icon"; + toolTipText = Res.get("dao.tx.bsqSwapTx"); + } else { + awesomeIcon = AwesomeIcon.LEAF; + style = "dao-tx-type-trade-fee-icon"; + } break; case PROPOSAL: case COMPENSATION_REQUEST: diff --git a/desktop/src/main/java/bisq/desktop/main/debug/DebugView.java b/desktop/src/main/java/bisq/desktop/main/debug/DebugView.java index 89d0acbf09..6e5613df31 100644 --- a/desktop/src/main/java/bisq/desktop/main/debug/DebugView.java +++ b/desktop/src/main/java/bisq/desktop/main/debug/DebugView.java @@ -23,56 +23,56 @@ import bisq.desktop.components.TitledGroupBg; import bisq.core.offer.availability.tasks.ProcessOfferAvailabilityResponse; import bisq.core.offer.availability.tasks.SendOfferAvailabilityRequest; -import bisq.core.offer.placeoffer.tasks.AddToOfferBook; -import bisq.core.offer.placeoffer.tasks.CreateMakerFeeTx; -import bisq.core.offer.placeoffer.tasks.ValidateOffer; -import bisq.core.trade.protocol.tasks.ApplyFilter; -import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness; -import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest; -import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDepositTxAndDelayedPayoutTxMessage; -import bisq.core.trade.protocol.tasks.buyer.BuyerProcessPayoutTxPublishedMessage; -import bisq.core.trade.protocol.tasks.buyer.BuyerSendCounterCurrencyTransferStartedMessage; -import bisq.core.trade.protocol.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureResponse; -import bisq.core.trade.protocol.tasks.buyer.BuyerSetupDepositTxListener; -import bisq.core.trade.protocol.tasks.buyer.BuyerSetupPayoutTxListener; -import bisq.core.trade.protocol.tasks.buyer.BuyerSignPayoutTx; -import bisq.core.trade.protocol.tasks.buyer.BuyerSignsDelayedPayoutTx; -import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesFinalDelayedPayoutTx; -import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesPreparedDelayedPayoutTx; -import bisq.core.trade.protocol.tasks.buyer_as_maker.BuyerAsMakerCreatesAndSignsDepositTx; -import bisq.core.trade.protocol.tasks.buyer_as_maker.BuyerAsMakerSendsInputsForDepositTxResponse; -import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerCreatesDepositTxInputs; -import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSendsDepositTxMessage; -import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSignsDepositTx; -import bisq.core.trade.protocol.tasks.maker.MakerCreateAndSignContract; -import bisq.core.trade.protocol.tasks.maker.MakerProcessesInputsForDepositTxRequest; -import bisq.core.trade.protocol.tasks.maker.MakerRemovesOpenOffer; -import bisq.core.trade.protocol.tasks.maker.MakerSetsLockTime; -import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment; -import bisq.core.trade.protocol.tasks.seller.SellerBroadcastPayoutTx; -import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx; -import bisq.core.trade.protocol.tasks.seller.SellerFinalizesDelayedPayoutTx; -import bisq.core.trade.protocol.tasks.seller.SellerProcessCounterCurrencyTransferStartedMessage; -import bisq.core.trade.protocol.tasks.seller.SellerProcessDelayedPayoutTxSignatureResponse; -import bisq.core.trade.protocol.tasks.seller.SellerPublishesDepositTx; -import bisq.core.trade.protocol.tasks.seller.SellerPublishesTradeStatistics; -import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest; -import bisq.core.trade.protocol.tasks.seller.SellerSendPayoutTxPublishedMessage; -import bisq.core.trade.protocol.tasks.seller.SellerSendsDepositTxAndDelayedPayoutTxMessage; -import bisq.core.trade.protocol.tasks.seller.SellerSignAndFinalizePayoutTx; -import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx; -import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerCreatesUnsignedDepositTx; -import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerFinalizesDepositTx; -import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerProcessDepositTxMessage; -import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerSendsInputsForDepositTxResponse; -import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerCreatesDepositTxInputs; -import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerSignsDepositTx; -import bisq.core.trade.protocol.tasks.taker.CreateTakerFeeTx; -import bisq.core.trade.protocol.tasks.taker.TakerProcessesInputsForDepositTxResponse; -import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx; -import bisq.core.trade.protocol.tasks.taker.TakerSendInputsForDepositTxRequest; -import bisq.core.trade.protocol.tasks.taker.TakerVerifyAndSignContract; -import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment; +import bisq.core.offer.placeoffer.bisq_v1.tasks.AddToOfferBook; +import bisq.core.offer.placeoffer.bisq_v1.tasks.CreateMakerFeeTx; +import bisq.core.offer.placeoffer.bisq_v1.tasks.ValidateOffer; +import bisq.core.trade.protocol.bisq_v1.tasks.ApplyFilter; +import bisq.core.trade.protocol.bisq_v1.tasks.VerifyPeersAccountAgeWitness; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerProcessDepositTxAndDelayedPayoutTxMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerProcessPayoutTxPublishedMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerSendCounterCurrencyTransferStartedMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureResponse; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerSetupDepositTxListener; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerSetupPayoutTxListener; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerSignPayoutTx; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerSignsDelayedPayoutTx; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerVerifiesFinalDelayedPayoutTx; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerVerifiesPreparedDelayedPayoutTx; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer_as_maker.BuyerAsMakerCreatesAndSignsDepositTx; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer_as_maker.BuyerAsMakerSendsInputsForDepositTxResponse; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer_as_taker.BuyerAsTakerCreatesDepositTxInputs; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer_as_taker.BuyerAsTakerSendsDepositTxMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.buyer_as_taker.BuyerAsTakerSignsDepositTx; +import bisq.core.trade.protocol.bisq_v1.tasks.maker.MakerCreateAndSignContract; +import bisq.core.trade.protocol.bisq_v1.tasks.maker.MakerProcessesInputsForDepositTxRequest; +import bisq.core.trade.protocol.bisq_v1.tasks.maker.MakerRemovesOpenOffer; +import bisq.core.trade.protocol.bisq_v1.tasks.maker.MakerSetsLockTime; +import bisq.core.trade.protocol.bisq_v1.tasks.maker.MakerVerifyTakerFeePayment; +import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerBroadcastPayoutTx; +import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerCreatesDelayedPayoutTx; +import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerFinalizesDelayedPayoutTx; +import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerProcessCounterCurrencyTransferStartedMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerProcessDelayedPayoutTxSignatureResponse; +import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerPublishesDepositTx; +import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerPublishesTradeStatistics; +import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest; +import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerSendPayoutTxPublishedMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerSendsDepositTxAndDelayedPayoutTxMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerSignAndFinalizePayoutTx; +import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerSignsDelayedPayoutTx; +import bisq.core.trade.protocol.bisq_v1.tasks.seller_as_maker.SellerAsMakerCreatesUnsignedDepositTx; +import bisq.core.trade.protocol.bisq_v1.tasks.seller_as_maker.SellerAsMakerFinalizesDepositTx; +import bisq.core.trade.protocol.bisq_v1.tasks.seller_as_maker.SellerAsMakerProcessDepositTxMessage; +import bisq.core.trade.protocol.bisq_v1.tasks.seller_as_maker.SellerAsMakerSendsInputsForDepositTxResponse; +import bisq.core.trade.protocol.bisq_v1.tasks.seller_as_taker.SellerAsTakerCreatesDepositTxInputs; +import bisq.core.trade.protocol.bisq_v1.tasks.seller_as_taker.SellerAsTakerSignsDepositTx; +import bisq.core.trade.protocol.bisq_v1.tasks.taker.CreateTakerFeeTx; +import bisq.core.trade.protocol.bisq_v1.tasks.taker.TakerProcessesInputsForDepositTxResponse; +import bisq.core.trade.protocol.bisq_v1.tasks.taker.TakerPublishFeeTx; +import bisq.core.trade.protocol.bisq_v1.tasks.taker.TakerSendInputsForDepositTxRequest; +import bisq.core.trade.protocol.bisq_v1.tasks.taker.TakerVerifyAndSignContract; +import bisq.core.trade.protocol.bisq_v1.tasks.taker.TakerVerifyMakerFeePayment; import bisq.common.taskrunner.Task; import bisq.common.util.Tuple2; diff --git a/desktop/src/main/java/bisq/desktop/main/funds/locked/LockedListItem.java b/desktop/src/main/java/bisq/desktop/main/funds/locked/LockedListItem.java index a2d80382dc..e7f483078d 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/locked/LockedListItem.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/locked/LockedListItem.java @@ -25,7 +25,7 @@ import bisq.core.btc.model.AddressEntry; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.WalletService; import bisq.core.locale.Res; -import bisq.core.trade.Trade; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.util.coin.CoinFormatter; import org.bitcoinj.core.Address; diff --git a/desktop/src/main/java/bisq/desktop/main/funds/locked/LockedView.java b/desktop/src/main/java/bisq/desktop/main/funds/locked/LockedView.java index db459d0515..45544d09e4 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/locked/LockedView.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/locked/LockedView.java @@ -33,9 +33,9 @@ import bisq.core.btc.wallet.BtcWalletService; import bisq.core.locale.Res; import bisq.core.offer.OpenOffer; import bisq.core.offer.OpenOfferManager; -import bisq.core.trade.Tradable; -import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; +import bisq.core.trade.model.Tradable; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.user.Preferences; import bisq.core.util.FormattingUtils; import bisq.core.util.coin.CoinFormatter; diff --git a/desktop/src/main/java/bisq/desktop/main/funds/reserved/ReservedView.java b/desktop/src/main/java/bisq/desktop/main/funds/reserved/ReservedView.java index 4d5db041fe..ab42a4fd74 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/reserved/ReservedView.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/reserved/ReservedView.java @@ -33,9 +33,9 @@ import bisq.core.btc.wallet.BtcWalletService; import bisq.core.locale.Res; import bisq.core.offer.OpenOffer; import bisq.core.offer.OpenOfferManager; -import bisq.core.trade.Tradable; -import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; +import bisq.core.trade.model.Tradable; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.user.Preferences; import bisq.core.util.FormattingUtils; import bisq.core.util.coin.CoinFormatter; diff --git a/desktop/src/main/java/bisq/desktop/main/funds/transactions/DisplayedTransactions.java b/desktop/src/main/java/bisq/desktop/main/funds/transactions/DisplayedTransactions.java index 0606299547..a94466f9cc 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/transactions/DisplayedTransactions.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/transactions/DisplayedTransactions.java @@ -18,7 +18,7 @@ package bisq.desktop.main.funds.transactions; import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.trade.Tradable; +import bisq.core.trade.model.Tradable; import org.bitcoinj.core.Transaction; diff --git a/desktop/src/main/java/bisq/desktop/main/funds/transactions/DummyTransactionAwareTradable.java b/desktop/src/main/java/bisq/desktop/main/funds/transactions/DummyTransactionAwareTradable.java index 74b24037ab..a5ee768e53 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/transactions/DummyTransactionAwareTradable.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/transactions/DummyTransactionAwareTradable.java @@ -17,7 +17,7 @@ package bisq.desktop.main.funds.transactions; -import bisq.core.trade.Tradable; +import bisq.core.trade.model.Tradable; import org.bitcoinj.core.Transaction; diff --git a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TradableRepository.java b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TradableRepository.java index e58221dac1..26dc50366c 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TradableRepository.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TradableRepository.java @@ -18,10 +18,11 @@ package bisq.desktop.main.funds.transactions; import bisq.core.offer.OpenOfferManager; -import bisq.core.trade.Tradable; +import bisq.core.trade.ClosedTradableManager; import bisq.core.trade.TradeManager; -import bisq.core.trade.closed.ClosedTradableManager; -import bisq.core.trade.failed.FailedTradesManager; +import bisq.core.trade.bisq_v1.FailedTradesManager; +import bisq.core.trade.bsq_swap.BsqSwapTradeManager; +import bisq.core.trade.model.Tradable; import javax.inject.Inject; import javax.inject.Singleton; @@ -36,24 +37,28 @@ public class TradableRepository { private final TradeManager tradeManager; private final ClosedTradableManager closedTradableManager; private final FailedTradesManager failedTradesManager; + private final BsqSwapTradeManager bsqSwapTradeManager; @Inject TradableRepository(OpenOfferManager openOfferManager, TradeManager tradeManager, ClosedTradableManager closedTradableManager, - FailedTradesManager failedTradesManager) { + FailedTradesManager failedTradesManager, + BsqSwapTradeManager bsqSwapTradeManager) { this.openOfferManager = openOfferManager; this.tradeManager = tradeManager; this.closedTradableManager = closedTradableManager; this.failedTradesManager = failedTradesManager; + this.bsqSwapTradeManager = bsqSwapTradeManager; } - Set getAll() { + public Set getAll() { return ImmutableSet.builder() .addAll(openOfferManager.getObservableList()) .addAll(tradeManager.getObservableList()) .addAll(closedTradableManager.getObservableList()) .addAll(failedTradesManager.getObservableList()) + .addAll(bsqSwapTradeManager.getObservableList()) .build(); } } diff --git a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareOpenOffer.java b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareOpenOffer.java index fd59a7dc1a..4a4368b01a 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareOpenOffer.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareOpenOffer.java @@ -19,7 +19,7 @@ package bisq.desktop.main.funds.transactions; import bisq.core.offer.Offer; import bisq.core.offer.OpenOffer; -import bisq.core.trade.Tradable; +import bisq.core.trade.model.Tradable; import org.bitcoinj.core.Transaction; @@ -36,7 +36,7 @@ class TransactionAwareOpenOffer implements TransactionAwareTradable { String txId = transaction.getTxId().toString(); - return paymentTxId.equals(txId); + return paymentTxId != null && paymentTxId.equals(txId); } public Tradable asTradable() { diff --git a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTradable.java b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTradable.java index 199e01590f..b00c283d96 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTradable.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTradable.java @@ -17,7 +17,7 @@ package bisq.desktop.main.funds.transactions; -import bisq.core.trade.Tradable; +import bisq.core.trade.model.Tradable; import org.bitcoinj.core.Transaction; diff --git a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTradableFactory.java b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTradableFactory.java index 86b7699e2e..3c699d9a0f 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTradableFactory.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTradableFactory.java @@ -21,8 +21,8 @@ import bisq.core.btc.wallet.BtcWalletService; import bisq.core.offer.OpenOffer; import bisq.core.support.dispute.arbitration.ArbitrationManager; import bisq.core.support.dispute.refund.RefundManager; -import bisq.core.trade.Tradable; -import bisq.core.trade.Trade; +import bisq.core.trade.model.Tradable; +import bisq.core.trade.model.TradeModel; import bisq.common.crypto.PubKeyRing; @@ -48,17 +48,17 @@ public class TransactionAwareTradableFactory { this.pubKeyRing = pubKeyRing; } - TransactionAwareTradable create(Tradable delegate) { - if (delegate instanceof OpenOffer) { - return new TransactionAwareOpenOffer((OpenOffer) delegate); - } else if (delegate instanceof Trade) { - return new TransactionAwareTrade((Trade) delegate, + TransactionAwareTradable create(Tradable tradable) { + if (tradable instanceof OpenOffer) { + return new TransactionAwareOpenOffer((OpenOffer) tradable); + } else if (tradable instanceof TradeModel) { + return new TransactionAwareTrade((TradeModel) tradable, arbitrationManager, refundManager, btcWalletService, pubKeyRing); } else { - return new DummyTransactionAwareTradable(delegate); + return new DummyTransactionAwareTradable(tradable); } } } diff --git a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTrade.java b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTrade.java index 9d0ab09ef2..d940ad5df7 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTrade.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTrade.java @@ -22,9 +22,11 @@ import bisq.core.offer.Offer; import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.arbitration.ArbitrationManager; import bisq.core.support.dispute.refund.RefundManager; -import bisq.core.trade.Contract; -import bisq.core.trade.Tradable; -import bisq.core.trade.Trade; +import bisq.core.trade.model.Tradable; +import bisq.core.trade.model.TradeModel; +import bisq.core.trade.model.bisq_v1.Contract; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; import bisq.common.crypto.PubKeyRing; @@ -43,18 +45,18 @@ import static com.google.common.base.Preconditions.checkNotNull; @Slf4j class TransactionAwareTrade implements TransactionAwareTradable { - private final Trade trade; + private final TradeModel tradeModel; private final ArbitrationManager arbitrationManager; private final RefundManager refundManager; private final BtcWalletService btcWalletService; private final PubKeyRing pubKeyRing; - TransactionAwareTrade(Trade trade, + TransactionAwareTrade(TradeModel tradeModel, ArbitrationManager arbitrationManager, RefundManager refundManager, BtcWalletService btcWalletService, PubKeyRing pubKeyRing) { - this.trade = trade; + this.tradeModel = tradeModel; this.arbitrationManager = arbitrationManager; this.refundManager = refundManager; this.btcWalletService = btcWalletService; @@ -66,19 +68,34 @@ class TransactionAwareTrade implements TransactionAwareTradable { Sha256Hash hash = transaction.getTxId(); String txId = hash.toString(); - boolean isTakerOfferFeeTx = txId.equals(trade.getTakerFeeTxId()); - boolean isOfferFeeTx = isOfferFeeTx(txId); - boolean isDepositTx = isDepositTx(hash); - boolean isPayoutTx = isPayoutTx(hash); - boolean isDisputedPayoutTx = isDisputedPayoutTx(txId); - boolean isDelayedPayoutTx = transaction.getLockTime() != 0 && isDelayedPayoutTx(txId); - boolean isRefundPayoutTx = isRefundPayoutTx(txId); + boolean tradeRelated = false; + if (tradeModel instanceof Trade) { + Trade trade = (Trade) tradeModel; + boolean isTakerOfferFeeTx = txId.equals(trade.getTakerFeeTxId()); + boolean isOfferFeeTx = isOfferFeeTx(txId); + boolean isDepositTx = isDepositTx(hash); + boolean isPayoutTx = isPayoutTx(hash); + boolean isDisputedPayoutTx = isDisputedPayoutTx(txId); + boolean isDelayedPayoutTx = transaction.getLockTime() != 0 && isDelayedPayoutTx(txId); + boolean isRefundPayoutTx = isRefundPayoutTx(trade, txId); + tradeRelated = isTakerOfferFeeTx || + isOfferFeeTx || + isDepositTx || + isPayoutTx || + isDisputedPayoutTx || + isDelayedPayoutTx || + isRefundPayoutTx; + } + boolean isBsqSwapTrade = isBsqSwapTrade(txId); - return isTakerOfferFeeTx || isOfferFeeTx || isDepositTx || isPayoutTx || - isDisputedPayoutTx || isDelayedPayoutTx || isRefundPayoutTx; + return tradeRelated || isBsqSwapTrade; } private boolean isPayoutTx(Sha256Hash txId) { + if (isBsqSwapTrade()) + return false; + + Trade trade = (Trade) tradeModel; return Optional.ofNullable(trade.getPayoutTx()) .map(Transaction::getTxId) .map(hash -> hash.equals(txId)) @@ -86,6 +103,10 @@ class TransactionAwareTrade implements TransactionAwareTradable { } private boolean isDepositTx(Sha256Hash txId) { + if (isBsqSwapTrade()) + return false; + + Trade trade = (Trade) tradeModel; return Optional.ofNullable(trade.getDepositTx()) .map(Transaction::getTxId) .map(hash -> hash.equals(txId)) @@ -93,17 +114,23 @@ class TransactionAwareTrade implements TransactionAwareTradable { } private boolean isOfferFeeTx(String txId) { - return Optional.ofNullable(trade.getOffer()) + if (isBsqSwapTrade()) + return false; + + return Optional.ofNullable(tradeModel.getOffer()) .map(Offer::getOfferFeePaymentTxId) .map(paymentTxId -> paymentTxId.equals(txId)) .orElse(false); } private boolean isDisputedPayoutTx(String txId) { - String delegateId = trade.getId(); + if (isBsqSwapTrade()) + return false; + + String delegateId = tradeModel.getId(); ObservableList disputes = arbitrationManager.getDisputesAsObservableList(); - boolean isAnyDisputeRelatedToThis = arbitrationManager.getDisputedTradeIds().contains(trade.getId()); + boolean isAnyDisputeRelatedToThis = arbitrationManager.getDisputedTradeIds().contains(tradeModel.getId()); return isAnyDisputeRelatedToThis && disputes.stream() .anyMatch(dispute -> { @@ -118,6 +145,9 @@ class TransactionAwareTrade implements TransactionAwareTradable { } boolean isDelayedPayoutTx(String txId) { + if (isBsqSwapTrade()) + return false; + Transaction transaction = btcWalletService.getTransaction(txId); if (transaction == null) return false; @@ -142,8 +172,11 @@ class TransactionAwareTrade implements TransactionAwareTradable { }); } - private boolean isRefundPayoutTx(String txId) { - String tradeId = trade.getId(); + private boolean isRefundPayoutTx(Trade trade, String txId) { + if (isBsqSwapTrade()) + return false; + + String tradeId = tradeModel.getId(); ObservableList disputes = refundManager.getDisputesAsObservableList(); boolean isAnyDisputeRelatedToThis = refundManager.getDisputedTradeIds().contains(tradeId); @@ -171,8 +204,19 @@ class TransactionAwareTrade implements TransactionAwareTradable { return false; } + private boolean isBsqSwapTrade() { + return tradeModel instanceof BsqSwapTrade; + } + + private boolean isBsqSwapTrade(String txId) { + if (isBsqSwapTrade()) { + return (txId.equals(((BsqSwapTrade) tradeModel).getTxId())); + } + return false; + } + @Override public Tradable asTradable() { - return trade; + return tradeModel; } } diff --git a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionsListItem.java b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionsListItem.java index f977c80f78..d6a41f76f3 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionsListItem.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionsListItem.java @@ -30,8 +30,9 @@ import bisq.core.dao.state.model.blockchain.TxType; import bisq.core.locale.Res; import bisq.core.offer.Offer; import bisq.core.offer.OpenOffer; -import bisq.core.trade.Tradable; -import bisq.core.trade.Trade; +import bisq.core.trade.model.Tradable; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; import bisq.core.util.coin.CoinFormatter; import org.bitcoinj.core.Coin; @@ -252,6 +253,21 @@ class TransactionsListItem { } } } + } else if (tradable instanceof BsqSwapTrade) { + direction = amountAsCoin.isPositive() ? Res.get("funds.tx.bsqSwapBuy") : + Res.get("funds.tx.bsqSwapSell"); + + // Find my BTC output address + var tx = btcWalletService.getTransaction(((BsqSwapTrade) tradable).getTxId()); + addressString = tx != null ? + tx.getOutputs().stream() + .filter(output -> output.isMine(btcWalletService.getWallet())) + .map(output -> output.getScriptPubKey().getToAddress(btcWalletService.getParams())) + .map(Object::toString) + .findFirst() + .orElse("") : + ""; + details = Res.get("funds.tx.bsqSwapTx", tradeId); } } else { if (amountAsCoin.isZero()) { diff --git a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionsView.java b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionsView.java index 3a2d3e1ba2..49e307f1b0 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionsView.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionsView.java @@ -25,6 +25,7 @@ import bisq.desktop.components.AutoTooltipLabel; import bisq.desktop.components.ExternalHyperlink; import bisq.desktop.components.HyperlinkWithIcon; import bisq.desktop.main.overlays.popups.Popup; +import bisq.desktop.main.overlays.windows.BsqTradeDetailsWindow; import bisq.desktop.main.overlays.windows.OfferDetailsWindow; import bisq.desktop.main.overlays.windows.TradeDetailsWindow; import bisq.desktop.util.GUIUtil; @@ -33,8 +34,9 @@ import bisq.core.btc.setup.WalletsSetup; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.locale.Res; import bisq.core.offer.OpenOffer; -import bisq.core.trade.Tradable; -import bisq.core.trade.Trade; +import bisq.core.trade.model.Tradable; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; import bisq.core.user.Preferences; import bisq.network.p2p.P2PService; @@ -85,8 +87,6 @@ import javax.annotation.Nullable; @FxmlView public class TransactionsView extends ActivatableView { - - @FXML TableView tableView; @FXML @@ -106,6 +106,7 @@ public class TransactionsView extends ActivatableView { private final WalletsSetup walletsSetup; private final Preferences preferences; private final TradeDetailsWindow tradeDetailsWindow; + private final BsqTradeDetailsWindow bsqTradeDetailsWindow; private final OfferDetailsWindow offerDetailsWindow; private WalletChangeEventListener walletChangeEventListener; @@ -123,6 +124,7 @@ public class TransactionsView extends ActivatableView { WalletsSetup walletsSetup, Preferences preferences, TradeDetailsWindow tradeDetailsWindow, + BsqTradeDetailsWindow bsqTradeDetailsWindow, OfferDetailsWindow offerDetailsWindow, DisplayedTransactionsFactory displayedTransactionsFactory) { this.btcWalletService = btcWalletService; @@ -130,6 +132,7 @@ public class TransactionsView extends ActivatableView { this.walletsSetup = walletsSetup; this.preferences = preferences; this.tradeDetailsWindow = tradeDetailsWindow; + this.bsqTradeDetailsWindow = bsqTradeDetailsWindow; this.offerDetailsWindow = offerDetailsWindow; this.displayedTransactions = displayedTransactionsFactory.create(); this.sortedDisplayedTransactions = displayedTransactions.asSortedList(); @@ -260,10 +263,13 @@ public class TransactionsView extends ActivatableView { } private void openDetailPopup(TransactionsListItem item) { - if (item.getTradable() instanceof OpenOffer) + if (item.getTradable() instanceof OpenOffer) { offerDetailsWindow.show(item.getTradable().getOffer()); - else if (item.getTradable() instanceof Trade) + } else if ((item.getTradable()) instanceof Trade) { tradeDetailsWindow.show((Trade) item.getTradable()); + } else if ((item.getTradable()) instanceof BsqSwapTrade) { + bsqTradeDetailsWindow.show((BsqSwapTrade) item.getTradable()); + } } diff --git a/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalView.java b/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalView.java index 1af8806df2..35dbd66154 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalView.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalView.java @@ -40,8 +40,8 @@ import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.Restrictions; import bisq.core.locale.Res; import bisq.core.provider.fee.FeeService; -import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.user.DontShowAgainLookup; import bisq.core.user.Preferences; import bisq.core.util.FormattingUtils; diff --git a/desktop/src/main/java/bisq/desktop/main/market/MarketView.java b/desktop/src/main/java/bisq/desktop/main/market/MarketView.java index 4aa94894f8..66f9d66171 100644 --- a/desktop/src/main/java/bisq/desktop/main/market/MarketView.java +++ b/desktop/src/main/java/bisq/desktop/main/market/MarketView.java @@ -35,7 +35,7 @@ import bisq.desktop.util.DisplayUtils; import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.bisq_v1.OfferPayload; import bisq.core.trade.statistics.TradeStatistics3; import bisq.core.trade.statistics.TradeStatistics3StorageService; import bisq.core.util.FormattingUtils; @@ -211,8 +211,8 @@ public class MarketView extends ActivatableView { private String getAllOffersWithReferralId() { List list = offerBook.getOfferBookListItems().stream() .map(OfferBookListItem::getOffer) - .filter(offer -> offer.getOfferPayload().getExtraDataMap() != null) - .filter(offer -> offer.getOfferPayload().getExtraDataMap().get(OfferPayload.REFERRAL_ID) != null) + .filter(offer -> offer.getExtraDataMap() != null) + .filter(offer -> offer.getExtraDataMap().get(OfferPayload.REFERRAL_ID) != null) .map(offer -> { StringBuilder sb = new StringBuilder(); sb.append("Offer ID: ").append(offer.getId()).append("\n") @@ -221,7 +221,7 @@ public class MarketView extends ActivatableView { .append("Price: ").append(FormattingUtils.formatPrice(offer.getPrice())).append("\n") .append("Amount: ").append(DisplayUtils.formatAmount(offer, formatter)).append(" BTC\n") .append("Payment method: ").append(Res.get(offer.getPaymentMethod().getId())).append("\n") - .append("ReferralID: ").append(offer.getOfferPayload().getExtraDataMap().get(OfferPayload.REFERRAL_ID)); + .append("ReferralID: ").append(offer.getExtraDataMap().get(OfferPayload.REFERRAL_ID)); return sb.toString(); }) .collect(Collectors.toList()); diff --git a/desktop/src/main/java/bisq/desktop/main/market/offerbook/OfferBookChartView.java b/desktop/src/main/java/bisq/desktop/main/market/offerbook/OfferBookChartView.java index e17ecd2021..c15860f385 100644 --- a/desktop/src/main/java/bisq/desktop/main/market/offerbook/OfferBookChartView.java +++ b/desktop/src/main/java/bisq/desktop/main/market/offerbook/OfferBookChartView.java @@ -37,7 +37,7 @@ import bisq.desktop.util.GUIUtil; import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferDirection; import bisq.core.util.FormattingUtils; import bisq.core.util.VolumeUtil; import bisq.core.util.coin.CoinFormatter; @@ -164,8 +164,8 @@ public class OfferBookChartView extends ActivatableViewAndModel, VBox, Button, Label> tupleBuy = getOfferTable(OfferPayload.Direction.BUY); - Tuple4, VBox, Button, Label> tupleSell = getOfferTable(OfferPayload.Direction.SELL); + Tuple4, VBox, Button, Label> tupleBuy = getOfferTable(OfferDirection.BUY); + Tuple4, VBox, Button, Label> tupleSell = getOfferTable(OfferDirection.SELL); buyOfferTableView = tupleBuy.first; sellOfferTableView = tupleSell.first; @@ -181,8 +181,8 @@ public class OfferBookChartView extends ActivatableViewAndModel, VBox, Button, Label> getOfferTable(OfferPayload.Direction direction) { + private Tuple4, VBox, Button, Label> getOfferTable(OfferDirection direction) { TableView tableView = new TableView<>(); tableView.setMinHeight(initialOfferTableViewHeight); tableView.setPrefHeight(initialOfferTableViewHeight); @@ -605,7 +605,7 @@ public class OfferBookChartView extends ActivatableViewAndModel avatarColumn = new AutoTooltipTableColumn<>(isSellOffer ? diff --git a/desktop/src/main/java/bisq/desktop/main/market/offerbook/OfferBookChartViewModel.java b/desktop/src/main/java/bisq/desktop/main/market/offerbook/OfferBookChartViewModel.java index 6817881858..1c9c275d78 100644 --- a/desktop/src/main/java/bisq/desktop/main/market/offerbook/OfferBookChartViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/market/offerbook/OfferBookChartViewModel.java @@ -35,7 +35,7 @@ import bisq.core.locale.GlobalSettings; import bisq.core.locale.TradeCurrency; import bisq.core.monetary.Price; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferDirection; import bisq.core.provider.price.PriceFeedService; import bisq.core.user.Preferences; import bisq.core.util.VolumeUtil; @@ -120,7 +120,7 @@ class OfferBookChartViewModel extends ActivatableViewModel { list.addAll(c.getAddedSubList()); if (list.stream() .map(OfferBookListItem::getOffer) - .anyMatch(e -> e.getOfferPayload().getCurrencyCode().equals(selectedTradeCurrencyProperty.get().getCode()))) + .anyMatch(e -> e.getCurrencyCode().equals(selectedTradeCurrencyProperty.get().getCode()))) updateChartData(); } @@ -308,7 +308,7 @@ class OfferBookChartViewModel extends ActivatableViewModel { List allBuyOffers = offerBookListItems.stream() .map(OfferBookListItem::getOffer) .filter(e -> e.getCurrencyCode().equals(selectedTradeCurrencyProperty.get().getCode()) - && e.getDirection().equals(OfferPayload.Direction.BUY)) + && e.getDirection().equals(OfferDirection.BUY)) .sorted(buyOfferSortComparator) .collect(Collectors.toList()); @@ -332,12 +332,12 @@ class OfferBookChartViewModel extends ActivatableViewModel { maxPlacesForBuyVolume.set(formatVolume(offer, false).length()); } - buildChartAndTableEntries(allBuyOffers, OfferPayload.Direction.BUY, buyData, topBuyOfferList); + buildChartAndTableEntries(allBuyOffers, OfferDirection.BUY, buyData, topBuyOfferList); List allSellOffers = offerBookListItems.stream() .map(OfferBookListItem::getOffer) .filter(e -> e.getCurrencyCode().equals(selectedTradeCurrencyProperty.get().getCode()) - && e.getDirection().equals(OfferPayload.Direction.SELL)) + && e.getDirection().equals(OfferDirection.SELL)) .sorted(sellOfferSortComparator) .collect(Collectors.toList()); @@ -359,11 +359,11 @@ class OfferBookChartViewModel extends ActivatableViewModel { maxPlacesForSellVolume.set(formatVolume(offer, false).length()); } - buildChartAndTableEntries(allSellOffers, OfferPayload.Direction.SELL, sellData, topSellOfferList); + buildChartAndTableEntries(allSellOffers, OfferDirection.SELL, sellData, topSellOfferList); } private void buildChartAndTableEntries(List sortedList, - OfferPayload.Direction direction, + OfferDirection direction, List> data, ObservableList offerTableList) { data.clear(); @@ -378,12 +378,12 @@ class OfferBookChartViewModel extends ActivatableViewModel { double priceAsDouble = (double) price.getValue() / LongMath.pow(10, price.smallestUnitExponent()); if (CurrencyUtil.isCryptoCurrency(getCurrencyCode())) { - if (direction.equals(OfferPayload.Direction.SELL)) + if (direction.equals(OfferDirection.SELL)) data.add(0, new XYChart.Data<>(priceAsDouble, accumulatedAmount)); else data.add(new XYChart.Data<>(priceAsDouble, accumulatedAmount)); } else { - if (direction.equals(OfferPayload.Direction.BUY)) + if (direction.equals(OfferDirection.BUY)) data.add(0, new XYChart.Data<>(priceAsDouble, accumulatedAmount)); else data.add(new XYChart.Data<>(priceAsDouble, accumulatedAmount)); diff --git a/desktop/src/main/java/bisq/desktop/main/market/spread/SpreadViewModel.java b/desktop/src/main/java/bisq/desktop/main/market/spread/SpreadViewModel.java index 545c947836..dd86703b0a 100644 --- a/desktop/src/main/java/bisq/desktop/main/market/spread/SpreadViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/market/spread/SpreadViewModel.java @@ -27,7 +27,7 @@ import bisq.core.locale.Res; import bisq.core.monetary.Altcoin; import bisq.core.monetary.Price; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferDirection; import bisq.core.provider.price.MarketPrice; import bisq.core.provider.price.PriceFeedService; import bisq.core.util.FormattingUtils; @@ -139,13 +139,13 @@ class SpreadViewModel extends ActivatableViewModel { for (String key : offersByCurrencyMap.keySet()) { List offers = offersByCurrencyMap.get(key); - final boolean isFiatCurrency = (offers.size() > 0 && !offers.get(0).getPaymentMethod().isAsset()); + boolean isFiatCurrency = (offers.size() > 0 && offers.get(0).getPaymentMethod().isFiat()); List uniqueOffers = offers.stream().filter(distinctByKey(Offer::getId)).collect(Collectors.toList()); List buyOffers = uniqueOffers .stream() - .filter(e -> e.getDirection().equals(OfferPayload.Direction.BUY)) + .filter(e -> e.getDirection().equals(OfferDirection.BUY)) .sorted((o1, o2) -> { long a = o1.getPrice() != null ? o1.getPrice().getValue() : 0; long b = o2.getPrice() != null ? o2.getPrice().getValue() : 0; @@ -162,7 +162,7 @@ class SpreadViewModel extends ActivatableViewModel { List sellOffers = uniqueOffers .stream() - .filter(e -> e.getDirection().equals(OfferPayload.Direction.SELL)) + .filter(e -> e.getDirection().equals(OfferDirection.SELL)) .sorted((o1, o2) -> { long a = o1.getPrice() != null ? o1.getPrice().getValue() : 0; long b = o2.getPrice() != null ? o2.getPrice().getValue() : 0; diff --git a/desktop/src/main/java/bisq/desktop/main/offer/BuyOfferView.java b/desktop/src/main/java/bisq/desktop/main/offer/BuyOfferView.java index 47675d6822..f473ec7146 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/BuyOfferView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/BuyOfferView.java @@ -21,7 +21,7 @@ import bisq.desktop.Navigation; import bisq.desktop.common.view.FxmlView; import bisq.desktop.common.view.ViewLoader; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferDirection; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import bisq.core.user.Preferences; import bisq.core.user.User; @@ -46,6 +46,6 @@ public class BuyOfferView extends OfferView { arbitratorManager, user, p2PService, - OfferPayload.Direction.BUY); + OfferDirection.BUY); } } diff --git a/desktop/src/main/java/bisq/desktop/main/offer/OfferView.java b/desktop/src/main/java/bisq/desktop/main/offer/OfferView.java index e071b6967b..8e828c4204 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/OfferView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/OfferView.java @@ -22,9 +22,11 @@ import bisq.desktop.common.view.ActivatableView; import bisq.desktop.common.view.View; import bisq.desktop.common.view.ViewLoader; import bisq.desktop.main.MainView; -import bisq.desktop.main.offer.createoffer.CreateOfferView; +import bisq.desktop.main.offer.bisq_v1.createoffer.CreateOfferView; +import bisq.desktop.main.offer.bisq_v1.takeoffer.TakeOfferView; +import bisq.desktop.main.offer.bsq_swap.create_offer.BsqSwapCreateOfferView; +import bisq.desktop.main.offer.bsq_swap.take_offer.BsqSwapTakeOfferView; import bisq.desktop.main.offer.offerbook.OfferBookView; -import bisq.desktop.main.offer.takeoffer.TakeOfferView; import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.util.GUIUtil; @@ -34,7 +36,8 @@ import bisq.core.locale.LanguageUtil; import bisq.core.locale.Res; import bisq.core.locale.TradeCurrency; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferDirection; +import bisq.core.payment.payload.PaymentMethod; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import bisq.core.user.Preferences; import bisq.core.user.User; @@ -57,7 +60,9 @@ public abstract class OfferView extends ActivatableView { private OfferBookView offerBookView; private CreateOfferView createOfferView; + private BsqSwapCreateOfferView bsqSwapCreateOfferView; private TakeOfferView takeOfferView; + private BsqSwapTakeOfferView bsqSwapTakeOfferView; private AnchorPane createOfferPane, takeOfferPane; private Tab takeOfferTab, createOfferTab, offerBookTab; @@ -66,7 +71,7 @@ public abstract class OfferView extends ActivatableView { private final Preferences preferences; private final User user; private final P2PService p2PService; - private final OfferPayload.Direction direction; + private final OfferDirection direction; private final ArbitratorManager arbitratorManager; private Offer offer; @@ -75,6 +80,7 @@ public abstract class OfferView extends ActivatableView { private Navigation.Listener navigationListener; private ChangeListener tabChangeListener; private ListChangeListener tabListChangeListener; + private OfferView.OfferActionHandler offerActionHandler; protected OfferView(ViewLoader viewLoader, Navigation navigation, @@ -82,7 +88,7 @@ public abstract class OfferView extends ActivatableView { ArbitratorManager arbitratorManager, User user, P2PService p2PService, - OfferPayload.Direction direction) { + OfferDirection direction) { this.viewLoader = viewLoader; this.navigation = navigation; this.preferences = preferences; @@ -102,8 +108,12 @@ public abstract class OfferView extends ActivatableView { if (newValue != null) { if (newValue.equals(createOfferTab) && createOfferView != null) { createOfferView.onTabSelected(true); + } else if (newValue.equals(createOfferTab) && bsqSwapCreateOfferView != null) { + bsqSwapCreateOfferView.onTabSelected(true); } else if (newValue.equals(takeOfferTab) && takeOfferView != null) { takeOfferView.onTabSelected(true); + } else if (newValue.equals(takeOfferTab) && bsqSwapTakeOfferView != null) { + bsqSwapTakeOfferView.onTabSelected(true); } else if (newValue.equals(offerBookTab) && offerBookView != null) { offerBookView.onTabSelected(true); } @@ -111,8 +121,12 @@ public abstract class OfferView extends ActivatableView { if (oldValue != null) { if (oldValue.equals(createOfferTab) && createOfferView != null) { createOfferView.onTabSelected(false); + } else if (oldValue.equals(createOfferTab) && bsqSwapCreateOfferView != null) { + bsqSwapCreateOfferView.onTabSelected(false); } else if (oldValue.equals(takeOfferTab) && takeOfferView != null) { takeOfferView.onTabSelected(false); + } else if (oldValue.equals(takeOfferTab) && bsqSwapTakeOfferView != null) { + bsqSwapTakeOfferView.onTabSelected(false); } else if (oldValue.equals(offerBookTab) && offerBookView != null) { offerBookView.onTabSelected(false); } @@ -128,11 +142,33 @@ public abstract class OfferView extends ActivatableView { onTakeOfferViewRemoved(); } }; + + offerActionHandler = new OfferActionHandler() { + @Override + public void onCreateOffer(TradeCurrency tradeCurrency, PaymentMethod paymentMethod) { + if (createOfferViewOpen) { + root.getTabs().remove(createOfferTab); + } + if (canCreateOrTakeOffer(tradeCurrency)) { + openCreateOffer(tradeCurrency, paymentMethod); + } + } + + @Override + public void onTakeOffer(Offer offer) { + if (takeOfferViewOpen) { + root.getTabs().remove(takeOfferTab); + } + if (canCreateOrTakeOffer(CurrencyUtil.getTradeCurrency(offer.getCurrencyCode()).get())) { + openTakeOffer(offer); + } + } + }; } @Override protected void activate() { - Optional tradeCurrencyOptional = (this.direction == OfferPayload.Direction.SELL) ? + Optional tradeCurrencyOptional = (this.direction == OfferDirection.SELL) ? CurrencyUtil.getTradeCurrency(preferences.getSellScreenCurrencyCode()) : CurrencyUtil.getTradeCurrency(preferences.getBuyScreenCurrencyCode()); tradeCurrency = tradeCurrencyOptional.orElseGet(GlobalSettings::getDefaultTradeCurrency); @@ -150,8 +186,10 @@ public abstract class OfferView extends ActivatableView { root.getTabs().removeListener(tabListChangeListener); } - private String getCreateOfferTabName() { - return Res.get("offerbook.createOffer").toUpperCase(); + private String getCreateOfferTabName(Class viewClass) { + return viewClass == BsqSwapCreateOfferView.class ? + Res.get("offerbook.bsqSwap.createOffer").toUpperCase() : + Res.get("offerbook.createOffer").toUpperCase(); } private String getTakeOfferTabName() { @@ -162,7 +200,7 @@ public abstract class OfferView extends ActivatableView { TabPane tabPane = root; tabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.ALL_TABS); View view; - boolean isBuy = direction == OfferPayload.Direction.BUY; + boolean isBuy = direction == OfferDirection.BUY; if (viewClass == OfferBookView.class && offerBookView == null) { view = viewLoader.load(viewClass); @@ -173,28 +211,6 @@ public abstract class OfferView extends ActivatableView { tabPane.getTabs().add(offerBookTab); offerBookView = (OfferBookView) view; offerBookView.onTabSelected(true); - - OfferActionHandler offerActionHandler = new OfferActionHandler() { - @Override - public void onCreateOffer(TradeCurrency tradeCurrency) { - if (createOfferViewOpen) { - tabPane.getTabs().remove(createOfferTab); - } - if (canCreateOrTakeOffer()) { - openCreateOffer(tradeCurrency); - } - } - - @Override - public void onTakeOffer(Offer offer) { - if (takeOfferViewOpen) { - tabPane.getTabs().remove(takeOfferTab); - } - if (canCreateOrTakeOffer()) { - openTakeOffer(offer); - } - } - }; offerBookView.setOfferActionHandler(offerActionHandler); offerBookView.setDirection(direction); } else if (viewClass == CreateOfferView.class && createOfferView == null) { @@ -202,15 +218,29 @@ public abstract class OfferView extends ActivatableView { // CreateOffer and TakeOffer must not be cached by ViewLoader as we cannot use a view multiple times // in different graphs createOfferView = (CreateOfferView) view; - createOfferView.initWithData(direction, tradeCurrency); + createOfferView.initWithData(direction, tradeCurrency, offerActionHandler); createOfferPane = createOfferView.getRoot(); - createOfferTab = new Tab(getCreateOfferTabName()); + createOfferTab = new Tab(getCreateOfferTabName(viewClass)); createOfferTab.setClosable(true); // close handler from close on create offer action createOfferView.setCloseHandler(() -> tabPane.getTabs().remove(createOfferTab)); createOfferTab.setContent(createOfferPane); tabPane.getTabs().add(createOfferTab); tabPane.getSelectionModel().select(createOfferTab); + createOfferViewOpen = true; + } else if (viewClass == BsqSwapCreateOfferView.class && bsqSwapCreateOfferView == null) { + view = viewLoader.load(viewClass); + bsqSwapCreateOfferView = (BsqSwapCreateOfferView) view; + bsqSwapCreateOfferView.initWithData(direction, offerActionHandler); + createOfferPane = bsqSwapCreateOfferView.getRoot(); + createOfferTab = new Tab(getCreateOfferTabName(viewClass)); + createOfferTab.setClosable(true); + // close handler from close on create offer action + bsqSwapCreateOfferView.setCloseHandler(() -> tabPane.getTabs().remove(createOfferTab)); + createOfferTab.setContent(createOfferPane); + tabPane.getTabs().add(createOfferTab); + tabPane.getSelectionModel().select(createOfferTab); + createOfferViewOpen = true; } else if (viewClass == TakeOfferView.class && takeOfferView == null && offer != null) { view = viewLoader.load(viewClass); // CreateOffer and TakeOffer must not be cached by ViewLoader as we cannot use a view multiple times @@ -225,12 +255,26 @@ public abstract class OfferView extends ActivatableView { takeOfferTab.setContent(takeOfferPane); tabPane.getTabs().add(takeOfferTab); tabPane.getSelectionModel().select(takeOfferTab); + } else if (viewClass == BsqSwapTakeOfferView.class && takeOfferView == null && offer != null) { + view = viewLoader.load(viewClass); + // CreateOffer and TakeOffer must not be cached by ViewLoader as we cannot use a view multiple times + // in different graphs + bsqSwapTakeOfferView = (BsqSwapTakeOfferView) view; + bsqSwapTakeOfferView.initWithData(offer); + takeOfferPane = bsqSwapTakeOfferView.getRoot(); + takeOfferTab = new Tab(getTakeOfferTabName()); + takeOfferTab.setClosable(true); + // close handler from close on take offer action + bsqSwapTakeOfferView.setCloseHandler(() -> tabPane.getTabs().remove(takeOfferTab)); + takeOfferTab.setContent(takeOfferPane); + tabPane.getTabs().add(takeOfferTab); + tabPane.getSelectionModel().select(takeOfferTab); } } - protected boolean canCreateOrTakeOffer() { + protected boolean canCreateOrTakeOffer(TradeCurrency tradeCurrency) { return GUIUtil.isBootstrappedOrShowPopup(p2PService) && - GUIUtil.canCreateOrTakeOfferOrShowPopup(user, navigation); + GUIUtil.canCreateOrTakeOfferOrShowPopup(user, navigation, tradeCurrency); } private void showNoArbitratorForUserLocaleWarning() { @@ -246,20 +290,28 @@ public abstract class OfferView extends ActivatableView { return arbitratorManager.getObservableMap().values().stream() .flatMap(arbitrator -> arbitrator.getLanguageCodes().stream()) .distinct() - .map(languageCode -> LanguageUtil.getDisplayName(languageCode)) + .map(LanguageUtil::getDisplayName) .collect(Collectors.joining(", ")); } private void openTakeOffer(Offer offer) { - OfferView.this.takeOfferViewOpen = true; - OfferView.this.offer = offer; - OfferView.this.navigation.navigateTo(MainView.class, OfferView.this.getClass(), TakeOfferView.class); + takeOfferViewOpen = true; + this.offer = offer; + if (offer.isBsqSwapOffer()) { + navigation.navigateTo(MainView.class, this.getClass(), BsqSwapTakeOfferView.class); + } else { + navigation.navigateTo(MainView.class, this.getClass(), TakeOfferView.class); + } } - private void openCreateOffer(TradeCurrency tradeCurrency) { - OfferView.this.createOfferViewOpen = true; - OfferView.this.tradeCurrency = tradeCurrency; - OfferView.this.navigation.navigateTo(MainView.class, OfferView.this.getClass(), CreateOfferView.class); + private void openCreateOffer(TradeCurrency tradeCurrency, PaymentMethod paymentMethod) { + createOfferViewOpen = true; + this.tradeCurrency = tradeCurrency; + if (tradeCurrency.getCode().equals("BSQ") && paymentMethod.isBsqSwap()) { + navigation.navigateTo(MainView.class, this.getClass(), BsqSwapCreateOfferView.class); + } else { + navigation.navigateTo(MainView.class, this.getClass(), CreateOfferView.class); + } } private void onCreateOfferViewRemoved() { @@ -268,6 +320,9 @@ public abstract class OfferView extends ActivatableView { createOfferView.onClose(); createOfferView = null; } + if (bsqSwapCreateOfferView != null) { + bsqSwapCreateOfferView = null; + } offerBookView.enableCreateOfferButton(); navigation.navigateTo(MainView.class, this.getClass(), OfferBookView.class); @@ -280,12 +335,15 @@ public abstract class OfferView extends ActivatableView { takeOfferView.onClose(); takeOfferView = null; } + if (bsqSwapTakeOfferView != null) { + bsqSwapTakeOfferView = null; + } navigation.navigateTo(MainView.class, this.getClass(), OfferBookView.class); } public interface OfferActionHandler { - void onCreateOffer(TradeCurrency tradeCurrency); + void onCreateOffer(TradeCurrency tradeCurrency, PaymentMethod paymentMethod); void onTakeOffer(Offer offer); } diff --git a/desktop/src/main/java/bisq/desktop/main/offer/SellOfferView.java b/desktop/src/main/java/bisq/desktop/main/offer/SellOfferView.java index d88fb8c9dd..bbd1617814 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/SellOfferView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/SellOfferView.java @@ -21,7 +21,7 @@ import bisq.desktop.Navigation; import bisq.desktop.common.view.FxmlView; import bisq.desktop.common.view.ViewLoader; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferDirection; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import bisq.core.user.Preferences; import bisq.core.user.User; @@ -46,6 +46,6 @@ public class SellOfferView extends OfferView { arbitratorManager, user, p2PService, - OfferPayload.Direction.SELL); + OfferDirection.SELL); } } diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/MutableOfferDataModel.java similarity index 95% rename from desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java rename to desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/MutableOfferDataModel.java index 3f2a733168..3321e64707 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/MutableOfferDataModel.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.desktop.main.offer; +package bisq.desktop.main.offer.bisq_v1; import bisq.desktop.Navigation; import bisq.desktop.util.DisplayUtils; @@ -33,15 +33,15 @@ import bisq.core.locale.CurrencyUtil; import bisq.core.locale.TradeCurrency; import bisq.core.monetary.Price; import bisq.core.monetary.Volume; -import bisq.core.offer.CreateOfferService; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferDirection; import bisq.core.offer.OfferUtil; import bisq.core.offer.OpenOfferManager; +import bisq.core.offer.bisq_v1.CreateOfferService; import bisq.core.payment.PaymentAccount; import bisq.core.provider.fee.FeeService; import bisq.core.provider.price.PriceFeedService; -import bisq.core.trade.handlers.TransactionResultHandler; +import bisq.core.trade.bisq_v1.TransactionResultHandler; import bisq.core.trade.statistics.TradeStatistics3; import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.user.Preferences; @@ -53,6 +53,7 @@ import bisq.core.util.coin.CoinUtil; import bisq.network.p2p.P2PService; +import bisq.common.app.DevEnv; import bisq.common.util.MathUtils; import bisq.common.util.Tuple2; import bisq.common.util.Utilities; @@ -60,8 +61,6 @@ import bisq.common.util.Utilities; import org.bitcoinj.core.Coin; import org.bitcoinj.core.Transaction; -import com.google.inject.Inject; - import javax.inject.Named; import javafx.beans.property.BooleanProperty; @@ -84,7 +83,9 @@ import javafx.collections.SetChangeListener; import java.util.Comparator; import java.util.Date; import java.util.HashSet; +import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -113,7 +114,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs private final BalanceListener btcBalanceListener; private final SetChangeListener paymentAccountsChangeListener; - protected OfferPayload.Direction direction; + protected OfferDirection direction; protected TradeCurrency tradeCurrency; protected final StringProperty tradeCurrencyCode = new SimpleStringProperty(); protected final BooleanProperty useMarketBasedPrice = new SimpleBooleanProperty(); @@ -149,7 +150,6 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs // Constructor, lifecycle /////////////////////////////////////////////////////////////////////////////////////////// - @Inject public MutableOfferDataModel(CreateOfferService createOfferService, OpenOfferManager openOfferManager, OfferUtil offerUtil, @@ -179,7 +179,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs this.navigation = navigation; this.tradeStatisticsManager = tradeStatisticsManager; - offerId = createOfferService.getRandomOfferId(); + offerId = OfferUtil.getRandomOfferId(); shortOfferId = Utilities.getShortId(offerId); addressEntry = btcWalletService.getOrCreateAddressEntry(offerId, AddressEntry.Context.OFFER_FUNDING); @@ -229,7 +229,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs /////////////////////////////////////////////////////////////////////////////////////////// // called before activate() - public boolean initWithData(OfferPayload.Direction direction, TradeCurrency tradeCurrency) { + public boolean initWithData(OfferDirection direction, TradeCurrency tradeCurrency) { this.direction = direction; this.tradeCurrency = tradeCurrency; @@ -352,8 +352,8 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs return; } // Get average historic prices over for the prior trade period equaling the lock time - var blocksRange = Restrictions.getLockTime(paymentAccount.getPaymentMethod().isAsset()); - var startDate = new Date(System.currentTimeMillis() - blocksRange * 10 * 60000); + var blocksRange = Restrictions.getLockTime(paymentAccount.getPaymentMethod().isBlockchain()); + var startDate = new Date(System.currentTimeMillis() - blocksRange * 10L * 60000); var sortedRangeData = tradeStatisticsManager.getObservableTradeStatisticsSet().stream() .filter(e -> e.getCurrency().equals(getTradeCurrency().getCode())) .filter(e -> e.getDate().compareTo(startDate) >= 0) @@ -423,7 +423,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs } @Override - public void onUpdateBalances(Coin availableConfirmedBalance, + public void onUpdateBalances(Coin availableBalance, Coin availableNonBsqBalance, Coin unverifiedBalance, Coin unconfirmedChangeBalance, @@ -471,16 +471,16 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs return true; } - OfferPayload.Direction getDirection() { + OfferDirection getDirection() { return direction; } boolean isSellOffer() { - return direction == OfferPayload.Direction.SELL; + return direction == OfferDirection.SELL; } boolean isBuyOffer() { - return direction == OfferPayload.Direction.BUY; + return direction == OfferDirection.BUY; } AddressEntry getAddressEntry() { @@ -627,11 +627,20 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs } private void fillPaymentAccounts() { - if (user.getPaymentAccounts() != null) - paymentAccounts.setAll(new HashSet<>(user.getPaymentAccounts())); + paymentAccounts.setAll(new HashSet<>(getUserPaymentAccounts())); paymentAccounts.sort(comparing(PaymentAccount::getAccountName)); } + private Set getUserPaymentAccounts() { + return Objects.requireNonNull(user.getPaymentAccounts()).stream() + .filter(this::isNotBsqSwapOrDaoActivated) + .collect(Collectors.toSet()); + } + + private boolean isNotBsqSwapOrDaoActivated(PaymentAccount paymentAccount) { + return !paymentAccount.getPaymentMethod().isBsqSwap() || DevEnv.isDaoActivated(); + } + protected void setAmount(Coin amount) { this.amount.set(amount); } @@ -648,6 +657,11 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs this.buyerSecurityDeposit.set(value); } + void resetAddressEntry() { + btcWalletService.resetAddressEntriesForOpenOffer(offerId); + } + + /////////////////////////////////////////////////////////////////////////////////////////// // Getters /////////////////////////////////////////////////////////////////////////////////////////// @@ -769,7 +783,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs boolean canPlaceOffer() { return GUIUtil.isBootstrappedOrShowPopup(p2PService) && - GUIUtil.canCreateOrTakeOfferOrShowPopup(user, navigation); + GUIUtil.canCreateOrTakeOfferOrShowPopup(user, navigation, tradeCurrency); } public boolean isMinBuyerSecurityDeposit() { diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferView.java b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/MutableOfferView.java similarity index 98% rename from desktop/src/main/java/bisq/desktop/main/offer/MutableOfferView.java rename to desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/MutableOfferView.java index f0f42d5e5d..07a86f35e4 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/MutableOfferView.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.desktop.main.offer; +package bisq.desktop.main.offer.bisq_v1; import bisq.desktop.Navigation; import bisq.desktop.common.view.ActivatableViewAndModel; @@ -35,6 +35,7 @@ import bisq.desktop.main.account.content.fiataccounts.FiatAccountsView; import bisq.desktop.main.dao.DaoView; import bisq.desktop.main.dao.wallet.BsqWalletView; import bisq.desktop.main.dao.wallet.receive.BsqReceiveView; +import bisq.desktop.main.offer.OfferView; import bisq.desktop.main.overlays.notifications.Notification; import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.main.overlays.windows.OfferDetailsWindow; @@ -48,7 +49,7 @@ import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; import bisq.core.locale.TradeCurrency; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferDirection; import bisq.core.payment.FasterPaymentsAccount; import bisq.core.payment.PaymentAccount; import bisq.core.payment.payload.PaymentMethod; @@ -116,6 +117,8 @@ import java.util.HashMap; import java.util.List; import java.util.concurrent.TimeUnit; +import lombok.Setter; + import org.jetbrains.annotations.NotNull; import static bisq.core.payment.payload.PaymentMethod.HAL_CASH_ID; @@ -170,13 +173,16 @@ public abstract class MutableOfferView> exten protected int gridRow = 0; private final List editOfferElements = new ArrayList<>(); - private HashMap paymentAccountWarningDisplayed = new HashMap<>(); + private final HashMap paymentAccountWarningDisplayed = new HashMap<>(); private boolean clearXchangeWarningDisplayed, fasterPaymentsWarningDisplayed, isActivated; private InfoInputTextField marketBasedPriceInfoInputTextField, volumeInfoInputTextField, buyerSecurityDepositInfoInputTextField, triggerPriceInfoInputTextField; private AutoTooltipSlideToggleButton tradeFeeInBtcToggle, tradeFeeInBsqToggle; private Text xIcon, fakeXIcon; + @Setter + private OfferView.OfferActionHandler offerActionHandler; + /////////////////////////////////////////////////////////////////////////////////////////// // Constructor, lifecycle @@ -290,16 +296,20 @@ public abstract class MutableOfferView> exten /////////////////////////////////////////////////////////////////////////////////////////// public void onTabSelected(boolean isSelected) { - if (isSelected && !model.getDataModel().isTabSelected) + if (isSelected && !model.getDataModel().isTabSelected) { doActivate(); - else + } else { deactivate(); + } isActivated = isSelected; model.getDataModel().onTabSelected(isSelected); } - public void initWithData(OfferPayload.Direction direction, TradeCurrency tradeCurrency) { + public void initWithData(OfferDirection direction, TradeCurrency tradeCurrency, + OfferView.OfferActionHandler offerActionHandler) { + this.offerActionHandler = offerActionHandler; + boolean result = model.initWithData(direction, tradeCurrency); if (!result) { @@ -312,7 +322,7 @@ public abstract class MutableOfferView> exten }).show(); } - if (direction == OfferPayload.Direction.BUY) { + if (direction == OfferDirection.BUY) { placeOfferButton.setId("buy-button-big"); placeOfferButton.updateText(Res.get("createOffer.placeOfferButton", Res.get("shared.buy"))); } else { @@ -348,7 +358,7 @@ public abstract class MutableOfferView> exten Offer offer = model.createAndGetOffer(); if (!DevEnv.isDevMode()) { offerDetailsWindow.onPlaceOffer(() -> - model.onPlaceOffer(offer, offerDetailsWindow::hide)) + model.onPlaceOffer(offer, offerDetailsWindow::hide)) .show(offer); } else { balanceSubscription.unsubscribe(); @@ -524,6 +534,18 @@ public abstract class MutableOfferView> exten PaymentAccount paymentAccount = paymentAccountsComboBox.getSelectionModel().getSelectedItem(); if (paymentAccount != null) { + // We represent BSQ swaps as payment method and switch to a new view if it is selected + if (paymentAccount.getPaymentMethod().isBsqSwap()) { + model.dataModel.resetAddressEntry(); + close(); + + if (offerActionHandler != null) { + offerActionHandler.onCreateOffer(paymentAccount.getSelectedTradeCurrency(), + paymentAccount.getPaymentMethod()); + } + return; + } + maybeShowClearXchangeWarning(paymentAccount); maybeShowFasterPaymentsWarning(paymentAccount); maybeShowAccountWarning(paymentAccount, model.getDataModel().isBuyOffer()); @@ -1524,8 +1546,8 @@ public abstract class MutableOfferView> exten addPayInfoEntry(infoGridPane, i++, Res.getWithCol("shared.tradeAmount"), model.tradeAmount.get()); addPayInfoEntry(infoGridPane, i++, Res.getWithCol("shared.yourSecurityDeposit"), model.getSecurityDepositInfo()); - addPayInfoEntry(infoGridPane, i++, Res.get("createOffer.fundsBox.offerFee"), model.getTradeFee()); - addPayInfoEntry(infoGridPane, i++, Res.get("createOffer.fundsBox.networkFee"), model.getTxFee()); + addPayInfoEntry(infoGridPane, i++, Res.getWithCol("createOffer.fundsBox.offerFee"), model.getTradeFee()); + addPayInfoEntry(infoGridPane, i++, Res.getWithCol("createOffer.fundsBox.networkFee"), model.getTxFee()); Separator separator = new Separator(); separator.setOrientation(Orientation.HORIZONTAL); separator.getStyleClass().add("offer-separator"); diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/MutableOfferViewModel.java similarity index 96% rename from desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java rename to desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/MutableOfferViewModel.java index 4e7ea068e3..344966a016 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/MutableOfferViewModel.java @@ -15,12 +15,11 @@ * along with Bisq. If not, see . */ -package bisq.desktop.main.offer; +package bisq.desktop.main.offer.bisq_v1; import bisq.desktop.Navigation; import bisq.desktop.common.model.ActivatableWithDataModel; import bisq.desktop.main.MainView; -import bisq.desktop.main.PriceUtil; import bisq.desktop.main.funds.FundsView; import bisq.desktop.main.funds.deposit.DepositView; import bisq.desktop.main.overlays.popups.Popup; @@ -28,12 +27,9 @@ import bisq.desktop.main.settings.SettingsView; import bisq.desktop.main.settings.preferences.PreferencesView; import bisq.desktop.util.DisplayUtils; import bisq.desktop.util.GUIUtil; -import bisq.desktop.util.validation.AltcoinValidator; import bisq.desktop.util.validation.BsqValidator; import bisq.desktop.util.validation.BtcValidator; -import bisq.desktop.util.validation.FiatPriceValidator; import bisq.desktop.util.validation.FiatVolumeValidator; -import bisq.desktop.util.validation.MonetaryValidator; import bisq.desktop.util.validation.SecurityDepositValidator; import bisq.core.account.witness.AccountAgeWitnessService; @@ -45,7 +41,7 @@ import bisq.core.monetary.Altcoin; import bisq.core.monetary.Price; import bisq.core.monetary.Volume; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferDirection; import bisq.core.offer.OfferRestrictions; import bisq.core.offer.OfferUtil; import bisq.core.payment.PaymentAccount; @@ -56,11 +52,15 @@ import bisq.core.provider.price.PriceFeedService; import bisq.core.user.Preferences; import bisq.core.util.FormattingUtils; import bisq.core.util.ParsingUtils; +import bisq.core.util.PriceUtil; import bisq.core.util.VolumeUtil; import bisq.core.util.coin.BsqFormatter; import bisq.core.util.coin.CoinFormatter; import bisq.core.util.coin.CoinUtil; +import bisq.core.util.validation.AltcoinValidator; +import bisq.core.util.validation.FiatPriceValidator; import bisq.core.util.validation.InputValidator; +import bisq.core.util.validation.MonetaryValidator; import bisq.common.Timer; import bisq.common.UserThread; @@ -70,7 +70,6 @@ import bisq.common.util.MathUtils; import org.bitcoinj.core.Coin; import org.bitcoinj.utils.Fiat; -import javax.inject.Inject; import javax.inject.Named; import javafx.scene.control.ComboBox; @@ -194,7 +193,6 @@ public abstract class MutableOfferViewModel ext // Constructor, lifecycle /////////////////////////////////////////////////////////////////////////////////////////// - @Inject public MutableOfferViewModel(M dataModel, FiatVolumeValidator fiatVolumeValidator, FiatPriceValidator fiatPriceValidator, @@ -268,7 +266,7 @@ public abstract class MutableOfferViewModel ext } private void addBindings() { - if (dataModel.getDirection() == OfferPayload.Direction.BUY) { + if (dataModel.getDirection() == OfferDirection.BUY) { volumeDescriptionLabel.bind(createStringBinding( () -> Res.get("createOffer.amountPriceBox.buy.volumeDescription", dataModel.getTradeCurrencyCode().get()), dataModel.getTradeCurrencyCode())); @@ -337,9 +335,9 @@ public abstract class MutableOfferViewModel ext try { double priceAsDouble = ParsingUtils.parseNumberStringToDouble(price.get()); double relation = priceAsDouble / marketPriceAsDouble; - final OfferPayload.Direction compareDirection = CurrencyUtil.isCryptoCurrency(currencyCode) ? - OfferPayload.Direction.SELL : - OfferPayload.Direction.BUY; + final OfferDirection compareDirection = CurrencyUtil.isCryptoCurrency(currencyCode) ? + OfferDirection.SELL : + OfferDirection.BUY; double percentage = dataModel.getDirection() == compareDirection ? 1 - relation : relation - 1; percentage = MathUtils.roundDouble(percentage, 4); dataModel.setMarketPriceMargin(percentage); @@ -373,9 +371,9 @@ public abstract class MutableOfferViewModel ext percentage = MathUtils.roundDouble(percentage, 4); double marketPriceAsDouble = marketPrice.getPrice(); final boolean isCryptoCurrency = CurrencyUtil.isCryptoCurrency(currencyCode); - final OfferPayload.Direction compareDirection = isCryptoCurrency ? - OfferPayload.Direction.SELL : - OfferPayload.Direction.BUY; + final OfferDirection compareDirection = isCryptoCurrency ? + OfferDirection.SELL : + OfferDirection.BUY; double factor = dataModel.getDirection() == compareDirection ? 1 - percentage : 1 + percentage; @@ -512,11 +510,11 @@ public abstract class MutableOfferViewModel ext isTradeFeeVisible.setValue(true); tradeFee.set(getFormatterForMakerFee().formatCoin(makerFeeAsCoin)); - tradeFeeInBtcWithFiat.set(FeeUtil.getTradeFeeWithFiatEquivalent(offerUtil, + tradeFeeInBtcWithFiat.set(OfferViewModelUtil.getTradeFeeWithFiatEquivalent(offerUtil, dataModel.getMakerFeeInBtc(), true, btcFormatter)); - tradeFeeInBsqWithFiat.set(FeeUtil.getTradeFeeWithFiatEquivalent(offerUtil, + tradeFeeInBsqWithFiat.set(OfferViewModelUtil.getTradeFeeWithFiatEquivalent(offerUtil, dataModel.getMakerFeeInBsq(), false, bsqFormatter)); @@ -583,14 +581,14 @@ public abstract class MutableOfferViewModel ext // API /////////////////////////////////////////////////////////////////////////////////////////// - boolean initWithData(OfferPayload.Direction direction, TradeCurrency tradeCurrency) { + boolean initWithData(OfferDirection direction, TradeCurrency tradeCurrency) { boolean result = dataModel.initWithData(direction, tradeCurrency); if (dataModel.paymentAccount != null) btcValidator.setMaxValue(dataModel.paymentAccount.getPaymentMethod().getMaxTradeLimitAsCoin(dataModel.getTradeCurrencyCode().get())); btcValidator.setMaxTradeLimit(Coin.valueOf(dataModel.getMaxTradeLimit())); btcValidator.setMinValue(Restrictions.getMinTradeAmount()); - final boolean isBuy = dataModel.getDirection() == OfferPayload.Direction.BUY; + final boolean isBuy = dataModel.getDirection() == OfferDirection.BUY; amountDescription = Res.get("createOffer.amountPriceBox.amountDescription", isBuy ? Res.get("shared.buy") : Res.get("shared.sell")); @@ -983,7 +981,7 @@ public abstract class MutableOfferViewModel ext } public boolean isSellOffer() { - return dataModel.getDirection() == OfferPayload.Direction.SELL; + return dataModel.getDirection() == OfferDirection.SELL; } public TradeCurrency getTradeCurrency() { @@ -991,7 +989,7 @@ public abstract class MutableOfferViewModel ext } public String getTradeAmount() { - return FeeUtil.getTradeFeeWithFiatEquivalent(offerUtil, + return OfferViewModelUtil.getTradeFeeWithFiatEquivalent(offerUtil, dataModel.getAmount().get(), true, btcFormatter); @@ -1008,7 +1006,7 @@ public abstract class MutableOfferViewModel ext } public String getSecurityDepositInfo() { - return FeeUtil.getTradeFeeWithFiatEquivalentAndPercentage(offerUtil, + return OfferViewModelUtil.getTradeFeeWithFiatEquivalentAndPercentage(offerUtil, dataModel.getSecurityDeposit(), dataModel.getAmount().get(), true, @@ -1024,7 +1022,7 @@ public abstract class MutableOfferViewModel ext public String getTradeFee() { if (dataModel.isCurrencyForMakerFeeBtc()) { - return FeeUtil.getTradeFeeWithFiatEquivalentAndPercentage(offerUtil, + return OfferViewModelUtil.getTradeFeeWithFiatEquivalentAndPercentage(offerUtil, dataModel.getMakerFeeInBtc(), dataModel.getAmount().get(), true, @@ -1033,7 +1031,7 @@ public abstract class MutableOfferViewModel ext } else { // For BSQ we use the fiat equivalent only. Calculating the % value would require to // calculate the BTC value of the BSQ fee and use that... - return FeeUtil.getTradeFeeWithFiatEquivalent(offerUtil, + return OfferViewModelUtil.getTradeFeeWithFiatEquivalent(offerUtil, dataModel.getMakerFeeInBsq(), false, bsqFormatter); @@ -1050,12 +1048,12 @@ public abstract class MutableOfferViewModel ext public String getTotalToPayInfo() { if (dataModel.isCurrencyForMakerFeeBtc()) { - return FeeUtil.getTradeFeeWithFiatEquivalent(offerUtil, + return OfferViewModelUtil.getTradeFeeWithFiatEquivalent(offerUtil, dataModel.totalToPayAsCoin.get(), true, btcFormatter); } else { - return FeeUtil.getTradeFeeWithFiatEquivalent(offerUtil, + return OfferViewModelUtil.getTradeFeeWithFiatEquivalent(offerUtil, dataModel.totalToPayAsCoin.get(), true, btcFormatter) + " + " + getTradeFee(); @@ -1075,7 +1073,7 @@ public abstract class MutableOfferViewModel ext } public String getTxFee() { - return FeeUtil.getTradeFeeWithFiatEquivalentAndPercentage(offerUtil, + return OfferViewModelUtil.getTradeFeeWithFiatEquivalentAndPercentage(offerUtil, dataModel.getTxFee(), dataModel.getAmount().get(), true, @@ -1235,7 +1233,7 @@ public abstract class MutableOfferViewModel ext private void maybeShowMakeOfferToUnsignedAccountWarning() { if (!makeOfferFromUnsignedAccountWarningDisplayed && - dataModel.getDirection() == OfferPayload.Direction.SELL && + dataModel.getDirection() == OfferDirection.SELL && PaymentMethod.hasChargebackRisk(dataModel.getPaymentAccount().getPaymentMethod(), dataModel.getTradeCurrency().getCode())) { Coin checkAmount = dataModel.getMinAmount().get() == null ? dataModel.getAmount().get() : dataModel.getMinAmount().get(); if (checkAmount != null && !checkAmount.isGreaterThan(OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT)) { diff --git a/desktop/src/main/java/bisq/desktop/main/offer/OfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/OfferDataModel.java similarity index 98% rename from desktop/src/main/java/bisq/desktop/main/offer/OfferDataModel.java rename to desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/OfferDataModel.java index c502050751..15320c7760 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/OfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/OfferDataModel.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.desktop.main.offer; +package bisq.desktop.main.offer.bisq_v1; import bisq.desktop.common.model.ActivatableDataModel; diff --git a/desktop/src/main/java/bisq/desktop/main/offer/FeeUtil.java b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/OfferViewModelUtil.java similarity index 92% rename from desktop/src/main/java/bisq/desktop/main/offer/FeeUtil.java rename to desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/OfferViewModelUtil.java index 92698c91ba..6ce76791dd 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/FeeUtil.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/OfferViewModelUtil.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.desktop.main.offer; +package bisq.desktop.main.offer.bisq_v1; import bisq.desktop.util.DisplayUtils; import bisq.desktop.util.GUIUtil; @@ -32,7 +32,8 @@ import org.bitcoinj.core.Coin; import java.util.Optional; -public class FeeUtil { +// Shared utils for ViewModels +public class OfferViewModelUtil { public static String getTradeFeeWithFiatEquivalent(OfferUtil offerUtil, Coin tradeFee, boolean isCurrencyForMakerFeeBtc, @@ -66,15 +67,15 @@ public class FeeUtil { " " + Res.get("guiUtil.ofTradeAmount"); } return offerUtil.getFeeInUserFiatCurrency(tradeFee, - isCurrencyForMakerFeeBtc, - formatter) + isCurrencyForMakerFeeBtc, + formatter) .map(VolumeUtil::formatAverageVolumeWithCode) .map(feeInFiat -> Res.get("feeOptionWindow.btcFeeWithFiatAndPercentage", feeAsBtc, feeInFiat, percentage)) .orElseGet(() -> Res.get("feeOptionWindow.btcFeeWithPercentage", feeAsBtc, percentage)); } else { // For BSQ we use the fiat equivalent only. Calculating the % value would be more effort. // We could calculate the BTC value if the BSQ fee and use that... - return FeeUtil.getTradeFeeWithFiatEquivalent(offerUtil, + return OfferViewModelUtil.getTradeFeeWithFiatEquivalent(offerUtil, tradeFee, false, formatter); diff --git a/desktop/src/main/java/bisq/desktop/main/offer/OfferViewUtil.java b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/OfferViewUtil.java similarity index 89% rename from desktop/src/main/java/bisq/desktop/main/offer/OfferViewUtil.java rename to desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/OfferViewUtil.java index dfd6a37580..f5aff0937d 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/OfferViewUtil.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/OfferViewUtil.java @@ -15,15 +15,13 @@ * along with Bisq. If not, see . */ -package bisq.desktop.main.offer; +package bisq.desktop.main.offer.bisq_v1; import javafx.scene.control.Label; import javafx.geometry.Insets; -/** - * Reusable methods for CreateOfferView, TakeOfferView or other related views - */ +// Shared utils for Views public class OfferViewUtil { public static Label createPopOverLabel(String text) { final Label label = new Label(text); diff --git a/desktop/src/main/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/createoffer/CreateOfferDataModel.java similarity index 94% rename from desktop/src/main/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModel.java rename to desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/createoffer/CreateOfferDataModel.java index 0bec3dcfd8..0b7e4423c8 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/createoffer/CreateOfferDataModel.java @@ -19,17 +19,17 @@ but WITHOUT see . */ -package bisq.desktop.main.offer.createoffer; +package bisq.desktop.main.offer.bisq_v1.createoffer; import bisq.desktop.Navigation; -import bisq.desktop.main.offer.MutableOfferDataModel; +import bisq.desktop.main.offer.bisq_v1.MutableOfferDataModel; import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.btc.wallet.BsqWalletService; import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.offer.CreateOfferService; import bisq.core.offer.OfferUtil; import bisq.core.offer.OpenOfferManager; +import bisq.core.offer.bisq_v1.CreateOfferService; import bisq.core.provider.fee.FeeService; import bisq.core.provider.price.PriceFeedService; import bisq.core.trade.statistics.TradeStatisticsManager; diff --git a/desktop/src/main/java/bisq/desktop/main/offer/createoffer/CreateOfferView.fxml b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/createoffer/CreateOfferView.fxml similarity index 96% rename from desktop/src/main/java/bisq/desktop/main/offer/createoffer/CreateOfferView.fxml rename to desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/createoffer/CreateOfferView.fxml index 4d419ff1f9..679aefc988 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/createoffer/CreateOfferView.fxml +++ b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/createoffer/CreateOfferView.fxml @@ -18,6 +18,6 @@ --> - diff --git a/desktop/src/main/java/bisq/desktop/main/offer/createoffer/CreateOfferView.java b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/createoffer/CreateOfferView.java similarity index 93% rename from desktop/src/main/java/bisq/desktop/main/offer/createoffer/CreateOfferView.java rename to desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/createoffer/CreateOfferView.java index 1f326ebc71..0ad84c2c00 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/createoffer/CreateOfferView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/createoffer/CreateOfferView.java @@ -15,11 +15,11 @@ * along with Bisq. If not, see . */ -package bisq.desktop.main.offer.createoffer; +package bisq.desktop.main.offer.bisq_v1.createoffer; import bisq.desktop.Navigation; import bisq.desktop.common.view.FxmlView; -import bisq.desktop.main.offer.MutableOfferView; +import bisq.desktop.main.offer.bisq_v1.MutableOfferView; import bisq.desktop.main.overlays.windows.OfferDetailsWindow; import bisq.core.user.Preferences; @@ -33,7 +33,6 @@ import javax.inject.Named; @FxmlView public class CreateOfferView extends MutableOfferView { - @Inject private CreateOfferView(CreateOfferViewModel model, Navigation navigation, diff --git a/desktop/src/main/java/bisq/desktop/main/offer/createoffer/CreateOfferViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/createoffer/CreateOfferViewModel.java similarity index 92% rename from desktop/src/main/java/bisq/desktop/main/offer/createoffer/CreateOfferViewModel.java rename to desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/createoffer/CreateOfferViewModel.java index 62fef3ac7e..7945a20aa8 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/createoffer/CreateOfferViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/createoffer/CreateOfferViewModel.java @@ -15,15 +15,13 @@ * along with Bisq. If not, see . */ -package bisq.desktop.main.offer.createoffer; +package bisq.desktop.main.offer.bisq_v1.createoffer; import bisq.desktop.Navigation; import bisq.desktop.common.model.ViewModel; -import bisq.desktop.main.offer.MutableOfferViewModel; -import bisq.desktop.util.validation.AltcoinValidator; +import bisq.desktop.main.offer.bisq_v1.MutableOfferViewModel; import bisq.desktop.util.validation.BsqValidator; import bisq.desktop.util.validation.BtcValidator; -import bisq.desktop.util.validation.FiatPriceValidator; import bisq.desktop.util.validation.FiatVolumeValidator; import bisq.desktop.util.validation.SecurityDepositValidator; @@ -34,6 +32,8 @@ import bisq.core.user.Preferences; import bisq.core.util.FormattingUtils; import bisq.core.util.coin.BsqFormatter; import bisq.core.util.coin.CoinFormatter; +import bisq.core.util.validation.AltcoinValidator; +import bisq.core.util.validation.FiatPriceValidator; import com.google.inject.Inject; diff --git a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/takeoffer/TakeOfferDataModel.java similarity index 97% rename from desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java rename to desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/takeoffer/TakeOfferDataModel.java index 1e49d56937..c5914b0869 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/takeoffer/TakeOfferDataModel.java @@ -15,10 +15,10 @@ * along with Bisq. If not, see . */ -package bisq.desktop.main.offer.takeoffer; +package bisq.desktop.main.offer.bisq_v1.takeoffer; import bisq.desktop.Navigation; -import bisq.desktop.main.offer.OfferDataModel; +import bisq.desktop.main.offer.bisq_v1.OfferDataModel; import bisq.desktop.main.offer.offerbook.OfferBook; import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.util.GUIUtil; @@ -36,8 +36,9 @@ import bisq.core.locale.Res; import bisq.core.monetary.Price; import bisq.core.monetary.Volume; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferDirection; import bisq.core.offer.OfferUtil; +import bisq.core.offer.bisq_v1.OfferPayload; import bisq.core.payment.PaymentAccount; import bisq.core.payment.PaymentAccountUtil; import bisq.core.payment.payload.PaymentMethod; @@ -45,7 +46,8 @@ import bisq.core.provider.fee.FeeService; import bisq.core.provider.mempool.MempoolService; import bisq.core.provider.price.PriceFeedService; import bisq.core.trade.TradeManager; -import bisq.core.trade.handlers.TradeResultHandler; +import bisq.core.trade.bisq_v1.TradeResultHandler; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.user.Preferences; import bisq.core.user.User; import bisq.core.util.VolumeUtil; @@ -217,7 +219,7 @@ class TakeOfferDataModel extends OfferDataModel { this.amount.set(Coin.valueOf(Math.min(offer.getAmount().value, getMaxTradeLimit()))); - securityDeposit = offer.getDirection() == OfferPayload.Direction.SELL ? + securityDeposit = offer.getDirection() == OfferDirection.SELL ? getBuyerSecurityDeposit() : getSellerSecurityDeposit(); @@ -255,7 +257,8 @@ class TakeOfferDataModel extends OfferDataModel { }); mempoolStatus.setValue(-1); - mempoolService.validateOfferMakerTx(offer.getOfferPayload(), (txValidator -> { + OfferPayload offerPayload = offer.getOfferPayload().orElseThrow(); + mempoolService.validateOfferMakerTx(offerPayload, (txValidator -> { mempoolStatus.setValue(txValidator.isFail() ? 0 : 1); if (txValidator.isFail()) { mempoolStatusText = txValidator.toString(); @@ -312,7 +315,7 @@ class TakeOfferDataModel extends OfferDataModel { // errorMessageHandler is used only in the check availability phase. As soon we have a trade we write the error msg in the trade object as we want to // have it persisted as well. - void onTakeOffer(TradeResultHandler tradeResultHandler) { + void onTakeOffer(TradeResultHandler tradeResultHandler) { checkNotNull(txFeeFromFeeService, "txFeeFromFeeService must not be null"); checkNotNull(getTakerFee(), "takerFee must not be null"); @@ -421,7 +424,7 @@ class TakeOfferDataModel extends OfferDataModel { // Getters /////////////////////////////////////////////////////////////////////////////////////////// - OfferPayload.Direction getDirection() { + OfferDirection getDirection() { return offer.getDirection(); } @@ -460,7 +463,7 @@ class TakeOfferDataModel extends OfferDataModel { } boolean canTakeOffer() { - return GUIUtil.canCreateOrTakeOfferOrShowPopup(user, navigation) && + return GUIUtil.canCreateOrTakeOfferOrShowPopup(user, navigation, paymentAccount.getSelectedTradeCurrency()) && GUIUtil.isBootstrappedOrShowPopup(p2PService); } @@ -525,11 +528,11 @@ class TakeOfferDataModel extends OfferDataModel { } boolean isBuyOffer() { - return getDirection() == OfferPayload.Direction.BUY; + return getDirection() == OfferDirection.BUY; } boolean isSellOffer() { - return getDirection() == OfferPayload.Direction.SELL; + return getDirection() == OfferDirection.SELL; } boolean isCryptoCurrency() { @@ -590,7 +593,6 @@ class TakeOfferDataModel extends OfferDataModel { } boolean wouldCreateDustForMaker() { - //noinspection SimplifiableIfStatement boolean result; if (amount.get() != null && offer != null) { Coin customAmount = offer.getAmount().subtract(amount.get()); @@ -673,7 +675,7 @@ class TakeOfferDataModel extends OfferDataModel { // we have to keep a minimum amount of BSQ == bitcoin dust limit // otherwise there would be dust violations for change UTXOs // essentially means the minimum usable balance of BSQ is 5.46 - Coin usableBsqBalance = bsqWalletService.getAvailableConfirmedBalance().subtract(Restrictions.getMinNonDustOutput()); + Coin usableBsqBalance = bsqWalletService.getAvailableBalance().subtract(Restrictions.getMinNonDustOutput()); if (usableBsqBalance.isNegative()) usableBsqBalance = Coin.ZERO; return usableBsqBalance; diff --git a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferView.fxml b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/takeoffer/TakeOfferView.fxml similarity index 96% rename from desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferView.fxml rename to desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/takeoffer/TakeOfferView.fxml index af183ce569..79ec2cbad4 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferView.fxml +++ b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/takeoffer/TakeOfferView.fxml @@ -18,7 +18,7 @@ --> - diff --git a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferView.java b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/takeoffer/TakeOfferView.java similarity index 99% rename from desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferView.java rename to desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/takeoffer/TakeOfferView.java index caeab48c27..3eb2482023 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/takeoffer/TakeOfferView.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.desktop.main.offer.takeoffer; +package bisq.desktop.main.offer.bisq_v1.takeoffer; import bisq.desktop.Navigation; import bisq.desktop.common.view.ActivatableViewAndModel; @@ -37,7 +37,7 @@ import bisq.desktop.main.dao.wallet.receive.BsqReceiveView; import bisq.desktop.main.funds.FundsView; import bisq.desktop.main.funds.withdrawal.WithdrawalView; import bisq.desktop.main.offer.OfferView; -import bisq.desktop.main.offer.OfferViewUtil; +import bisq.desktop.main.offer.bisq_v1.OfferViewUtil; import bisq.desktop.main.overlays.notifications.Notification; import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.main.overlays.windows.GenericMessageWindow; @@ -52,7 +52,7 @@ import bisq.desktop.util.Transitions; import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferDirection; import bisq.core.payment.FasterPaymentsAccount; import bisq.core.payment.PaymentAccount; import bisq.core.payment.payload.PaymentMethod; @@ -163,7 +163,7 @@ public class TakeOfferView extends ActivatableViewAndModel paymentAccountWarningDisplayed = new HashMap<>(); + private final HashMap paymentAccountWarningDisplayed = new HashMap<>(); private boolean offerDetailsWindowDisplayed, clearXchangeWarningDisplayed, fasterPaymentsWarningDisplayed, takeOfferFromUnsignedAccountWarningDisplayed, cashByMailWarningDisplayed; private SimpleBooleanProperty errorPopupDisplayed; @@ -354,7 +354,7 @@ public class TakeOfferView extends ActivatableViewAndModel { if (newValue != null) { - new Popup().error(Res.get("takeOffer.error.message", model.errorMessage.get()) + + new Popup().error(Res.get("takeOffer.error.message", model.errorMessage.get()) + "\n\n" + Res.get("popup.error.tryRestart")) .onClose(() -> { errorPopupDisplayed.set(true); @@ -734,7 +734,7 @@ public class TakeOfferView extends ActivatableViewAndModel new Popup().headLine(Res.get("takeOffer.success.headline")) + new Popup().headLine(Res.get("takeOffer.success.headline")) .feedback(Res.get("takeOffer.success.info")) .actionButtonTextWithGoTo("navigation.portfolio.pending") .dontShowAgainId(key) @@ -745,7 +745,7 @@ public class TakeOfferView extends ActivatableViewAndModel. */ -package bisq.desktop.main.offer.takeoffer; +package bisq.desktop.main.offer.bisq_v1.takeoffer; import bisq.desktop.Navigation; import bisq.desktop.common.model.ActivatableWithDataModel; @@ -23,7 +23,7 @@ import bisq.desktop.common.model.ViewModel; import bisq.desktop.main.MainView; import bisq.desktop.main.funds.FundsView; import bisq.desktop.main.funds.deposit.DepositView; -import bisq.desktop.main.offer.FeeUtil; +import bisq.desktop.main.offer.bisq_v1.OfferViewModelUtil; import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.util.DisplayUtils; import bisq.desktop.util.GUIUtil; @@ -35,13 +35,13 @@ import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; import bisq.core.monetary.Price; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferDirection; import bisq.core.offer.OfferRestrictions; import bisq.core.offer.OfferUtil; import bisq.core.payment.PaymentAccount; import bisq.core.payment.payload.PaymentMethod; import bisq.core.provider.fee.FeeService; -import bisq.core.trade.Trade; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.util.FormattingUtils; import bisq.core.util.VolumeUtil; import bisq.core.util.coin.BsqFormatter; @@ -264,8 +264,8 @@ class TakeOfferViewModel extends ActivatableWithDataModel im return true; } else { new Popup().warning(Res.get("shared.notEnoughFunds", - btcFormatter.formatCoinWithCode(dataModel.getTotalToPayAsCoin().get()), - btcFormatter.formatCoinWithCode(dataModel.getTotalAvailableBalance()))) + btcFormatter.formatCoinWithCode(dataModel.getTotalToPayAsCoin().get()), + btcFormatter.formatCoinWithCode(dataModel.getTotalAvailableBalance()))) .actionButtonTextWithGoTo("navigation.funds.depositFunds") .onAction(() -> navigation.navigateTo(MainView.class, FundsView.class, DepositView.class)) .show(); @@ -288,11 +288,11 @@ class TakeOfferViewModel extends ActivatableWithDataModel im isTradeFeeVisible.setValue(true); tradeFee.set(getFormatterForTakerFee().formatCoin(takerFeeAsCoin)); - tradeFeeInBtcWithFiat.set(FeeUtil.getTradeFeeWithFiatEquivalent(offerUtil, + tradeFeeInBtcWithFiat.set(OfferViewModelUtil.getTradeFeeWithFiatEquivalent(offerUtil, dataModel.getTakerFeeInBtc(), true, btcFormatter)); - tradeFeeInBsqWithFiat.set(FeeUtil.getTradeFeeWithFiatEquivalent(offerUtil, + tradeFeeInBsqWithFiat.set(OfferViewModelUtil.getTradeFeeWithFiatEquivalent(offerUtil, dataModel.getTakerFeeInBsq(), false, bsqFormatter)); @@ -348,16 +348,16 @@ class TakeOfferViewModel extends ActivatableWithDataModel im amountValidationResult.set(new InputValidator.ValidationResult(false, Res.get("takeOffer.validation.amountLargerThanOfferAmountMinusFee"))); } else if (btcValidator.getMaxTradeLimit() != null && btcValidator.getMaxTradeLimit().value == OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT.value) { - if (dataModel.getDirection() == OfferPayload.Direction.BUY) { + if (dataModel.getDirection() == OfferDirection.BUY) { new Popup().information(Res.get("popup.warning.tradeLimitDueAccountAgeRestriction.seller", - btcFormatter.formatCoinWithCode(OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT), - Res.get("offerbook.warning.newVersionAnnouncement"))) + btcFormatter.formatCoinWithCode(OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT), + Res.get("offerbook.warning.newVersionAnnouncement"))) .width(900) .show(); } else { new Popup().information(Res.get("popup.warning.tradeLimitDueAccountAgeRestriction.buyer", - btcFormatter.formatCoinWithCode(OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT), - Res.get("offerbook.warning.newVersionAnnouncement"))) + btcFormatter.formatCoinWithCode(OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT), + Res.get("offerbook.warning.newVersionAnnouncement"))) .width(900) .show(); } @@ -420,7 +420,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel im private void applyTradeErrorMessage(@Nullable String errorMessage) { if (errorMessage != null) { String appendMsg = ""; - switch (trade.getState().getPhase()) { + switch (trade.getTradePhase()) { case INIT: appendMsg = Res.get("takeOffer.error.noFundsLost"); break; @@ -478,7 +478,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel im private void addBindings() { volume.bind(createStringBinding(() -> VolumeUtil.formatVolume(dataModel.volume.get()), dataModel.volume)); - if (dataModel.getDirection() == OfferPayload.Direction.SELL) { + if (dataModel.getDirection() == OfferDirection.SELL) { volumeDescriptionLabel.set(Res.get("createOffer.amountPriceBox.buy.volumeDescription", dataModel.getCurrencyCode())); } else { volumeDescriptionLabel.set(Res.get("createOffer.amountPriceBox.sell.volumeDescription", dataModel.getCurrencyCode())); @@ -642,11 +642,11 @@ class TakeOfferViewModel extends ActivatableWithDataModel im } boolean isSeller() { - return dataModel.getDirection() == OfferPayload.Direction.BUY; + return dataModel.getDirection() == OfferDirection.BUY; } public boolean isSellingToAnUnsignedAccount(Offer offer) { - if (offer.getDirection() == OfferPayload.Direction.BUY && + if (offer.getDirection() == OfferDirection.BUY && PaymentMethod.hasChargebackRisk(offer.getPaymentMethod(), offer.getCurrencyCode())) { // considered risky when either UNSIGNED, PEER_INITIAL, or BANNED (see #5343) return accountAgeWitnessService.getSignState(offer) == AccountAgeWitnessService.SignState.UNSIGNED || @@ -685,14 +685,14 @@ class TakeOfferViewModel extends ActivatableWithDataModel im } String getTradeAmount() { - return FeeUtil.getTradeFeeWithFiatEquivalent(offerUtil, + return OfferViewModelUtil.getTradeFeeWithFiatEquivalent(offerUtil, dataModel.getAmount().get(), true, btcFormatter); } public String getSecurityDepositInfo() { - return FeeUtil.getTradeFeeWithFiatEquivalentAndPercentage(offerUtil, + return OfferViewModelUtil.getTradeFeeWithFiatEquivalentAndPercentage(offerUtil, dataModel.getSecurityDeposit(), dataModel.getAmount().get(), true, @@ -707,7 +707,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel im public String getTradeFee() { if (dataModel.isCurrencyForTakerFeeBtc()) { - return FeeUtil.getTradeFeeWithFiatEquivalentAndPercentage(offerUtil, + return OfferViewModelUtil.getTradeFeeWithFiatEquivalentAndPercentage(offerUtil, dataModel.getTakerFeeInBtc(), dataModel.getAmount().get(), true, @@ -716,7 +716,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel im } else { // For BSQ we use the fiat equivalent only. Calculating the % value would require to // calculate the BTC value of the BSQ fee and use that... - return FeeUtil.getTradeFeeWithFiatEquivalent(offerUtil, + return OfferViewModelUtil.getTradeFeeWithFiatEquivalent(offerUtil, dataModel.getTakerFeeInBsq(), false, bsqFormatter); @@ -733,12 +733,12 @@ class TakeOfferViewModel extends ActivatableWithDataModel im public String getTotalToPayInfo() { if (dataModel.isCurrencyForTakerFeeBtc()) { - return FeeUtil.getTradeFeeWithFiatEquivalent(offerUtil, + return OfferViewModelUtil.getTradeFeeWithFiatEquivalent(offerUtil, dataModel.totalToPayAsCoin.get(), true, btcFormatter); } else { - return FeeUtil.getTradeFeeWithFiatEquivalent(offerUtil, + return OfferViewModelUtil.getTradeFeeWithFiatEquivalent(offerUtil, dataModel.totalToPayAsCoin.get(), true, btcFormatter) + " + " + getTradeFee(); @@ -746,7 +746,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel im } public String getTxFee() { - return FeeUtil.getTradeFeeWithFiatEquivalentAndPercentage(offerUtil, + return OfferViewModelUtil.getTradeFeeWithFiatEquivalentAndPercentage(offerUtil, dataModel.getTotalTxFee(), dataModel.getAmount().get(), true, diff --git a/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/BsqSwapOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/BsqSwapOfferDataModel.java new file mode 100644 index 0000000000..f5622dc8cc --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/BsqSwapOfferDataModel.java @@ -0,0 +1,111 @@ +/* + * 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.desktop.main.offer.bsq_swap; + +import bisq.desktop.common.model.ActivatableDataModel; +import bisq.desktop.util.DisplayUtils; +import bisq.desktop.util.GUIUtil; + +import bisq.core.locale.TradeCurrency; +import bisq.core.offer.OfferDirection; +import bisq.core.offer.bsq_swap.BsqSwapOfferModel; +import bisq.core.user.User; +import bisq.core.util.FormattingUtils; +import bisq.core.util.coin.CoinFormatter; + +import bisq.network.p2p.P2PService; + +import javax.inject.Named; + +import lombok.Getter; +import lombok.experimental.Delegate; + +public abstract class BsqSwapOfferDataModel extends ActivatableDataModel { + protected final User user; + private final P2PService p2PService; + private final CoinFormatter btcFormatter; + + // We use the BsqSwapOfferModel from core as delegate + // This contains all non UI specific domain aspects and is re-used from the API. + @Delegate(excludes = ExcludesDelegateMethods.class) + protected final BsqSwapOfferModel bsqSwapOfferModel; + + @Getter + protected TradeCurrency tradeCurrency; + @Getter + private boolean isTabSelected; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, lifecycle + /////////////////////////////////////////////////////////////////////////////////////////// + + public BsqSwapOfferDataModel(BsqSwapOfferModel bsqSwapOfferModel, + User user, + P2PService p2PService, + @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter) { + this.bsqSwapOfferModel = bsqSwapOfferModel; + this.user = user; + this.p2PService = p2PService; + this.btcFormatter = btcFormatter; + } + + @Override + protected void activate() { + bsqSwapOfferModel.doActivate(); + } + + @Override + protected void deactivate() { + bsqSwapOfferModel.doDeactivate(); + } + + public void onTabSelected(boolean isSelected) { + this.isTabSelected = isSelected; + } + + protected void createListeners() { + bsqSwapOfferModel.createListeners(); + } + + protected void addListeners() { + bsqSwapOfferModel.addListeners(); + } + + protected void removeListeners() { + bsqSwapOfferModel.removeListeners(); + } + + public boolean canPlaceOrTakeOffer() { + return GUIUtil.isBootstrappedOrShowPopup(p2PService); + } + + public void calculateAmount() { + bsqSwapOfferModel.calculateAmount(amount -> DisplayUtils.reduceTo4Decimals(amount, btcFormatter)); + } + + private interface ExcludesDelegateMethods { + void init(OfferDirection direction, boolean isMaker); + + void createListeners(); + + void addListeners(); + + void removeListeners(); + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/BsqSwapOfferView.java b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/BsqSwapOfferView.java new file mode 100644 index 0000000000..623044694b --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/BsqSwapOfferView.java @@ -0,0 +1,395 @@ +/* + * 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.desktop.main.offer.bsq_swap; + +import bisq.desktop.Navigation; +import bisq.desktop.common.view.ActivatableViewAndModel; +import bisq.desktop.components.AutoTooltipButton; +import bisq.desktop.components.AutoTooltipLabel; +import bisq.desktop.components.FundsTextField; +import bisq.desktop.components.InputTextField; +import bisq.desktop.components.TitledGroupBg; +import bisq.desktop.main.offer.OfferView; +import bisq.desktop.main.overlays.windows.BsqSwapOfferDetailsWindow; +import bisq.desktop.util.GUIUtil; +import bisq.desktop.util.Layout; + +import bisq.core.locale.Res; +import bisq.core.payment.PaymentAccount; + +import bisq.common.util.Tuple3; + +import org.bitcoinj.core.Coin; + +import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon; + +import javafx.scene.control.Button; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.Separator; +import javafx.scene.control.TextField; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.ColumnConstraints; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; +import javafx.scene.text.Text; + +import javafx.geometry.HPos; +import javafx.geometry.Insets; +import javafx.geometry.Orientation; +import javafx.geometry.VPos; + +import javafx.beans.value.ChangeListener; + +import javafx.util.Callback; + +import static bisq.desktop.util.FormBuilder.add2ButtonsWithBox; +import static bisq.desktop.util.FormBuilder.addFundsTextfield; +import static bisq.desktop.util.FormBuilder.addTitledGroupBg; +import static javafx.scene.layout.Region.USE_COMPUTED_SIZE; + +public abstract class BsqSwapOfferView> extends ActivatableViewAndModel { + protected final Navigation navigation; + protected final BsqSwapOfferDetailsWindow bsqSwapOfferDetailsWindow; + + protected ScrollPane scrollPane; + protected GridPane gridPane; + protected HBox nextButtonBar, actionButtonBar, firstRowHBox, secondRowHBox, amountValueCurrencyBox, + volumeValueCurrencyBox, priceValueCurrencyBox, minAmountValueCurrencyBox; + protected VBox paymentAccountVBox, currencyTextFieldBox; + protected InputTextField amountTextField; + protected Label resultLabel, xLabel, amountDescriptionLabel, priceCurrencyLabel, priceDescriptionLabel, + volumeDescriptionLabel, volumeCurrencyLabel; + protected Text xIcon; + protected Button nextButton, cancelButton1, cancelButton2; + protected AutoTooltipButton actionButton; + protected TitledGroupBg paymentAccountTitledGroupBg, feeInfoTitledGroupBg; + protected FundsTextField inputAmountTextField, payoutAmountTextField; + protected ChangeListener amountFocusedListener; + protected ChangeListener missingFundsListener; + protected OfferView.CloseHandler closeHandler; + protected int gridRow = 0; + protected boolean isMissingFundsPopupOpen; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, lifecycle + /////////////////////////////////////////////////////////////////////////////////////////// + + public BsqSwapOfferView(M model, + Navigation navigation, + BsqSwapOfferDetailsWindow bsqSwapOfferDetailsWindow) { + super(model); + + this.navigation = navigation; + this.bsqSwapOfferDetailsWindow = bsqSwapOfferDetailsWindow; + } + + @Override + protected void initialize() { + super.initialize(); + + addScrollPane(); + addGridPane(); + addPaymentAccountGroup(); + addAmountPriceGroup(); + addNextAndCancelButtons(); + addFeeInfoGroup(); + + createListeners(); + + GUIUtil.focusWhenAddedToScene(amountTextField); + } + + @Override + protected void activate() { + super.activate(); + } + + @Override + protected void deactivate() { + super.deactivate(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + public void setCloseHandler(OfferView.CloseHandler closeHandler) { + this.closeHandler = closeHandler; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // UI handler + /////////////////////////////////////////////////////////////////////////////////////////// + + public abstract void onTabSelected(boolean isSelected); + + protected abstract void onCancel1(); + + protected void onShowFeeInfoScreen() { + scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED); + + nextButton.setVisible(false); + nextButton.setManaged(false); + nextButton.setOnAction(null); + + cancelButton1.setVisible(false); + cancelButton1.setManaged(false); + cancelButton1.setOnAction(null); + + actionButtonBar.setManaged(true); + actionButtonBar.setVisible(true); + + feeInfoTitledGroupBg.setVisible(true); + inputAmountTextField.setVisible(true); + payoutAmountTextField.setVisible(true); + + updateOfferElementsStyle(); + } + + protected abstract void onCancel2(); + + protected abstract void onAction(); + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Navigation + /////////////////////////////////////////////////////////////////////////////////////////// + + protected void close() { + if (closeHandler != null) + closeHandler.close(); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Bindings, Listeners + /////////////////////////////////////////////////////////////////////////////////////////// + + protected void createListeners() { + missingFundsListener = (observable, oldValue, newValue) -> checkForMissingFunds(newValue); + } + + protected abstract void addListeners(); + + protected void removeListeners() { + model.dataModel.getMissingFunds().removeListener(missingFundsListener); + } + + protected abstract void addBindings(); + + protected abstract void removeBindings(); + + protected void addSubscriptions() { + } + + protected void removeSubscriptions() { + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Build UI elements + /////////////////////////////////////////////////////////////////////////////////////////// + + private void addScrollPane() { + scrollPane = new ScrollPane(); + scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); + scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); + scrollPane.setFitToWidth(true); + scrollPane.setFitToHeight(true); + AnchorPane.setLeftAnchor(scrollPane, 0d); + AnchorPane.setTopAnchor(scrollPane, 0d); + AnchorPane.setRightAnchor(scrollPane, 0d); + AnchorPane.setBottomAnchor(scrollPane, 0d); + root.getChildren().add(scrollPane); + } + + private void addGridPane() { + gridPane = new GridPane(); + gridPane.getStyleClass().add("content-pane"); + gridPane.setPadding(new Insets(30, 25, -1, 25)); + gridPane.setHgap(5); + gridPane.setVgap(5); + ColumnConstraints columnConstraints1 = new ColumnConstraints(); + columnConstraints1.setHalignment(HPos.RIGHT); + columnConstraints1.setHgrow(Priority.NEVER); + columnConstraints1.setMinWidth(200); + ColumnConstraints columnConstraints2 = new ColumnConstraints(); + columnConstraints2.setHgrow(Priority.ALWAYS); + gridPane.getColumnConstraints().addAll(columnConstraints1, columnConstraints2); + scrollPane.setContent(gridPane); + } + + protected abstract void addPaymentAccountGroup(); + + protected abstract void addAmountPriceGroup(); + + protected void addNextAndCancelButtons() { + Tuple3 tuple = add2ButtonsWithBox(gridPane, ++gridRow, + Res.get("shared.nextStep"), Res.get("shared.cancel"), 15, true); + + nextButtonBar = tuple.third; + + nextButton = tuple.first; + nextButton.setMaxWidth(200); + nextButton.setOnAction(e -> onShowFeeInfoScreen()); + + cancelButton1 = tuple.second; + cancelButton1.setMaxWidth(200); + cancelButton1.setOnAction(e -> onCancel1()); + } + + protected void addFeeInfoGroup() { + // don't increase gridRow as we removed button when this gets visible + feeInfoTitledGroupBg = addTitledGroupBg(gridPane, gridRow, 2, + Res.get("bsqSwapOffer.amounts.headline"), Layout.COMPACT_GROUP_DISTANCE); + feeInfoTitledGroupBg.getStyleClass().add("last"); + feeInfoTitledGroupBg.setVisible(false); + + inputAmountTextField = addFundsTextfield(gridPane, gridRow, + Res.get("bsqSwapOffer.inputAmount"), Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE); + inputAmountTextField.setVisible(false); + + payoutAmountTextField = addFundsTextfield(gridPane, ++gridRow, + Res.get("bsqSwapOffer.payoutAmount")); + payoutAmountTextField.setVisible(false); + + inputAmountTextField.setPrefWidth(830); + payoutAmountTextField.setPrefWidth(830); + + Tuple3 tuple = add2ButtonsWithBox(gridPane, ++gridRow, + Res.get("shared.cancel"), Res.get("shared.cancel"), 5, false); + + actionButton = (AutoTooltipButton) tuple.first; + actionButton.setMaxWidth(USE_COMPUTED_SIZE); + actionButton.setOnAction(e -> onAction()); + + cancelButton2 = tuple.second; + cancelButton2.setMaxWidth(USE_COMPUTED_SIZE); + cancelButton2.setOnAction(e -> onCancel2()); + + actionButtonBar = tuple.third; + actionButtonBar.setManaged(false); + actionButtonBar.setVisible(false); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // DetailsPopover + /////////////////////////////////////////////////////////////////////////////////////////// + + protected GridPane createInputAmountDetailsPopover() { + GridPane infoGridPane = new GridPane(); + + infoGridPane.setHgap(5); + infoGridPane.setVgap(5); + infoGridPane.setPadding(new Insets(10, 10, 10, 10)); + int i = 0; + if (model.dataModel.isSellOffer()) { + addPayInfoEntry(infoGridPane, i++, Res.getWithCol("shared.tradeAmount"), model.btcTradeAmount.get()); + addPayInfoEntry(infoGridPane, i++, Res.getWithCol("createOffer.fundsBox.networkFee"), model.getTxFee()); + } else { + addPayInfoEntry(infoGridPane, i++, Res.getWithCol("shared.tradeAmount"), model.bsqTradeAmount.get()); + addPayInfoEntry(infoGridPane, i++, Res.getWithCol("createOffer.fundsBox.offerFee"), model.getTradeFee()); + } + Separator separator = new Separator(); + separator.setOrientation(Orientation.HORIZONTAL); + separator.getStyleClass().add("offer-separator"); + GridPane.setConstraints(separator, 1, i++); + infoGridPane.getChildren().add(separator); + addPayInfoEntry(infoGridPane, i, Res.getWithCol("shared.total"), model.inputAmount.get()); + return infoGridPane; + } + + protected GridPane createPayoutAmountDetailsPopover() { + GridPane infoGridPane = new GridPane(); + infoGridPane.setHgap(5); + infoGridPane.setVgap(5); + infoGridPane.setPadding(new Insets(10, 10, 10, 10)); + int i = 0; + if (model.dataModel.isSellOffer()) { + addPayInfoEntry(infoGridPane, i++, Res.getWithCol("shared.tradeAmount"), model.bsqTradeAmount.get()); + addPayInfoEntry(infoGridPane, i++, Res.getWithCol("createOffer.fundsBox.offerFee"), "- " + model.getTradeFee()); + } else { + addPayInfoEntry(infoGridPane, i++, Res.getWithCol("shared.tradeAmount"), model.btcTradeAmount.get()); + addPayInfoEntry(infoGridPane, i++, Res.getWithCol("createOffer.fundsBox.networkFee"), "- " + model.getTxFee()); + } + Separator separator = new Separator(); + separator.setOrientation(Orientation.HORIZONTAL); + separator.getStyleClass().add("offer-separator"); + GridPane.setConstraints(separator, 1, i++); + infoGridPane.getChildren().add(separator); + addPayInfoEntry(infoGridPane, i, Res.getWithCol("shared.total"), model.payoutAmount.get()); + + return infoGridPane; + } + + private void addPayInfoEntry(GridPane infoGridPane, int row, String labelText, String value) { + Label label = new AutoTooltipLabel(labelText); + TextField textField = new TextField(value); + textField.setMinWidth(300); + textField.setEditable(false); + textField.setFocusTraversable(false); + textField.setId("payment-info"); + GridPane.setConstraints(label, 0, row, 1, 1, HPos.RIGHT, VPos.CENTER); + GridPane.setConstraints(textField, 1, row); + infoGridPane.getChildren().addAll(label, textField); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Utils + /////////////////////////////////////////////////////////////////////////////////////////// + + protected Callback, ListCell> getPaymentAccountListCellFactory( + ComboBox paymentAccountsComboBox) { + return GUIUtil.getPaymentAccountListCellFactory(paymentAccountsComboBox, model.getAccountAgeWitnessService()); + } + + protected void updateOfferElementsStyle() { + String activeInputStyle = "input-with-border"; + String readOnlyInputStyle = "input-with-border-readonly"; + amountValueCurrencyBox.getStyleClass().remove(activeInputStyle); + amountValueCurrencyBox.getStyleClass().add(readOnlyInputStyle); + volumeValueCurrencyBox.getStyleClass().remove(activeInputStyle); + volumeValueCurrencyBox.getStyleClass().add(readOnlyInputStyle); + priceValueCurrencyBox.getStyleClass().remove(activeInputStyle); + priceValueCurrencyBox.getStyleClass().add(readOnlyInputStyle); + minAmountValueCurrencyBox.getStyleClass().remove(activeInputStyle); + minAmountValueCurrencyBox.getStyleClass().add(readOnlyInputStyle); + + resultLabel.getStyleClass().add("small"); + xLabel.getStyleClass().add("small"); + xIcon.setStyle(String.format("-fx-font-family: %s; -fx-font-size: %s;", MaterialDesignIcon.CLOSE.fontFamily(), "1em")); + } + + protected abstract void checkForMissingFunds(Coin newValue); + + protected void requestFocus() { + // JFXComboBox causes a bug with requesting focus. Not clear why that happens but requesting a focus + // on our view here avoids that the currency List overlay gets displayed. + root.requestFocus(); + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/BsqSwapOfferViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/BsqSwapOfferViewModel.java new file mode 100644 index 0000000000..a5c64328aa --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/BsqSwapOfferViewModel.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.desktop.main.offer.bsq_swap; + +import bisq.desktop.common.model.ActivatableWithDataModel; + +import bisq.core.account.witness.AccountAgeWitnessService; +import bisq.core.locale.Res; +import bisq.core.util.coin.BsqFormatter; +import bisq.core.util.coin.CoinFormatter; + +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.InsufficientMoneyException; + +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import static javafx.beans.binding.Bindings.createStringBinding; + +@Slf4j +public abstract class BsqSwapOfferViewModel extends ActivatableWithDataModel { + @Getter + protected final StringProperty inputAmount = new SimpleStringProperty(); + @Getter + protected final StringProperty payoutAmount = new SimpleStringProperty(); + protected final StringProperty btcTradeAmount = new SimpleStringProperty(); + protected final StringProperty bsqTradeAmount = new SimpleStringProperty(); + protected final BsqFormatter bsqFormatter; + @Getter + protected AccountAgeWitnessService accountAgeWitnessService; + protected final CoinFormatter btcFormatter; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, lifecycle + /////////////////////////////////////////////////////////////////////////////////////////// + + public BsqSwapOfferViewModel(M dataModel, + CoinFormatter btcFormatter, + BsqFormatter bsqFormatter, + AccountAgeWitnessService accountAgeWitnessService) { + super(dataModel); + + this.btcFormatter = btcFormatter; + this.bsqFormatter = bsqFormatter; + this.accountAgeWitnessService = accountAgeWitnessService; + + createListeners(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Bindings, listeners + /////////////////////////////////////////////////////////////////////////////////////////// + + protected abstract void createListeners(); + + protected abstract void addListeners(); + + protected abstract void removeListeners(); + + protected void addBindings() { + inputAmount.bind(createStringBinding(() -> { + CoinFormatter formatter = dataModel.isBuyer() ? bsqFormatter : btcFormatter; + return formatter.formatCoinWithCode(dataModel.getInputAmountAsCoin().get()); + }, + dataModel.getInputAmountAsCoin())); + payoutAmount.bind(createStringBinding(() -> { + CoinFormatter formatter = dataModel.isBuyer() ? btcFormatter : bsqFormatter; + return formatter.formatCoinWithCode(dataModel.getPayoutAmountAsCoin().get()); + }, + dataModel.getInputAmountAsCoin())); + btcTradeAmount.bind(createStringBinding(() -> btcFormatter.formatCoinWithCode(dataModel.getBtcAmount().get()), + dataModel.getBtcAmount())); + + bsqTradeAmount.bind(createStringBinding(() -> bsqFormatter.formatCoinWithCode(dataModel.getBsqAmount().get()), + dataModel.getBsqAmount())); + } + + protected void removeBindings() { + inputAmount.unbind(); + payoutAmount.unbind(); + btcTradeAmount.unbind(); + bsqTradeAmount.unbind(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Getters + /////////////////////////////////////////////////////////////////////////////////////////// + + protected String getTradeFee() { + return bsqFormatter.formatCoinWithCode(dataModel.getTradeFee()); + } + + public String getInputAmountDetails() { + String details; + if (dataModel.isBuyer()) { + details = Res.get("bsqSwapOffer.inputAmount.details.buyer", + bsqTradeAmount.get(), getTradeFee()); + } else { + details = Res.get("bsqSwapOffer.inputAmount.details.seller", + btcTradeAmount.get(), getTxFee()); + } + + return details; + } + + public String getPayoutAmountDetails() { + String details; + if (dataModel.isBuyer()) { + details = Res.get("bsqSwapOffer.outputAmount.details.buyer", + btcTradeAmount.get(), getTxFee()); + } else { + details = Res.get("bsqSwapOffer.outputAmount.details.seller", + bsqTradeAmount.get(), getTradeFee()); + } + + return details; + } + + public String getTxFee() { + try { + Coin txFee = dataModel.getTxFee(); + return btcFormatter.formatCoinWithCode(txFee); + } catch (InsufficientMoneyException e) { + Coin txFee = dataModel.getEstimatedTxFee(); + return Res.get("bsqSwapOffer.estimated", btcFormatter.formatCoinWithCode(txFee)); + } + } + + public String getMissingFunds(Coin missing) { + return dataModel.isBuyer() ? + bsqFormatter.formatCoinWithCode(missing) : + btcFormatter.formatCoinWithCode(missing); + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/create_offer/BsqSwapCreateOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/create_offer/BsqSwapCreateOfferDataModel.java new file mode 100644 index 0000000000..b17cccf725 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/create_offer/BsqSwapCreateOfferDataModel.java @@ -0,0 +1,166 @@ +/* + * 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.desktop.main.offer.bsq_swap.create_offer; + +import bisq.desktop.main.offer.bsq_swap.BsqSwapOfferDataModel; + +import bisq.core.locale.TradeCurrency; +import bisq.core.offer.Offer; +import bisq.core.offer.OfferDirection; +import bisq.core.offer.OfferUtil; +import bisq.core.offer.bsq_swap.BsqSwapOfferModel; +import bisq.core.offer.bsq_swap.OpenBsqSwapOfferService; +import bisq.core.payment.PaymentAccount; +import bisq.core.user.User; +import bisq.core.util.FormattingUtils; +import bisq.core.util.coin.CoinFormatter; + +import bisq.network.p2p.P2PService; + +import javax.inject.Inject; +import javax.inject.Named; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.SetChangeListener; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Consumer; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Comparator.comparing; + +@Slf4j +class BsqSwapCreateOfferDataModel extends BsqSwapOfferDataModel { + private final OpenBsqSwapOfferService openBsqSwapOfferService; + Offer offer; + private SetChangeListener paymentAccountsChangeListener; + @Getter + private final ObservableList paymentAccounts = FXCollections.observableArrayList(); + @Getter + private PaymentAccount paymentAccount; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, lifecycle + /////////////////////////////////////////////////////////////////////////////////////////// + + @Inject + BsqSwapCreateOfferDataModel(BsqSwapOfferModel bsqSwapOfferModel, + OpenBsqSwapOfferService openBsqSwapOfferService, + User user, + P2PService p2PService, + @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter) { + super(bsqSwapOfferModel, + user, + p2PService, + btcFormatter); + + this.openBsqSwapOfferService = openBsqSwapOfferService; + + setOfferId(OfferUtil.getRandomOfferId()); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + void initWithData(OfferDirection direction) { + bsqSwapOfferModel.init(direction, true); + + fillPaymentAccounts(); + applyPaymentAccount(); + applyTradeCurrency(); + } + + protected void requestNewOffer(Consumer resultHandler) { + openBsqSwapOfferService.requestNewOffer(getOfferId(), + getDirection(), + getBtcAmount().get(), + getMinAmount().get(), + getPrice().get(), + offer -> { + this.offer = offer; + resultHandler.accept(offer); + }); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // UI actions + /////////////////////////////////////////////////////////////////////////////////////////// + + void onPlaceOffer(Runnable resultHandler) { + openBsqSwapOfferService.placeBsqSwapOffer(offer, + resultHandler, + log::error); + } + + @Override + protected void createListeners() { + super.createListeners(); + paymentAccountsChangeListener = change -> fillPaymentAccounts(); + } + + @Override + protected void addListeners() { + super.addListeners(); + user.getPaymentAccountsAsObservable().addListener(paymentAccountsChangeListener); + } + + @Override + protected void removeListeners() { + super.removeListeners(); + user.getPaymentAccountsAsObservable().removeListener(paymentAccountsChangeListener); + } + + private void fillPaymentAccounts() { + if (getUserPaymentAccounts() != null) { + paymentAccounts.setAll(new HashSet<>(getUserPaymentAccounts())); + } + paymentAccounts.sort(comparing(PaymentAccount::getAccountName)); + } + + private Set getUserPaymentAccounts() { + return user.getPaymentAccounts(); + } + + private void applyPaymentAccount() { + Optional bsqAccountOptional = Objects.requireNonNull(getUserPaymentAccounts()).stream() + .filter(e -> e.getPaymentMethod().isBsqSwap()).findFirst(); + checkArgument(bsqAccountOptional.isPresent(), "BSQ account must be present"); + this.paymentAccount = bsqAccountOptional.get(); + } + + private void applyTradeCurrency() { + Optional optionalTradeCurrency = paymentAccount.getTradeCurrency(); + checkArgument(optionalTradeCurrency.isPresent(), "BSQ tradeCurrency must be present"); + tradeCurrency = optionalTradeCurrency.get(); + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/create_offer/BsqSwapCreateOfferView.fxml b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/create_offer/BsqSwapCreateOfferView.fxml new file mode 100644 index 0000000000..622b90ddba --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/create_offer/BsqSwapCreateOfferView.fxml @@ -0,0 +1,23 @@ + + + + + + + diff --git a/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/create_offer/BsqSwapCreateOfferView.java b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/create_offer/BsqSwapCreateOfferView.java new file mode 100644 index 0000000000..16e4210b94 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/create_offer/BsqSwapCreateOfferView.java @@ -0,0 +1,600 @@ +/* + * 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.desktop.main.offer.bsq_swap.create_offer; + +import bisq.desktop.Navigation; +import bisq.desktop.common.view.FxmlView; +import bisq.desktop.components.AutoTooltipLabel; +import bisq.desktop.components.BusyAnimation; +import bisq.desktop.components.InfoInputTextField; +import bisq.desktop.components.InputTextField; +import bisq.desktop.components.TitledGroupBg; +import bisq.desktop.main.MainView; +import bisq.desktop.main.offer.OfferView; +import bisq.desktop.main.offer.bsq_swap.BsqSwapOfferView; +import bisq.desktop.main.overlays.popups.Popup; +import bisq.desktop.main.overlays.windows.BsqSwapOfferDetailsWindow; +import bisq.desktop.main.portfolio.PortfolioView; +import bisq.desktop.main.portfolio.openoffer.OpenOffersView; +import bisq.desktop.util.GUIUtil; +import bisq.desktop.util.Layout; + +import bisq.core.locale.CurrencyUtil; +import bisq.core.locale.Res; +import bisq.core.offer.OfferDirection; +import bisq.core.payment.PaymentAccount; +import bisq.core.user.DontShowAgainLookup; + +import bisq.common.UserThread; +import bisq.common.app.DevEnv; +import bisq.common.util.Tuple2; +import bisq.common.util.Tuple3; + +import org.bitcoinj.core.Coin; + +import javax.inject.Inject; + +import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon; + +import javafx.scene.Node; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; + +import javafx.geometry.Insets; +import javafx.geometry.Pos; + +import javafx.beans.value.ChangeListener; + +import javafx.event.ActionEvent; +import javafx.event.EventHandler; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +import static bisq.core.offer.bsq_swap.BsqSwapOfferModel.BSQ; +import static bisq.desktop.util.FormBuilder.*; + +@FxmlView +@Slf4j +public class BsqSwapCreateOfferView extends BsqSwapOfferView { + private InputTextField minAmountTextField, priceTextField, volumeTextField; + private Label miningPowLabel; + private BusyAnimation miningPowBusyAnimation; + private ComboBox paymentAccountsComboBox; + private ChangeListener minAmountFocusedListener, volumeFocusedListener, + priceFocusedListener, placeOfferCompletedListener; + private ChangeListener errorMessageListener; + private EventHandler paymentAccountsComboBoxSelectionHandler; + private final List editOfferElements = new ArrayList<>(); + private boolean isActivated; + + @Setter + private OfferView.OfferActionHandler offerActionHandler; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, lifecycle + /////////////////////////////////////////////////////////////////////////////////////////// + + @Inject + public BsqSwapCreateOfferView(BsqSwapCreateOfferViewModel model, + Navigation navigation, + BsqSwapOfferDetailsWindow bsqSwapOfferDetailsWindow) { + super(model, navigation, bsqSwapOfferDetailsWindow); + } + + @Override + protected void initialize() { + super.initialize(); + } + + @Override + protected void activate() { + super.activate(); + + if (model.dataModel.isTabSelected()) { + doActivate(); + } + } + + private void doActivate() { + if (!isActivated) { + isActivated = true; + paymentAccountsComboBox.setPrefWidth(250); + + addListeners(); + addBindings(); + + paymentAccountsComboBox.setItems(model.dataModel.getPaymentAccounts()); + paymentAccountsComboBox.getSelectionModel().select(model.dataModel.getPaymentAccount()); + onPaymentAccountsComboBoxSelected(); + + String key = "BsqSwapMakerInfo"; + if (DontShowAgainLookup.showAgain(key)) { + new Popup().information(Res.get("createOffer.bsqSwap.offerVisibility") + "\n\n" + Res.get("bsqSwapOffer.feeHandling")) + .width(1000) + .closeButtonText(Res.get("shared.iUnderstand")) + .dontShowAgainId(key) + .show(); + } + } + } + + @Override + protected void deactivate() { + super.deactivate(); + + if (isActivated) { + isActivated = false; + removeListeners(); + removeBindings(); + } + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + public void initWithData(OfferDirection direction, OfferView.OfferActionHandler offerActionHandler) { + this.offerActionHandler = offerActionHandler; + model.initWithData(direction); + + if (model.dataModel.isBuyOffer()) { + actionButton.setId("buy-button-big"); + actionButton.updateText(Res.get("createOffer.placeOfferButton", Res.get("shared.buy"))); + volumeDescriptionLabel.setText(Res.get("createOffer.amountPriceBox.buy.volumeDescription", BSQ)); + } else { + actionButton.setId("sell-button-big"); + actionButton.updateText(Res.get("createOffer.placeOfferButton", Res.get("shared.sell"))); + volumeDescriptionLabel.setText(Res.get("createOffer.amountPriceBox.sell.volumeDescription", BSQ)); + } + + String amountDescription = Res.get("createOffer.amountPriceBox.amountDescription", + model.dataModel.isBuyOffer() ? Res.get("shared.buy") : Res.get("shared.sell")); + amountDescriptionLabel.setText(amountDescription); + + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // UI actions + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void onTabSelected(boolean isSelected) { + if (isSelected && !model.dataModel.isTabSelected()) { + doActivate(); + } + + isActivated = isSelected; + model.dataModel.onTabSelected(isSelected); + } + + @Override + protected void onCancel1() { + close(); + } + + @Override + protected void onShowFeeInfoScreen() { + super.onShowFeeInfoScreen(); + + paymentAccountsComboBox.setDisable(true); + paymentAccountsComboBox.setMouseTransparent(true); + + editOfferElements.forEach(node -> { + node.setMouseTransparent(true); + node.setFocusTraversable(false); + }); + + inputAmountTextField.setFundsStructure(model.getInputAmountDetails()); + inputAmountTextField.setContentForInfoPopOver(createInputAmountDetailsPopover()); + payoutAmountTextField.setFundsStructure(model.getPayoutAmountDetails()); + payoutAmountTextField.setContentForInfoPopOver(createPayoutAmountDetailsPopover()); + + model.dataModel.getMissingFunds().addListener(missingFundsListener); + checkForMissingFunds(model.dataModel.getMissingFunds().get()); + + // We create the offer and start do the pow. + // As the pow could take some time we do it already now and not at offer confirm. + // We have already all data to create the offer, so no reason to delay it to later. + model.requestNewOffer(); + } + + @Override + protected void onAction() { + if (!model.dataModel.canPlaceOrTakeOffer()) { + return; + } + + if (DevEnv.isDevMode()) { + model.onPlaceOffer(); + requestFocus(); + return; + } + + bsqSwapOfferDetailsWindow.onPlaceOffer(model::onPlaceOffer).show(model.dataModel.offer); + requestFocus(); + } + + @Override + protected void onCancel2() { + close(); + } + + private void onPaymentAccountsComboBoxSelected() { + PaymentAccount paymentAccount = paymentAccountsComboBox.getSelectionModel().getSelectedItem(); + // We have represented BSQ swaps as payment method and switch to a new view if a non BSQ swap account is selected + if (paymentAccount != null && !paymentAccount.getPaymentMethod().isBsqSwap()) { + close(); + + if (offerActionHandler != null) { + offerActionHandler.onCreateOffer(paymentAccount.getSelectedTradeCurrency(), + paymentAccount.getPaymentMethod()); + } + } + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Bindings, Listeners + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + protected void createListeners() { + super.createListeners(); + + amountFocusedListener = (o, oldValue, newValue) -> { + model.onFocusOutAmountTextField(oldValue, newValue); + amountTextField.setText(model.amount.get()); + }; + minAmountFocusedListener = (o, oldValue, newValue) -> { + model.onFocusOutMinAmountTextField(oldValue, newValue); + minAmountTextField.setText(model.minAmount.get()); + }; + priceFocusedListener = (o, oldValue, newValue) -> { + model.onFocusOutPriceTextField(oldValue, newValue); + priceTextField.setText(model.price.get()); + }; + volumeFocusedListener = (o, oldValue, newValue) -> { + model.onFocusOutVolumeTextField(oldValue, newValue); + volumeTextField.setText(model.volume.get()); + }; + + errorMessageListener = (o, oldValue, newValue) -> { + if (newValue != null) + UserThread.runAfter(() -> new Popup().error(Res.get("createOffer.amountPriceBox.error.message", model.errorMessage.get())) + .show(), 100, TimeUnit.MILLISECONDS); + }; + + paymentAccountsComboBoxSelectionHandler = e -> onPaymentAccountsComboBoxSelected(); + + placeOfferCompletedListener = (o, oldValue, newValue) -> { + if (DevEnv.isDevMode()) { + close(); + } else if (newValue) { + // We need a bit of delay to avoid issues with fade out/fade in of 2 popups + String key = "createBsqOfferSuccessInfo"; + if (DontShowAgainLookup.showAgain(key)) { + UserThread.runAfter(() -> new Popup().headLine(Res.get("createOffer.success.headline")) + .feedback(Res.get("createOffer.success.info")) + .dontShowAgainId(key) + .actionButtonTextWithGoTo("navigation.portfolio.myOpenOffers") + .onAction(() -> { + UserThread.runAfter(() -> + navigation.navigateTo(MainView.class, PortfolioView.class, + OpenOffersView.class), + 100, TimeUnit.MILLISECONDS); + close(); + }) + .onClose(this::close) + .show(), + 100, TimeUnit.MILLISECONDS); + } else { + close(); + } + } + }; + } + + @Override + protected void addListeners() { + // focus out + amountTextField.focusedProperty().addListener(amountFocusedListener); + minAmountTextField.focusedProperty().addListener(minAmountFocusedListener); + priceTextField.focusedProperty().addListener(priceFocusedListener); + volumeTextField.focusedProperty().addListener(volumeFocusedListener); + + // warnings + model.errorMessage.addListener(errorMessageListener); + + model.placeOfferCompleted.addListener(placeOfferCompletedListener); + + // UI actions + paymentAccountsComboBox.setOnAction(paymentAccountsComboBoxSelectionHandler); + } + + @Override + protected void removeListeners() { + super.removeListeners(); + + // focus out + amountTextField.focusedProperty().removeListener(amountFocusedListener); + minAmountTextField.focusedProperty().removeListener(minAmountFocusedListener); + priceTextField.focusedProperty().removeListener(priceFocusedListener); + volumeTextField.focusedProperty().removeListener(volumeFocusedListener); + + // warnings + model.errorMessage.removeListener(errorMessageListener); + + model.placeOfferCompleted.removeListener(placeOfferCompletedListener); + + // UI actions + paymentAccountsComboBox.setOnAction(null); + } + + @Override + protected void addBindings() { + amountTextField.textProperty().bindBidirectional(model.amount); + minAmountTextField.textProperty().bindBidirectional(model.minAmount); + priceTextField.textProperty().bindBidirectional(model.price); + volumeTextField.textProperty().bindBidirectional(model.volume); + volumeTextField.promptTextProperty().bind(model.volumePromptLabel); + inputAmountTextField.textProperty().bind(model.getInputAmount()); + payoutAmountTextField.textProperty().bind(model.getPayoutAmount()); + // Validation + amountTextField.validationResultProperty().bind(model.amountValidationResult); + minAmountTextField.validationResultProperty().bind(model.minAmountValidationResult); + priceTextField.validationResultProperty().bind(model.priceValidationResult); + volumeTextField.validationResultProperty().bind(model.volumeValidationResult); + + nextButton.disableProperty().bind(model.isNextButtonDisabled); + actionButton.disableProperty().bind(model.isPlaceOfferButtonDisabled); + cancelButton2.disableProperty().bind(model.cancelButtonDisabled); + + // trading account + paymentAccountTitledGroupBg.managedProperty().bind(paymentAccountTitledGroupBg.visibleProperty()); + currencyTextFieldBox.managedProperty().bind(currencyTextFieldBox.visibleProperty()); + + miningPowLabel.visibleProperty().bind(model.miningPoW); + miningPowLabel.managedProperty().bind(model.miningPoW); + miningPowBusyAnimation.visibleProperty().bind(model.miningPoW); + miningPowBusyAnimation.managedProperty().bind(model.miningPoW); + miningPowBusyAnimation.isRunningProperty().bind(model.miningPoW); + } + + @Override + protected void removeBindings() { + amountTextField.textProperty().unbindBidirectional(model.amount); + minAmountTextField.textProperty().unbindBidirectional(model.minAmount); + priceTextField.textProperty().unbindBidirectional(model.price); + volumeTextField.textProperty().unbindBidirectional(model.volume); + volumeTextField.promptTextProperty().unbindBidirectional(model.volume); + inputAmountTextField.textProperty().unbind(); + payoutAmountTextField.textProperty().unbind(); + + // Validation + amountTextField.validationResultProperty().unbind(); + minAmountTextField.validationResultProperty().unbind(); + priceTextField.validationResultProperty().unbind(); + volumeTextField.validationResultProperty().unbind(); + + nextButton.disableProperty().unbind(); + actionButton.disableProperty().unbind(); + cancelButton2.disableProperty().unbind(); + + // trading account + paymentAccountTitledGroupBg.managedProperty().unbind(); + currencyTextFieldBox.managedProperty().unbind(); + + miningPowLabel.visibleProperty().unbind(); + miningPowLabel.managedProperty().unbind(); + miningPowBusyAnimation.visibleProperty().unbind(); + miningPowBusyAnimation.managedProperty().unbind(); + miningPowBusyAnimation.isRunningProperty().unbind(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Build UI elements + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + protected void addPaymentAccountGroup() { + paymentAccountTitledGroupBg = addTitledGroupBg(gridPane, gridRow, 1, Res.get("shared.selectTradingAccount")); + GridPane.setColumnSpan(paymentAccountTitledGroupBg, 2); + + HBox paymentGroupBox = new HBox(); + paymentGroupBox.setAlignment(Pos.CENTER_LEFT); + paymentGroupBox.setSpacing(12); + paymentGroupBox.setPadding(new Insets(10, 0, 18, 0)); + + Tuple3> paymentAccountBoxTuple = addTopLabelComboBox( + Res.get("shared.tradingAccount"), Res.get("shared.selectTradingAccount")); + + Tuple3 currencyTextFieldTuple = addTopLabelTextField(gridPane, gridRow, + Res.get("shared.currency"), BSQ, 5d); + currencyTextFieldBox = currencyTextFieldTuple.third; + + paymentAccountVBox = paymentAccountBoxTuple.first; + paymentGroupBox.getChildren().addAll(paymentAccountVBox, currencyTextFieldBox); + + GridPane.setRowIndex(paymentGroupBox, gridRow); + GridPane.setColumnSpan(paymentGroupBox, 2); + GridPane.setMargin(paymentGroupBox, new Insets(Layout.FIRST_ROW_DISTANCE, 0, 0, 0)); + gridPane.getChildren().add(paymentGroupBox); + + paymentAccountVBox.setMinWidth(800); + paymentAccountsComboBox = paymentAccountBoxTuple.third; + paymentAccountsComboBox.setMinWidth(paymentAccountVBox.getMinWidth()); + paymentAccountsComboBox.setPrefWidth(paymentAccountVBox.getMinWidth()); + paymentAccountsComboBox.setConverter(GUIUtil.getPaymentAccountsComboBoxStringConverter()); + paymentAccountsComboBox.setButtonCell(GUIUtil.getComboBoxButtonCell(Res.get("shared.selectTradingAccount"), + paymentAccountsComboBox, false)); + paymentAccountsComboBox.setCellFactory(getPaymentAccountListCellFactory(paymentAccountsComboBox)); + + editOfferElements.add(paymentAccountVBox); + } + + @Override + protected void addAmountPriceGroup() { + TitledGroupBg amountTitledGroupBg = addTitledGroupBg(gridPane, ++gridRow, 2, + Res.get("createOffer.setAmountPrice"), Layout.COMPACT_GROUP_DISTANCE); + GridPane.setColumnSpan(amountTitledGroupBg, 2); + + addFirstRow(); + addSecondRow(); + } + + private void addFirstRow() { + // amountBox + Tuple3 amountValueCurrencyBoxTuple = getEditableValueBox(Res.get("createOffer.amount.prompt")); + amountValueCurrencyBox = amountValueCurrencyBoxTuple.first; + amountTextField = amountValueCurrencyBoxTuple.second; + editOfferElements.add(amountTextField); + Label amountBtcLabel = amountValueCurrencyBoxTuple.third; + editOfferElements.add(amountBtcLabel); + Tuple2 amountInputBoxTuple = getTradeInputBox(amountValueCurrencyBox, ""); + amountDescriptionLabel = amountInputBoxTuple.first; + editOfferElements.add(amountDescriptionLabel); + VBox amountBox = amountInputBoxTuple.second; + + // x + xLabel = new Label(); + xIcon = getIconForLabel(MaterialDesignIcon.CLOSE, "2em", xLabel); + xIcon.getStyleClass().add("opaque-icon"); + xLabel.getStyleClass().add("opaque-icon-character"); + + // price + Tuple3 priceValueCurrencyBoxTuple = getEditableValueBox( + Res.get("createOffer.price.prompt")); + priceValueCurrencyBox = priceValueCurrencyBoxTuple.first; + priceTextField = priceValueCurrencyBoxTuple.second; + editOfferElements.add(priceTextField); + priceCurrencyLabel = priceValueCurrencyBoxTuple.third; + priceCurrencyLabel.setText("BTC"); + editOfferElements.add(priceCurrencyLabel); + Tuple2 priceInputBoxTuple = getTradeInputBox(priceValueCurrencyBox, ""); + priceDescriptionLabel = priceInputBoxTuple.first; + priceDescriptionLabel.setText(CurrencyUtil.getPriceWithCurrencyCode(BSQ, "shared.fixedPriceInCurForCur")); + + + getSmallIconForLabel(MaterialDesignIcon.LOCK, priceDescriptionLabel, "small-icon-label"); + + editOfferElements.add(priceDescriptionLabel); + VBox fixedPriceBox = priceInputBoxTuple.second; + + // = + resultLabel = new AutoTooltipLabel("="); + resultLabel.getStyleClass().add("opaque-icon-character"); + + // volume + Tuple3 volumeValueCurrencyBoxTuple = getEditableValueBoxWithInfo(Res.get("createOffer.volume.prompt")); + volumeValueCurrencyBox = volumeValueCurrencyBoxTuple.first; + InfoInputTextField volumeInfoInputTextField = volumeValueCurrencyBoxTuple.second; + volumeTextField = volumeInfoInputTextField.getInputTextField(); + editOfferElements.add(volumeTextField); + volumeCurrencyLabel = volumeValueCurrencyBoxTuple.third; + volumeCurrencyLabel.setText(BSQ); + editOfferElements.add(volumeCurrencyLabel); + Tuple2 volumeInputBoxTuple = getTradeInputBox(volumeValueCurrencyBox, ""); + volumeDescriptionLabel = volumeInputBoxTuple.first; + editOfferElements.add(volumeDescriptionLabel); + VBox volumeBox = volumeInputBoxTuple.second; + + firstRowHBox = new HBox(); + firstRowHBox.setSpacing(5); + firstRowHBox.setAlignment(Pos.CENTER_LEFT); + firstRowHBox.getChildren().addAll(amountBox, xLabel, fixedPriceBox, resultLabel, volumeBox); + GridPane.setColumnSpan(firstRowHBox, 2); + GridPane.setRowIndex(firstRowHBox, gridRow); + GridPane.setMargin(firstRowHBox, new Insets(Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE, 10, 0, 0)); + gridPane.getChildren().add(firstRowHBox); + } + + private void addSecondRow() { + Tuple3 amountValueCurrencyBoxTuple = getEditableValueBox(Res.get("createOffer.amount.prompt")); + minAmountValueCurrencyBox = amountValueCurrencyBoxTuple.first; + minAmountTextField = amountValueCurrencyBoxTuple.second; + editOfferElements.add(minAmountTextField); + Label minAmountBtcLabel = amountValueCurrencyBoxTuple.third; + editOfferElements.add(minAmountBtcLabel); + + Tuple2 amountInputBoxTuple = getTradeInputBox(minAmountValueCurrencyBox, Res.get("createOffer.amountPriceBox.minAmountDescription")); + + secondRowHBox = new HBox(); + secondRowHBox.setSpacing(5); + secondRowHBox.setAlignment(Pos.CENTER_LEFT); + secondRowHBox.getChildren().add(amountInputBoxTuple.second); + GridPane.setColumnSpan(secondRowHBox, 2); + GridPane.setRowIndex(secondRowHBox, ++gridRow); + GridPane.setColumnIndex(secondRowHBox, 0); + GridPane.setMargin(secondRowHBox, new Insets(0, 10, 10, 0)); + gridPane.getChildren().add(secondRowHBox); + } + + @Override + protected void addNextAndCancelButtons() { + super.addNextAndCancelButtons(); + + editOfferElements.add(nextButton); + editOfferElements.add(cancelButton1); + } + + @Override + protected void addFeeInfoGroup() { + super.addFeeInfoGroup(); + + miningPowBusyAnimation = new BusyAnimation(false); + miningPowLabel = new AutoTooltipLabel(Res.get("createOffer.bsqSwap.mintingPow")); + HBox.setMargin(miningPowLabel, new Insets(6, 0, 0, 0)); + actionButtonBar.getChildren().addAll(miningPowBusyAnimation, miningPowLabel); + } + + @Override + protected void updateOfferElementsStyle() { + super.updateOfferElementsStyle(); + + GridPane.setColumnSpan(firstRowHBox, 2); + GridPane.setColumnSpan(secondRowHBox, 1); + } + + @Override + protected void checkForMissingFunds(Coin missing) { + if (missing.isPositive() && !isMissingFundsPopupOpen) { + isMissingFundsPopupOpen = true; + String wallet = model.dataModel.isBuyer() ? "BSQ" : "BTC"; + String warning = Res.get("createOffer.bsqSwap.missingFunds.maker", + wallet, model.getMissingFunds(missing)); + new Popup().warning(warning) + .onClose(() -> { + isMissingFundsPopupOpen = false; + }) + .show(); + } + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/create_offer/BsqSwapCreateOfferViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/create_offer/BsqSwapCreateOfferViewModel.java new file mode 100644 index 0000000000..699cda79ac --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/create_offer/BsqSwapCreateOfferViewModel.java @@ -0,0 +1,581 @@ +/* + * 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.desktop.main.offer.bsq_swap.create_offer; + +import bisq.desktop.common.model.ViewModel; +import bisq.desktop.main.offer.bsq_swap.BsqSwapOfferViewModel; +import bisq.desktop.main.overlays.popups.Popup; +import bisq.desktop.util.DisplayUtils; +import bisq.desktop.util.validation.BsqValidator; +import bisq.desktop.util.validation.BtcValidator; + +import bisq.core.account.witness.AccountAgeWitnessService; +import bisq.core.btc.wallet.Restrictions; +import bisq.core.locale.Res; +import bisq.core.monetary.Price; +import bisq.core.monetary.Volume; +import bisq.core.offer.OfferDirection; +import bisq.core.offer.OfferRestrictions; +import bisq.core.payment.payload.PaymentMethod; +import bisq.core.util.FormattingUtils; +import bisq.core.util.VolumeUtil; +import bisq.core.util.coin.BsqFormatter; +import bisq.core.util.coin.CoinFormatter; +import bisq.core.util.validation.AltcoinValidator; +import bisq.core.util.validation.InputValidator; + +import bisq.common.Timer; +import bisq.common.UserThread; +import bisq.common.app.DevEnv; + +import org.bitcoinj.core.Coin; + +import javax.inject.Inject; +import javax.inject.Named; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.beans.value.ChangeListener; + +import java.util.concurrent.TimeUnit; + +import lombok.extern.slf4j.Slf4j; + +import static bisq.core.offer.bsq_swap.BsqSwapOfferModel.BSQ; + +@Slf4j +class BsqSwapCreateOfferViewModel extends BsqSwapOfferViewModel implements ViewModel { + private final BtcValidator btcValidator; + private final BsqValidator bsqValidator; + + private final AltcoinValidator altcoinValidator; + + private boolean createOfferRequested; + public final StringProperty amount = new SimpleStringProperty(); + public final StringProperty minAmount = new SimpleStringProperty(); + public final StringProperty price = new SimpleStringProperty(); + final StringProperty tradeFee = new SimpleStringProperty(); + public final StringProperty volume = new SimpleStringProperty(); + final StringProperty volumePromptLabel = new SimpleStringProperty(); + final StringProperty errorMessage = new SimpleStringProperty(); + + final BooleanProperty isPlaceOfferButtonDisabled = new SimpleBooleanProperty(true); + final BooleanProperty cancelButtonDisabled = new SimpleBooleanProperty(); + public final BooleanProperty isNextButtonDisabled = new SimpleBooleanProperty(true); + final BooleanProperty placeOfferCompleted = new SimpleBooleanProperty(); + final BooleanProperty miningPoW = new SimpleBooleanProperty(); + + final ObjectProperty amountValidationResult = new SimpleObjectProperty<>(); + final ObjectProperty minAmountValidationResult = new SimpleObjectProperty<>(); + final ObjectProperty priceValidationResult = new SimpleObjectProperty<>(); + final ObjectProperty volumeValidationResult = new SimpleObjectProperty<>(); + + private ChangeListener amountStringListener; + private ChangeListener minAmountStringListener; + private ChangeListener volumeStringListener; + + private ChangeListener amountAsCoinListener; + private ChangeListener minAmountAsCoinListener; + private ChangeListener priceListener; + private ChangeListener volumeListener; + + private ChangeListener errorMessageListener; + private Timer timeoutTimer; + private boolean ignoreVolumeStringListener, ignoreAmountStringListener; + private boolean syncMinAmountWithAmount = true; + private Timer miningPowTimer; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, lifecycle + /////////////////////////////////////////////////////////////////////////////////////////// + + @Inject + BsqSwapCreateOfferViewModel(BsqSwapCreateOfferDataModel dataModel, + AltcoinValidator altcoinValidator, + BtcValidator btcValidator, + BsqValidator bsqValidator, + AccountAgeWitnessService accountAgeWitnessService, + @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter, + BsqFormatter bsqFormatter) { + super(dataModel, btcFormatter, bsqFormatter, accountAgeWitnessService); + + this.altcoinValidator = altcoinValidator; + this.btcValidator = btcValidator; + this.bsqValidator = bsqValidator; + } + + @Override + protected void activate() { + if (DevEnv.isDevMode()) { + UserThread.runAfter(() -> { + amount.set("0.001"); + price.set("0.00002"); + minAmount.set(amount.get()); + applyTradeFee(); + setAmountToModel(); + setMinAmountToModel(); + setPriceToModel(); + dataModel.calculateVolume(); + dataModel.calculateInputAndPayout(); + updateButtonDisableState(); + }, 100, TimeUnit.MILLISECONDS); + } + + addBindings(); + addListeners(); + + updateButtonDisableState(); + } + + @Override + protected void deactivate() { + removeBindings(); + removeListeners(); + stopTimeoutTimer(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + void initWithData(OfferDirection direction) { + dataModel.initWithData(direction); + + btcValidator.setMaxValue(PaymentMethod.BSQ_SWAP.getMaxTradeLimitAsCoin(BSQ)); + btcValidator.setMaxTradeLimit(Coin.valueOf(dataModel.getMaxTradeLimit())); + btcValidator.setMinValue(Restrictions.getMinTradeAmount()); + + applyTradeFee(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // UI handler + /////////////////////////////////////////////////////////////////////////////////////////// + + void requestNewOffer() { + // We delay display a bit as pow is mostly very low so it would show flicker quickly + miningPowTimer = UserThread.runAfter(() -> { + miningPoW.set(true); + updateButtonDisableState(); + }, 200, TimeUnit.MILLISECONDS); + + + dataModel.requestNewOffer(offer -> { + if (miningPowTimer != null) { + miningPowTimer.stop(); + } + miningPoW.set(false); + updateButtonDisableState(); + }); + } + + void onPlaceOffer() { + errorMessage.set(null); + createOfferRequested = true; + + if (timeoutTimer == null) { + timeoutTimer = UserThread.runAfter(() -> { + stopTimeoutTimer(); + createOfferRequested = false; + errorMessage.set(Res.get("createOffer.timeoutAtPublishing")); + + updateButtonDisableState(); + }, 60); + } + errorMessageListener = (observable, oldValue, newValue) -> { + if (newValue != null) { + stopTimeoutTimer(); + createOfferRequested = false; + errorMessage.set(newValue); + + updateButtonDisableState(); + } + }; + + dataModel.offer.errorMessageProperty().addListener(errorMessageListener); + + dataModel.onPlaceOffer(() -> { + stopTimeoutTimer(); + placeOfferCompleted.set(true); + errorMessage.set(null); + }); + + updateButtonDisableState(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Focus + /////////////////////////////////////////////////////////////////////////////////////////// + + // On focus out we do validation and apply the data to the model + void onFocusOutAmountTextField(boolean oldValue, boolean newValue) { + if (oldValue && !newValue) { + InputValidator.ValidationResult result = isBtcInputValid(amount.get()); + amountValidationResult.set(result); + if (result.isValid) { + setAmountToModel(); + ignoreAmountStringListener = true; + amount.set(btcFormatter.formatCoin(dataModel.getBtcAmount().get())); + ignoreAmountStringListener = false; + dataModel.calculateVolume(); + + if (!dataModel.isMinAmountLessOrEqualAmount()) + minAmount.set(amount.get()); + else + amountValidationResult.set(result); + + if (minAmount.get() != null) + minAmountValidationResult.set(isBtcInputValid(minAmount.get())); + } else if (amount.get() != null && btcValidator.getMaxTradeLimit() != null && btcValidator.getMaxTradeLimit().value == OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT.value) { + amount.set(btcFormatter.formatCoin(btcValidator.getMaxTradeLimit())); + new Popup().information(Res.get("popup.warning.tradeLimitDueAccountAgeRestriction.buyer", + btcFormatter.formatCoinWithCode(OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT), + Res.get("offerbook.warning.newVersionAnnouncement"))) + .width(900) + .show(); + } + // We want to trigger a recalculation of the volume + UserThread.execute(() -> { + onFocusOutVolumeTextField(true, false); + onFocusOutMinAmountTextField(true, false); + }); + } + } + + void onFocusOutMinAmountTextField(boolean oldValue, boolean newValue) { + if (oldValue && !newValue) { + InputValidator.ValidationResult result = isBtcInputValid(minAmount.get()); + minAmountValidationResult.set(result); + if (result.isValid) { + Coin minAmountAsCoin = dataModel.getMinAmount().get(); + syncMinAmountWithAmount = minAmountAsCoin != null && + minAmountAsCoin.equals(dataModel.getBtcAmount().get()); + setMinAmountToModel(); + + dataModel.calculateMinVolume(); + + if (dataModel.getMinVolume().get() != null) { + InputValidator.ValidationResult minVolumeResult = isVolumeInputValid( + VolumeUtil.formatVolume(dataModel.getMinVolume().get())); + + volumeValidationResult.set(minVolumeResult); + + updateButtonDisableState(); + } + + this.minAmount.set(btcFormatter.formatCoin(minAmountAsCoin)); + + if (!dataModel.isMinAmountLessOrEqualAmount()) { + this.amount.set(this.minAmount.get()); + } else { + minAmountValidationResult.set(result); + if (this.amount.get() != null) + amountValidationResult.set(isBtcInputValid(this.amount.get())); + } + } else { + syncMinAmountWithAmount = true; + } + } + } + + void onFocusOutPriceTextField(boolean oldValue, boolean newValue) { + if (oldValue && !newValue) { + InputValidator.ValidationResult result = isPriceInputValid(price.get()); + priceValidationResult.set(result); + if (result.isValid) { + setPriceToModel(); + if (dataModel.getPrice().get() != null) + price.set(FormattingUtils.formatPrice(dataModel.getPrice().get())); + dataModel.calculateVolume(); + dataModel.calculateAmount(); + applyTradeFee(); + } + + // We want to trigger a recalculation of the volume and minAmount + UserThread.execute(() -> { + onFocusOutVolumeTextField(true, false); + triggerFocusOutOnAmountFields(); + }); + } + } + + void triggerFocusOutOnAmountFields() { + onFocusOutAmountTextField(true, false); + onFocusOutMinAmountTextField(true, false); + } + + void onFocusOutVolumeTextField(boolean oldValue, boolean newValue) { + if (oldValue && !newValue) { + InputValidator.ValidationResult result = isVolumeInputValid(volume.get()); + volumeValidationResult.set(result); + if (result.isValid) { + setVolumeToModel(); + ignoreVolumeStringListener = true; + + Volume volume = dataModel.getVolume().get(); + if (volume != null) { + this.volume.set(VolumeUtil.formatVolume(volume)); + } + + ignoreVolumeStringListener = false; + + dataModel.calculateAmount(); + + if (!dataModel.isMinAmountLessOrEqualAmount()) { + minAmount.set(amount.getValue()); + } else { + if (amount.get() != null) + amountValidationResult.set(isBtcInputValid(amount.get())); + + // We only check minAmountValidationResult if amountValidationResult is valid, otherwise we would get + // triggered a close of the popup when the minAmountValidationResult is applied + if (amountValidationResult.getValue() != null && amountValidationResult.getValue().isValid && minAmount.get() != null) + minAmountValidationResult.set(isBtcInputValid(minAmount.get())); + } + } + } + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Getters + /////////////////////////////////////////////////////////////////////////////////////////// + + private InputValidator.ValidationResult isBtcInputValid(String input) { + return btcValidator.validate(input); + } + + private InputValidator.ValidationResult isPriceInputValid(String input) { + return altcoinValidator.validate(input); + } + + private InputValidator.ValidationResult isVolumeInputValid(String input) { + return bsqValidator.validate(input); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Setters + /////////////////////////////////////////////////////////////////////////////////////////// + + private void applyTradeFee() { + tradeFee.set(getTradeFee()); + } + + private void setAmountToModel() { + if (amount.get() != null && !amount.get().isEmpty()) { + Coin amount = DisplayUtils.parseToCoinWith4Decimals(this.amount.get(), btcFormatter); + dataModel.setBtcAmount(amount); + if (syncMinAmountWithAmount || + dataModel.getMinAmount().get() == null || + dataModel.getMinAmount().get().equals(Coin.ZERO)) { + minAmount.set(this.amount.get()); + setMinAmountToModel(); + } + } else { + dataModel.setBtcAmount(null); + } + } + + private void setMinAmountToModel() { + if (minAmount.get() != null && !minAmount.get().isEmpty()) { + Coin minAmount = DisplayUtils.parseToCoinWith4Decimals(this.minAmount.get(), btcFormatter); + dataModel.setMinAmount(minAmount); + } else { + dataModel.setMinAmount(null); + } + } + + private void setPriceToModel() { + if (price.get() != null && !price.get().isEmpty()) { + try { + dataModel.setPrice(Price.parse(BSQ, this.price.get())); + } catch (Throwable t) { + log.debug(t.getMessage()); + } + } else { + dataModel.setPrice(null); + } + } + + private void setVolumeToModel() { + if (volume.get() != null && !volume.get().isEmpty()) { + try { + dataModel.setVolume(Volume.parse(volume.get(), BSQ)); + } catch (Throwable t) { + log.debug(t.getMessage()); + } + } else { + dataModel.setVolume(null); + } + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Bindings + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + protected void addBindings() { + super.addBindings(); + + volumePromptLabel.set(Res.get("createOffer.volume.prompt", BSQ)); + } + + @Override + protected void removeBindings() { + super.removeBindings(); + + volumePromptLabel.unbind(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Listeners + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + protected void createListeners() { + amountStringListener = (ov, oldValue, newValue) -> { + if (!ignoreAmountStringListener) { + if (isBtcInputValid(newValue).isValid) { + setAmountToModel(); + dataModel.calculateVolume(); + dataModel.calculateInputAndPayout(); + } + updateButtonDisableState(); + } + }; + minAmountStringListener = (ov, oldValue, newValue) -> { + if (isBtcInputValid(newValue).isValid) + setMinAmountToModel(); + updateButtonDisableState(); + }; + volumeStringListener = (ov, oldValue, newValue) -> { + if (!ignoreVolumeStringListener) { + if (isVolumeInputValid(newValue).isValid) { + setVolumeToModel(); + setPriceToModel(); + dataModel.calculateAmount(); + dataModel.calculateInputAndPayout(); + } + updateButtonDisableState(); + } + }; + + amountAsCoinListener = (ov, oldValue, newValue) -> { + if (newValue != null) { + amount.set(btcFormatter.formatCoin(newValue)); + } else { + amount.set(""); + } + + applyTradeFee(); + }; + minAmountAsCoinListener = (ov, oldValue, newValue) -> { + if (newValue != null) + minAmount.set(btcFormatter.formatCoin(newValue)); + else + minAmount.set(""); + }; + priceListener = (ov, oldValue, newValue) -> { + if (newValue != null) + price.set(FormattingUtils.formatPrice(newValue)); + else + price.set(""); + + applyTradeFee(); + }; + volumeListener = (ov, oldValue, newValue) -> { + ignoreVolumeStringListener = true; + if (newValue != null) + volume.set(VolumeUtil.formatVolume(newValue)); + else + volume.set(""); + + ignoreVolumeStringListener = false; + applyTradeFee(); + }; + } + + @Override + protected void addListeners() { + // Bidirectional bindings are used for all input fields: amount, price, volume and minAmount + // We do volume/amount calculation during input, so user has immediate feedback + amount.addListener(amountStringListener); + minAmount.addListener(minAmountStringListener); + volume.addListener(volumeStringListener); + + // Binding with Bindings.createObjectBinding does not work because of bi-directional binding + dataModel.getBtcAmount().addListener(amountAsCoinListener); + dataModel.getMinAmount().addListener(minAmountAsCoinListener); + dataModel.getPrice().addListener(priceListener); + dataModel.getVolume().addListener(volumeListener); + } + + @Override + protected void removeListeners() { + amount.removeListener(amountStringListener); + minAmount.removeListener(minAmountStringListener); + volume.removeListener(volumeStringListener); + + // Binding with Bindings.createObjectBinding does not work because of bi-directional binding + dataModel.getBtcAmount().removeListener(amountAsCoinListener); + dataModel.getMinAmount().removeListener(minAmountAsCoinListener); + dataModel.getPrice().removeListener(priceListener); + dataModel.getVolume().removeListener(volumeListener); + + if (dataModel.offer != null && errorMessageListener != null) + dataModel.offer.getErrorMessageProperty().removeListener(errorMessageListener); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Utils + /////////////////////////////////////////////////////////////////////////////////////////// + + private void updateButtonDisableState() { + boolean inputDataValid = isBtcInputValid(amount.get()).isValid && + isBtcInputValid(minAmount.get()).isValid && + isPriceInputValid(price.get()).isValid && + dataModel.getPrice().get() != null && + dataModel.getPrice().get().getValue() != 0 && + isVolumeInputValid(volume.get()).isValid && + isVolumeInputValid(VolumeUtil.formatVolume(dataModel.getMinVolume().get())).isValid && + dataModel.isMinAmountLessOrEqualAmount(); + + isNextButtonDisabled.set(!inputDataValid); + cancelButtonDisabled.set(createOfferRequested); + isPlaceOfferButtonDisabled.set(createOfferRequested || !inputDataValid || miningPoW.get()); + } + + private void stopTimeoutTimer() { + if (timeoutTimer != null) { + timeoutTimer.stop(); + timeoutTimer = null; + } + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/take_offer/BsqSwapTakeOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/take_offer/BsqSwapTakeOfferDataModel.java new file mode 100644 index 0000000000..1b85cd2f9c --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/take_offer/BsqSwapTakeOfferDataModel.java @@ -0,0 +1,124 @@ +/* + * 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.desktop.main.offer.bsq_swap.take_offer; + +import bisq.desktop.main.offer.bsq_swap.BsqSwapOfferDataModel; +import bisq.desktop.main.offer.offerbook.OfferBook; + +import bisq.core.offer.Offer; +import bisq.core.offer.bsq_swap.BsqSwapTakeOfferModel; +import bisq.core.trade.bisq_v1.TradeResultHandler; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.user.User; +import bisq.core.util.FormattingUtils; +import bisq.core.util.coin.CoinFormatter; + +import bisq.network.p2p.P2PService; + +import bisq.common.handlers.ErrorMessageHandler; + +import org.bitcoinj.core.Coin; + +import com.google.inject.Inject; + +import javax.inject.Named; + +import static com.google.common.base.Preconditions.checkNotNull; + +class BsqSwapTakeOfferDataModel extends BsqSwapOfferDataModel { + // We use the BsqSwapTakeOfferModel from core as delegate + // This contains all non UI specific domain aspects and is re-used from the API. + private final BsqSwapTakeOfferModel bsqSwapTakeOfferModel; + + private final OfferBook offerBook; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, lifecycle + /////////////////////////////////////////////////////////////////////////////////////////// + + @Inject + BsqSwapTakeOfferDataModel(BsqSwapTakeOfferModel bsqSwapTakeOfferModel, + OfferBook offerBook, + User user, + @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter, + P2PService p2PService) { + super(bsqSwapTakeOfferModel, + user, + p2PService, + btcFormatter); + this.bsqSwapTakeOfferModel = bsqSwapTakeOfferModel; + + this.offerBook = offerBook; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + void initWithData(Offer offer) { + bsqSwapTakeOfferModel.initWithData(offer); + } + + void onShowFeeInfoScreen() { + calculateInputAndPayout(); + } + + void removeOffer() { + offerBook.removeOffer(checkNotNull(getOffer())); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // UI actions + /////////////////////////////////////////////////////////////////////////////////////////// + + void onTakeOffer(TradeResultHandler tradeResultHandler, + ErrorMessageHandler warningHandler, + ErrorMessageHandler errorHandler) { + bsqSwapTakeOfferModel.onTakeOffer(tradeResultHandler, warningHandler, errorHandler, false); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Setter + /////////////////////////////////////////////////////////////////////////////////////////// + + void applyAmount(Coin amount) { + bsqSwapTakeOfferModel.applyAmount(amount); + setBtcAmount(Coin.valueOf(Math.min(amount.value, getMaxTradeLimit()))); + calculateVolume(); + calculateInputAndPayout(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Getters + /////////////////////////////////////////////////////////////////////////////////////////// + + boolean isAmountLargerThanOfferAmount() { + if (getBtcAmount().get() != null && getOffer() != null) + return getBtcAmount().get().isGreaterThan(getOffer().getAmount()); + return true; + } + + Offer getOffer() { + return bsqSwapTakeOfferModel.getOffer(); + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/take_offer/BsqSwapTakeOfferView.fxml b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/take_offer/BsqSwapTakeOfferView.fxml new file mode 100644 index 0000000000..0abbbfa98d --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/take_offer/BsqSwapTakeOfferView.fxml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/take_offer/BsqSwapTakeOfferView.java b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/take_offer/BsqSwapTakeOfferView.java new file mode 100644 index 0000000000..60024d1101 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/take_offer/BsqSwapTakeOfferView.java @@ -0,0 +1,607 @@ +/* + * 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.desktop.main.offer.bsq_swap.take_offer; + +import bisq.desktop.Navigation; +import bisq.desktop.common.view.FxmlView; +import bisq.desktop.components.AutoTooltipLabel; +import bisq.desktop.components.BusyAnimation; +import bisq.desktop.components.InfoInputTextField; +import bisq.desktop.components.InputTextField; +import bisq.desktop.components.TitledGroupBg; +import bisq.desktop.main.MainView; +import bisq.desktop.main.offer.bsq_swap.BsqSwapOfferView; +import bisq.desktop.main.overlays.popups.Popup; +import bisq.desktop.main.overlays.windows.BsqSwapOfferDetailsWindow; +import bisq.desktop.main.portfolio.PortfolioView; +import bisq.desktop.main.portfolio.bsqswaps.UnconfirmedBsqSwapsView; +import bisq.desktop.util.Layout; + +import bisq.core.locale.CurrencyUtil; +import bisq.core.locale.Res; +import bisq.core.offer.Offer; +import bisq.core.payment.PaymentAccount; +import bisq.core.payment.payload.PaymentMethod; +import bisq.core.user.DontShowAgainLookup; + +import bisq.common.UserThread; +import bisq.common.app.DevEnv; +import bisq.common.util.Tuple2; +import bisq.common.util.Tuple3; +import bisq.common.util.Tuple4; + +import org.bitcoinj.core.Coin; + +import javax.inject.Inject; + +import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon; + +import com.jfoenix.controls.JFXTextField; + +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; + +import javafx.geometry.Insets; +import javafx.geometry.Pos; + +import org.fxmisc.easybind.EasyBind; +import org.fxmisc.easybind.Subscription; + +import java.util.concurrent.TimeUnit; + +import static bisq.core.offer.bsq_swap.BsqSwapOfferModel.BSQ; +import static bisq.desktop.util.FormBuilder.*; + +@FxmlView +public class BsqSwapTakeOfferView extends BsqSwapOfferView { + private HBox minAmountHBox; + private Label offerAvailabilityLabel; + private TextField paymentMethodTextField, currencyTextField, priceTextField, + volumeTextField, minAmountTextField; + private BusyAnimation offerAvailabilityBusyAnimation; + private Subscription isTradeCompleteSubscription, showWarningInvalidBtcDecimalPlacesSubscription, + offerWarningSubscription, errorMessageSubscription, + isOfferAvailableSubscription; + private boolean offerDetailsWindowDisplayed, missingFundsPopupDisplayed; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, lifecycle + /////////////////////////////////////////////////////////////////////////////////////////// + + @Inject + public BsqSwapTakeOfferView(BsqSwapTakeOfferViewModel model, + Navigation navigation, + BsqSwapOfferDetailsWindow bsqSwapOfferDetailsWindow) { + super(model, navigation, bsqSwapOfferDetailsWindow); + } + + @Override + protected void initialize() { + super.initialize(); + + addOfferAvailabilityLabel(); + } + + @Override + protected void activate() { + super.activate(); + + addListeners(); + addBindings(); + addSubscriptions(); + + if (offerAvailabilityBusyAnimation != null) { + // temporarily disabled due to high CPU usage (per issue #4649) + // offerAvailabilityBusyAnimation.play(); + offerAvailabilityLabel.setVisible(true); + offerAvailabilityLabel.setManaged(true); + } else { + offerAvailabilityLabel.setVisible(false); + offerAvailabilityLabel.setManaged(false); + } + + if (!missingFundsPopupDisplayed) { + String key = "BsqSwapTakerInfo"; + if (DontShowAgainLookup.showAgain(key)) { + new Popup().information(Res.get("bsqSwapOffer.feeHandling")) + .width(1000) + .closeButtonText(Res.get("shared.iUnderstand")) + .dontShowAgainId(key) + .show(); + } + } + } + + @Override + protected void deactivate() { + super.deactivate(); + + removeListeners(); + removeBindings(); + removeSubscriptions(); + + if (offerAvailabilityBusyAnimation != null) { + offerAvailabilityBusyAnimation.stop(); + } + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + public void initWithData(Offer offer) { + model.initWithData(offer); + + if (model.dataModel.isSellOffer()) { + actionButton.setId("buy-button-big"); + actionButton.updateText(Res.get("takeOffer.takeOfferButton", Res.get("shared.buy"))); + nextButton.setId("buy-button"); + volumeDescriptionLabel.setText(Res.get("createOffer.amountPriceBox.buy.volumeDescription", BSQ)); + amountDescriptionLabel.setText(Res.get("takeOffer.amountPriceBox.sell.amountDescription")); + } else { + actionButton.setId("sell-button-big"); + nextButton.setId("sell-button"); + actionButton.updateText(Res.get("takeOffer.takeOfferButton", Res.get("shared.sell"))); + volumeDescriptionLabel.setText(Res.get("createOffer.amountPriceBox.sell.volumeDescription", BSQ)); + amountDescriptionLabel.setText(Res.get("takeOffer.amountPriceBox.buy.amountDescription")); + } + + paymentMethodTextField.setText(PaymentMethod.BSQ_SWAP.getDisplayString()); + currencyTextField.setText(CurrencyUtil.getNameByCode(BSQ)); + + if (model.isRange()) { + minAmountTextField.setText(model.amountRange); + minAmountHBox.setVisible(true); + minAmountHBox.setManaged(true); + } else { + amountTextField.setDisable(true); + } + + priceTextField.setText(model.price); + + if (!model.isRange()) { + model.dataModel.getMissingFunds().addListener(missingFundsListener); + checkForMissingFunds(model.dataModel.getMissingFunds().get()); + } + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // UI actions + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void onTabSelected(boolean isSelected) { + model.dataModel.onTabSelected(isSelected); + } + + @Override + protected void onCancel1() { + close(); + } + + @Override + protected void onShowFeeInfoScreen() { + super.onShowFeeInfoScreen(); + + offerAvailabilityBusyAnimation.stop(); + offerAvailabilityBusyAnimation.setVisible(false); + offerAvailabilityBusyAnimation.setManaged(false); + + offerAvailabilityLabel.setVisible(false); + offerAvailabilityLabel.setManaged(false); + + amountTextField.setMouseTransparent(true); + amountTextField.setDisable(false); + amountTextField.setFocusTraversable(false); + + minAmountTextField.setMouseTransparent(true); + minAmountTextField.setDisable(false); + minAmountTextField.setFocusTraversable(false); + + priceTextField.setMouseTransparent(true); + priceTextField.setDisable(false); + priceTextField.setFocusTraversable(false); + + volumeTextField.setMouseTransparent(true); + volumeTextField.setDisable(false); + volumeTextField.setFocusTraversable(false); + + actionButtonBar.setManaged(true); + actionButtonBar.setVisible(true); + + inputAmountTextField.setFundsStructure(model.getInputAmountDetails()); + inputAmountTextField.setContentForInfoPopOver(createInputAmountDetailsPopover()); + + payoutAmountTextField.setFundsStructure(model.getPayoutAmountDetails()); + payoutAmountTextField.setContentForInfoPopOver(createPayoutAmountDetailsPopover()); + + model.dataModel.onShowFeeInfoScreen(); + + if (model.isRange()) { + model.dataModel.getMissingFunds().addListener(missingFundsListener); + checkForMissingFunds(model.dataModel.getMissingFunds().get()); + } else if (model.dataModel.hasMissingFunds()) { + maybeShowMissingFundsPopup(); + } + } + + @Override + protected void onAction() { + if (!model.dataModel.canPlaceOrTakeOffer()) { + return; + } + + if (model.dataModel.hasMissingFunds()) { + maybeShowMissingFundsPopup(); + return; + } + + if (DevEnv.isDevMode()) { + model.onTakeOffer(() -> { + }, warningMessage -> { + log.warn(warningMessage); + new Popup().warning(warningMessage).show(); + }, + errorMessage -> { + log.error(errorMessage); + new Popup().warning(errorMessage).show(); + }); + // JFXComboBox causes a bug with requesting focus. Not clear why that happens but requesting a focus + // on our view here avoids that the currency List overlay gets displayed. + requestFocus(); + return; + } + + bsqSwapOfferDetailsWindow.onTakeOffer(() -> { + if (model.dataModel.hasMissingFunds()) { + maybeShowMissingFundsPopup(); + return; + } + + model.onTakeOffer(() -> { + offerDetailsWindowDisplayed = false; + model.dataModel.getMissingFunds().removeListener(missingFundsListener); + }, warningMessage -> { + log.warn(warningMessage); + new Popup().warning(warningMessage).show(); + }, + errorMessage -> { + log.error(errorMessage); + new Popup().warning(errorMessage).show(); + }); + requestFocus(); + }).show(model.offer, model.dataModel.getBtcAmount().get(), model.dataModel.getPrice().get()); + + offerDetailsWindowDisplayed = true; + } + + + @Override + protected void onCancel2() { + close(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Bindings, Listeners + /////////////////////////////////////////////////////////////////////////////////////////// + @Override + protected void createListeners() { + super.createListeners(); + + amountFocusedListener = (o, oldValue, newValue) -> { + model.onFocusOutAmountTextField(oldValue, newValue, amountTextField.getText()); + amountTextField.setText(model.amount.get()); + }; + } + + @Override + protected void addListeners() { + amountTextField.focusedProperty().addListener(amountFocusedListener); + } + + @Override + protected void removeListeners() { + super.removeListeners(); + + amountTextField.focusedProperty().removeListener(amountFocusedListener); + } + + @Override + protected void addBindings() { + amountTextField.textProperty().bindBidirectional(model.amount); + volumeTextField.textProperty().bindBidirectional(model.volume); + amountTextField.validationResultProperty().bind(model.amountValidationResult); + + inputAmountTextField.textProperty().bind(model.getInputAmount()); + payoutAmountTextField.textProperty().bind(model.getPayoutAmount()); + + nextButton.disableProperty().bind(model.isNextButtonDisabled); + actionButton.disableProperty().bind(model.isTakeOfferButtonDisabled); + cancelButton2.disableProperty().bind(model.cancelButtonDisabled); + + } + + @Override + protected void removeBindings() { + amountTextField.textProperty().unbindBidirectional(model.amount); + volumeTextField.textProperty().unbindBidirectional(model.volume); + amountTextField.validationResultProperty().unbind(); + + inputAmountTextField.textProperty().unbind(); + payoutAmountTextField.textProperty().unbind(); + + nextButton.disableProperty().unbind(); + actionButton.disableProperty().unbind(); + cancelButton2.disableProperty().unbind(); + } + + @Override + protected void addSubscriptions() { + offerWarningSubscription = EasyBind.subscribe(model.offerWarning, warning -> { + if (warning != null) { + if (offerDetailsWindowDisplayed) { + bsqSwapOfferDetailsWindow.hide(); + } + + UserThread.runAfter(() -> new Popup().warning(warning) + .onClose(() -> { + model.resetOfferWarning(); + close(); + }) + .show(), 100, TimeUnit.MILLISECONDS); + } + }); + + errorMessageSubscription = EasyBind.subscribe(model.errorMessage, newValue -> { + if (newValue == null) { + return; + } + new Popup().error(Res.get("takeOffer.error.message", model.errorMessage.get()) + "\n\n" + + Res.get("popup.error.tryRestart")) + .onClose(() -> { + model.resetErrorMessage(); + model.dataModel.removeOffer(); + close(); + }) + .show(); + }); + + isOfferAvailableSubscription = EasyBind.subscribe(model.isOfferAvailable, isOfferAvailable -> { + if (isOfferAvailable) { + offerAvailabilityBusyAnimation.stop(); + offerAvailabilityBusyAnimation.setVisible(false); + } + + offerAvailabilityLabel.setVisible(!isOfferAvailable); + offerAvailabilityLabel.setManaged(!isOfferAvailable); + }); + + showWarningInvalidBtcDecimalPlacesSubscription = EasyBind.subscribe(model.showWarningInvalidBtcDecimalPlaces, newValue -> { + if (newValue) { + new Popup().warning(Res.get("takeOffer.amountPriceBox.warning.invalidBtcDecimalPlaces")).show(); + model.showWarningInvalidBtcDecimalPlaces.set(false); + } + }); + + isTradeCompleteSubscription = EasyBind.subscribe(model.isTradeComplete, newValue -> { + if (!newValue) { + return; + } + + model.dataModel.removeOffer(); + + new Popup().headLine(Res.get("takeOffer.bsqSwap.success.headline")) + .feedback(Res.get("takeOffer.bsqSwap.success.info")) + .actionButtonTextWithGoTo("navigation.portfolio.bsqSwapTrades") + .width(730) + .onAction(() -> { + UserThread.runAfter( + () -> navigation.navigateTo(MainView.class, PortfolioView.class, UnconfirmedBsqSwapsView.class), + 100, TimeUnit.MILLISECONDS); + close(); + }) + .onClose(this::close) + .show(); + }); + } + + @Override + protected void removeSubscriptions() { + offerWarningSubscription.unsubscribe(); + errorMessageSubscription.unsubscribe(); + isOfferAvailableSubscription.unsubscribe(); + showWarningInvalidBtcDecimalPlacesSubscription.unsubscribe(); + isTradeCompleteSubscription.unsubscribe(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Build UI elements + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + protected void addPaymentAccountGroup() { + paymentAccountTitledGroupBg = addTitledGroupBg(gridPane, gridRow, 1, Res.get("takeOffer.paymentInfo")); + GridPane.setColumnSpan(paymentAccountTitledGroupBg, 2); + + // We use the addComboBoxTopLabelTextField only for convenience for having the expected layout + Tuple4, Label, TextField, HBox> paymentAccountTuple = addComboBoxTopLabelTextField(gridPane, + gridRow, "", Res.get("shared.paymentMethod"), Layout.FIRST_ROW_DISTANCE); + HBox hBox = paymentAccountTuple.fourth; + hBox.getChildren().remove(paymentAccountTuple.first); + + paymentMethodTextField = paymentAccountTuple.third; + paymentMethodTextField.setMinWidth(250); + paymentMethodTextField.setEditable(false); + paymentMethodTextField.setMouseTransparent(true); + paymentMethodTextField.setFocusTraversable(false); + + currencyTextField = new JFXTextField(); + currencyTextField.setMinWidth(250); + currencyTextField.setEditable(false); + currencyTextField.setMouseTransparent(true); + currencyTextField.setFocusTraversable(false); + + Tuple2 tradeCurrencyTuple = getTopLabelWithVBox(Res.get("shared.tradeCurrency"), currencyTextField); + VBox vBox = tradeCurrencyTuple.second; + HBox.setMargin(vBox, new Insets(5, 0, 0, 0)); + + hBox.setSpacing(30); + hBox.setAlignment(Pos.CENTER_LEFT); + hBox.setPadding(new Insets(10, 0, 18, 0)); + hBox.getChildren().add(vBox); + } + + @Override + protected void addAmountPriceGroup() { + TitledGroupBg titledGroupBg = addTitledGroupBg(gridPane, ++gridRow, 2, + Res.get("takeOffer.setAmountPrice"), Layout.COMPACT_GROUP_DISTANCE); + GridPane.setColumnSpan(titledGroupBg, 2); + + addFirstRow(); + addSecondRow(); + } + + private void addFirstRow() { + // amountBox + Tuple3 amountValueCurrencyBoxTuple = getEditableValueBox(Res.get("takeOffer.amount.prompt")); + amountValueCurrencyBox = amountValueCurrencyBoxTuple.first; + amountTextField = amountValueCurrencyBoxTuple.second; + Tuple2 amountInputBoxTuple = getTradeInputBox(amountValueCurrencyBox, ""); + amountDescriptionLabel = amountInputBoxTuple.first; + VBox amountBox = amountInputBoxTuple.second; + + // x + xLabel = new Label(); + xIcon = getIconForLabel(MaterialDesignIcon.CLOSE, "2em", xLabel); + xIcon.getStyleClass().add("opaque-icon"); + xLabel.getStyleClass().addAll("opaque-icon-character"); + + // price + Tuple3 priceValueCurrencyBoxTuple = getNonEditableValueBox(); + priceValueCurrencyBox = priceValueCurrencyBoxTuple.first; + priceTextField = priceValueCurrencyBoxTuple.second; + priceCurrencyLabel = priceValueCurrencyBoxTuple.third; + priceCurrencyLabel.setText("BTC"); + Tuple2 priceInputBoxTuple = getTradeInputBox(priceValueCurrencyBox, + Res.get("takeOffer.amountPriceBox.priceDescription")); + priceDescriptionLabel = priceInputBoxTuple.first; + priceDescriptionLabel.setText(CurrencyUtil.getPriceWithCurrencyCode(BSQ)); + + getSmallIconForLabel(MaterialDesignIcon.LOCK, priceDescriptionLabel, "small-icon-label"); + + VBox priceBox = priceInputBoxTuple.second; + + // = + resultLabel = new AutoTooltipLabel("="); + resultLabel.getStyleClass().addAll("opaque-icon-character"); + + // volume + Tuple3 volumeValueCurrencyBoxTuple = getNonEditableValueBoxWithInfo(); + volumeValueCurrencyBox = volumeValueCurrencyBoxTuple.first; + + InfoInputTextField volumeInfoTextField = volumeValueCurrencyBoxTuple.second; + volumeTextField = volumeInfoTextField.getInputTextField(); + volumeCurrencyLabel = volumeValueCurrencyBoxTuple.third; + volumeCurrencyLabel.setText(BSQ); + Tuple2 volumeInputBoxTuple = getTradeInputBox(volumeValueCurrencyBox, ""); + volumeDescriptionLabel = volumeInputBoxTuple.first; + VBox volumeBox = volumeInputBoxTuple.second; + + firstRowHBox = new HBox(); + firstRowHBox.setSpacing(5); + firstRowHBox.setAlignment(Pos.CENTER_LEFT); + firstRowHBox.getChildren().addAll(amountBox, xLabel, priceBox, resultLabel, volumeBox); + GridPane.setColumnSpan(firstRowHBox, 2); + GridPane.setRowIndex(firstRowHBox, gridRow); + GridPane.setMargin(firstRowHBox, new Insets(Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE, 10, 0, 0)); + gridPane.getChildren().add(firstRowHBox); + } + + private void addSecondRow() { + Tuple3 amountValueCurrencyBoxTuple = getNonEditableValueBox(); + minAmountValueCurrencyBox = amountValueCurrencyBoxTuple.first; + minAmountTextField = amountValueCurrencyBoxTuple.second; + + Tuple2 amountInputBoxTuple = getTradeInputBox(minAmountValueCurrencyBox, + Res.get("takeOffer.amountPriceBox.amountRangeDescription")); + + VBox minAmountBox = amountInputBoxTuple.second; + minAmountHBox = new HBox(); + minAmountHBox.setSpacing(5); + minAmountHBox.setAlignment(Pos.CENTER_LEFT); + minAmountHBox.getChildren().add(minAmountBox); + + GridPane.setRowIndex(minAmountHBox, ++gridRow); + GridPane.setMargin(minAmountHBox, new Insets(0, 10, 10, 0)); + gridPane.getChildren().add(minAmountHBox); + + minAmountHBox.setVisible(false); + minAmountHBox.setManaged(false); + } + + private void addOfferAvailabilityLabel() { + offerAvailabilityBusyAnimation = new BusyAnimation(false); + offerAvailabilityLabel = new AutoTooltipLabel(Res.get("takeOffer.fundsBox.isOfferAvailable")); + HBox.setMargin(offerAvailabilityLabel, new Insets(6, 0, 0, 0)); + nextButtonBar.getChildren().addAll(offerAvailabilityBusyAnimation, offerAvailabilityLabel); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Utils + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + protected void updateOfferElementsStyle() { + super.updateOfferElementsStyle(); + + GridPane.setColumnSpan(firstRowHBox, 1); + } + + @Override + protected void checkForMissingFunds(Coin missing) { + if (missing.isPositive()) { + maybeShowMissingFundsPopup(); + } + } + + private void maybeShowMissingFundsPopup() { + if (!isMissingFundsPopupOpen) { + isMissingFundsPopupOpen = true; + missingFundsPopupDisplayed = true; + String wallet = model.dataModel.isBuyer() ? "BSQ" : "BTC"; + String warning = Res.get("createOffer.bsqSwap.missingFunds.taker", + wallet, model.getMissingFunds(model.dataModel.getMissingFunds().get())); + new Popup().warning(warning) + .onClose(() -> { + isMissingFundsPopupOpen = false; + close(); + }) + .show(); + } + } +} + diff --git a/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/take_offer/BsqSwapTakeOfferViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/take_offer/BsqSwapTakeOfferViewModel.java new file mode 100644 index 0000000000..48e86c6244 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/take_offer/BsqSwapTakeOfferViewModel.java @@ -0,0 +1,441 @@ +/* + * 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.desktop.main.offer.bsq_swap.take_offer; + +import bisq.desktop.Navigation; +import bisq.desktop.main.offer.bsq_swap.BsqSwapOfferViewModel; +import bisq.desktop.main.overlays.popups.Popup; +import bisq.desktop.util.DisplayUtils; +import bisq.desktop.util.validation.BtcValidator; + +import bisq.core.account.witness.AccountAgeWitnessService; +import bisq.core.locale.Res; +import bisq.core.offer.Offer; +import bisq.core.offer.OfferRestrictions; +import bisq.core.offer.OfferUtil; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.util.FormattingUtils; +import bisq.core.util.VolumeUtil; +import bisq.core.util.coin.BsqFormatter; +import bisq.core.util.coin.CoinFormatter; +import bisq.core.util.validation.InputValidator; + +import bisq.network.p2p.P2PService; +import bisq.network.p2p.network.CloseConnectionReason; +import bisq.network.p2p.network.Connection; +import bisq.network.p2p.network.ConnectionListener; + +import bisq.common.handlers.ErrorMessageHandler; + +import org.bitcoinj.core.Coin; + +import javax.inject.Inject; +import javax.inject.Named; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.beans.value.ChangeListener; + +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkNotNull; +import static javafx.beans.binding.Bindings.createStringBinding; + +@Slf4j +class BsqSwapTakeOfferViewModel extends BsqSwapOfferViewModel { + private final BtcValidator btcValidator; + private final P2PService p2PService; + + String amountRange; + private boolean takeOfferRequested; + BsqSwapTrade trade; + Offer offer; + String price; + + final StringProperty amount = new SimpleStringProperty(); + final StringProperty volume = new SimpleStringProperty(); + final StringProperty errorMessage = new SimpleStringProperty(); + final StringProperty offerWarning = new SimpleStringProperty(); + + final BooleanProperty isOfferAvailable = new SimpleBooleanProperty(); + final BooleanProperty isTakeOfferButtonDisabled = new SimpleBooleanProperty(true); + final BooleanProperty cancelButtonDisabled = new SimpleBooleanProperty(); + final BooleanProperty isNextButtonDisabled = new SimpleBooleanProperty(true); + final BooleanProperty showWarningInvalidBtcDecimalPlaces = new SimpleBooleanProperty(); + final BooleanProperty isTradeComplete = new SimpleBooleanProperty(); + final BooleanProperty takeOfferCompleted = new SimpleBooleanProperty(); + + final ObjectProperty amountValidationResult = new SimpleObjectProperty<>(); + + private ChangeListener amountListener; + private ChangeListener amountAsCoinListener; + private ChangeListener tradeStateListener; + private ChangeListener tradeErrorListener; + private ChangeListener offerStateListener; + private ChangeListener offerErrorListener; + private ConnectionListener connectionListener; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, lifecycle + /////////////////////////////////////////////////////////////////////////////////////////// + + @Inject + BsqSwapTakeOfferViewModel(BsqSwapTakeOfferDataModel dataModel, + OfferUtil offerUtil, + BtcValidator btcValidator, + P2PService p2PService, + AccountAgeWitnessService accountAgeWitnessService, + Navigation navigation, + @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter, + BsqFormatter bsqFormatter) { + super(dataModel, btcFormatter, bsqFormatter, accountAgeWitnessService); + this.btcValidator = btcValidator; + this.p2PService = p2PService; + + createListeners(); + } + + @Override + protected void activate() { + addBindings(); + addListeners(); + + amount.set(btcFormatter.formatCoin(dataModel.getBtcAmount().get())); + isTradeComplete.set(false); + + // when getting back to an open screen we want to re-check again + isOfferAvailable.set(false); + checkNotNull(offer, "offer must not be null"); + + offer.stateProperty().addListener(offerStateListener); + applyOfferState(offer.stateProperty().get()); + + updateButtonDisableState(); + } + + @Override + protected void deactivate() { + removeBindings(); + removeListeners(); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + // called before doActivate + void initWithData(Offer offer) { + dataModel.initWithData(offer); + this.offer = offer; + + amountRange = btcFormatter.formatCoin(offer.getMinAmount()) + " - " + btcFormatter.formatCoin(offer.getAmount()); + price = FormattingUtils.formatPrice(dataModel.getPrice().get()); + + offerErrorListener = (observable, oldValue, newValue) -> { + if (newValue != null) + errorMessage.set(newValue); + }; + offer.errorMessageProperty().addListener(offerErrorListener); + errorMessage.set(offer.getErrorMessage()); + + btcValidator.setMaxValue(offer.getAmount()); + btcValidator.setMaxTradeLimit(Coin.valueOf(Math.min(dataModel.getMaxTradeLimit(), offer.getAmount().value))); + btcValidator.setMinValue(offer.getMinAmount()); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // UI handler + /////////////////////////////////////////////////////////////////////////////////////////// + + void onTakeOffer(Runnable resultHandler, + ErrorMessageHandler warningHandler, + ErrorMessageHandler errorHandler) { + takeOfferRequested = true; + isTradeComplete.set(false); + dataModel.onTakeOffer(trade -> { + this.trade = trade; + trade.stateProperty().addListener(tradeStateListener); + onTradeState(trade.getState()); + trade.errorMessageProperty().addListener(tradeErrorListener); + applyTradeErrorMessage(trade.getErrorMessage()); + takeOfferCompleted.set(true); + resultHandler.run(); + }, + warningHandler, + errorHandler); + + updateButtonDisableState(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Focus handler + /////////////////////////////////////////////////////////////////////////////////////////// + + // On focus out we do validation and apply the data to the model + void onFocusOutAmountTextField(boolean oldValue, boolean newValue, String userInput) { + if (oldValue && !newValue) { + InputValidator.ValidationResult result = isBtcInputValid(amount.get()); + amountValidationResult.set(result); + if (result.isValid) { + showWarningInvalidBtcDecimalPlaces.set(!DisplayUtils.hasBtcValidDecimals(userInput, btcFormatter)); + // only allow max 4 decimal places for btc values + setAmountToModel(); + // reformat input + amount.set(btcFormatter.formatCoin(dataModel.getBtcAmount().get())); + + calculateVolume(); + + if (!dataModel.isMinAmountLessOrEqualAmount()) + amountValidationResult.set(new InputValidator.ValidationResult(false, + Res.get("takeOffer.validation.amountSmallerThanMinAmount"))); + + if (dataModel.isAmountLargerThanOfferAmount()) + amountValidationResult.set(new InputValidator.ValidationResult(false, + Res.get("takeOffer.validation.amountLargerThanOfferAmount"))); + } else if (btcValidator.getMaxTradeLimit() != null && btcValidator.getMaxTradeLimit().value == OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT.value) { + if (dataModel.isBuyOffer()) { + new Popup().information(Res.get("popup.warning.tradeLimitDueAccountAgeRestriction.seller", + btcFormatter.formatCoinWithCode(OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT), + Res.get("offerbook.warning.newVersionAnnouncement"))) + .width(900) + .show(); + } else { + new Popup().information(Res.get("popup.warning.tradeLimitDueAccountAgeRestriction.buyer", + btcFormatter.formatCoinWithCode(OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT), + Res.get("offerbook.warning.newVersionAnnouncement"))) + .width(900) + .show(); + } + } + } + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Bindings, listeners + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + protected void createListeners() { + amountListener = (ov, oldValue, newValue) -> { + if (isBtcInputValid(newValue).isValid) { + setAmountToModel(); + calculateVolume(); + dataModel.calculateInputAndPayout(); + } + updateButtonDisableState(); + }; + amountAsCoinListener = (ov, oldValue, newValue) -> { + amount.set(btcFormatter.formatCoin(newValue)); + }; + + tradeStateListener = (ov, oldValue, newValue) -> onTradeState(newValue); + tradeErrorListener = (ov, oldValue, newValue) -> applyTradeErrorMessage(newValue); + offerStateListener = (ov, oldValue, newValue) -> applyOfferState(newValue); + + connectionListener = new ConnectionListener() { + @Override + public void onDisconnect(CloseConnectionReason closeConnectionReason, Connection connection) { + if (connection.getPeersNodeAddressOptional().isPresent() && + connection.getPeersNodeAddressOptional().get().equals(offer.getMakerNodeAddress())) { + offerWarning.set(Res.get("takeOffer.warning.connectionToPeerLost")); + } + } + + @Override + public void onConnection(Connection connection) { + } + + @Override + public void onError(Throwable throwable) { + } + }; + } + + @Override + protected void addListeners() { + // Bidirectional bindings are used for all input fields: amount, price, volume and minAmount + // We do volume/amount calculation during input, so user has immediate feedback + amount.addListener(amountListener); + + // Binding with Bindings.createObjectBinding does not work because of bi-directional binding + dataModel.getBtcAmount().addListener(amountAsCoinListener); + + p2PService.getNetworkNode().addConnectionListener(connectionListener); + } + + @Override + protected void removeListeners() { + amount.removeListener(amountListener); + + // Binding with Bindings.createObjectBinding does not work because of bi-directional binding + dataModel.getBtcAmount().removeListener(amountAsCoinListener); + + if (offer != null) { + offer.stateProperty().removeListener(offerStateListener); + offer.errorMessageProperty().removeListener(offerErrorListener); + } + + if (trade != null) { + trade.stateProperty().removeListener(tradeStateListener); + trade.errorMessageProperty().removeListener(tradeErrorListener); + } + p2PService.getNetworkNode().removeConnectionListener(connectionListener); + } + + @Override + protected void addBindings() { + super.addBindings(); + + volume.bind(createStringBinding(() -> VolumeUtil.formatVolume(dataModel.getVolume().get()), dataModel.getVolume())); + } + + @Override + protected void removeBindings() { + super.removeBindings(); + + volume.unbind(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // States + /////////////////////////////////////////////////////////////////////////////////////////// + + private void applyOfferState(Offer.State state) { + offerWarning.set(null); + + // We have 2 situations handled here: + // 1. when clicking take offer in the offerbook screen, we do the availability check + // 2. Before actually taking the offer in the take offer screen, we check again the availability as some time might have passed in the meantime + // So we use the takeOfferRequested flag to display different network_messages depending on the context. + switch (state) { + case UNKNOWN: + break; + case OFFER_FEE_PAID: + // irrelevant for taker + break; + case AVAILABLE: + isOfferAvailable.set(true); + updateButtonDisableState(); + break; + case NOT_AVAILABLE: + if (takeOfferRequested) + offerWarning.set(Res.get("takeOffer.failed.offerNotAvailable")); + else + offerWarning.set(Res.get("takeOffer.failed.offerTaken")); + break; + case REMOVED: + if (!takeOfferRequested) + offerWarning.set(Res.get("takeOffer.failed.offerRemoved")); + + break; + case MAKER_OFFLINE: + if (takeOfferRequested) + offerWarning.set(Res.get("takeOffer.failed.offererNotOnline")); + else + offerWarning.set(Res.get("takeOffer.failed.offererOffline")); + break; + default: + log.error("Unhandled offer state: " + state); + break; + } + + updateButtonDisableState(); + } + + void resetOfferWarning() { + offerWarning.set(null); + } + + private void applyTradeErrorMessage(@Nullable String errorMessage) { + this.errorMessage.set(errorMessage); + if (errorMessage == null) { + return; + } + log.warn(errorMessage); + trade.setState(BsqSwapTrade.State.FAILED); + } + + private void onTradeState(BsqSwapTrade.State state) { + switch (state) { + case PREPARATION: + break; + case COMPLETED: + isTradeComplete.set(trade.isCompleted()); + break; + case FAILED: + break; + } + } + + private void updateButtonDisableState() { + boolean inputDataValid = isBtcInputValid(amount.get()).isValid + && dataModel.isMinAmountLessOrEqualAmount() + && !dataModel.isAmountLargerThanOfferAmount() + && isOfferAvailable.get(); + isNextButtonDisabled.set(!inputDataValid); + cancelButtonDisabled.set(takeOfferRequested); + isTakeOfferButtonDisabled.set(takeOfferRequested || !inputDataValid); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Setters + /////////////////////////////////////////////////////////////////////////////////////////// + + private void calculateVolume() { + setAmountToModel(); + } + + private void setAmountToModel() { + if (amount.get() != null && !amount.get().isEmpty()) { + Coin amount = DisplayUtils.parseToCoinWith4Decimals(this.amount.get(), btcFormatter); + dataModel.applyAmount(amount); + } + } + + public void resetErrorMessage() { + offer.setErrorMessage(null); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Getters + /////////////////////////////////////////////////////////////////////////////////////////// + + private InputValidator.ValidationResult isBtcInputValid(String input) { + return btcValidator.validate(input); + } + + public boolean isRange() { + return dataModel.getOffer().isRange(); + } + + public String getTradeFee() { + return bsqFormatter.formatCoinWithCode(dataModel.getTradeFee()); + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBook.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBook.java index 1632e6f35b..e8bbc457e3 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBook.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBook.java @@ -39,7 +39,7 @@ import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; -import static bisq.core.offer.OfferPayload.Direction.BUY; +import static bisq.core.offer.OfferDirection.BUY; /** * Holds and manages the unsorted and unfiltered offerbook list (except for banned offers) of both buy and sell offers. @@ -57,6 +57,7 @@ public class OfferBook { private final Map sellOfferCountMap = new HashMap<>(); private final FilterManager filterManager; + /////////////////////////////////////////////////////////////////////////////////////////// // Constructor /////////////////////////////////////////////////////////////////////////////////////////// @@ -80,6 +81,11 @@ public class OfferBook { return; } + if (offer.isBsqSwapOffer() && !filterManager.isProofOfWorkValid(offer)) { + log.info("Proof of work of offer with id {} is not valid.", offer.getId()); + return; + } + if (OfferRestrictions.requiresNodeAddressUpdate() && !Utils.isV3Address(offer.getMakerNodeAddress().getHostName())) { log.debug("Ignored offer with Tor v2 node address. ID={}", offer.getId()); return; @@ -112,6 +118,20 @@ public class OfferBook { printOfferBookListItems("After onRemoved"); } }); + + filterManager.filterProperty().addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + onProofOfWorkDifficultyChanged(); + } + }); + } + + private void onProofOfWorkDifficultyChanged() { + List toRemove = offerBookListItems.stream() + .filter(item -> item.getOffer().isBsqSwapOffer()) + .filter(item -> !filterManager.isProofOfWorkValid(item.getOffer())) + .collect(Collectors.toList()); + toRemove.forEach(offerBookListItems::remove); } private void removeDuplicateItem(OfferBookListItem newOfferBookListItem) { @@ -139,7 +159,7 @@ public class OfferBook { offer.setState(Offer.State.REMOVED); offer.cancelAvailabilityRequest(); - P2PDataStorage.ByteArray hashOfPayload = new P2PDataStorage.ByteArray(offer.getOfferPayload().getHash()); + P2PDataStorage.ByteArray hashOfPayload = new P2PDataStorage.ByteArray(offer.getOfferPayloadHash()); if (log.isDebugEnabled()) { // TODO delete debug stmt in future PR. log.debug("onRemoved: id = {}\n" @@ -164,7 +184,6 @@ public class OfferBook { } OfferBookListItem candidate = candidateWithMatchingPayloadHash.get(); - // Remove the candidate only if the candidate's offer payload the hash matches the // onRemoved hashOfPayload parameter. We may receive add/remove messages out of // order from the API's 'editoffer' method, and use the offer payload hash to @@ -201,7 +220,8 @@ public class OfferBook { // Investigate why.... offerBookListItems.clear(); offerBookListItems.addAll(offerBookService.getOffers().stream() - .filter(o -> isOfferAllowed(o)) + .filter(this::isOfferAllowed) + .filter(offer -> !offer.isBsqSwapOffer() || filterManager.isProofOfWorkValid(offer)) .map(OfferBookListItem::new) .collect(Collectors.toList())); diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookListItem.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookListItem.java index 2c56defd38..ff0f08cbc3 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookListItem.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookListItem.java @@ -66,7 +66,7 @@ public class OfferBookListItem { public OfferBookListItem(Offer offer) { this.offer = offer; - this.hashOfPayload = new P2PDataStorage.ByteArray(offer.getOfferPayload().getHash()); + this.hashOfPayload = new P2PDataStorage.ByteArray(offer.getOfferPayloadHash()); } public WitnessAgeData getWitnessAgeData(AccountAgeWitnessService accountAgeWitnessService, diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java index c6f8b40ebf..5c60d5f9b7 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java @@ -38,6 +38,7 @@ import bisq.desktop.main.funds.FundsView; import bisq.desktop.main.funds.withdrawal.WithdrawalView; import bisq.desktop.main.offer.OfferView; import bisq.desktop.main.overlays.popups.Popup; +import bisq.desktop.main.overlays.windows.BsqSwapOfferDetailsWindow; import bisq.desktop.main.overlays.windows.OfferDetailsWindow; import bisq.desktop.util.CssTheme; import bisq.desktop.util.FormBuilder; @@ -53,8 +54,8 @@ import bisq.core.locale.Res; import bisq.core.locale.TradeCurrency; import bisq.core.monetary.Price; import bisq.core.offer.Offer; -import bisq.core.offer.OfferFilter; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferDirection; +import bisq.core.offer.OfferFilterService; import bisq.core.offer.OfferRestrictions; import bisq.core.payment.PaymentAccount; import bisq.core.payment.payload.PaymentMethod; @@ -123,6 +124,7 @@ public class OfferBookView extends ActivatableViewAndModel Res.get(o.getOffer().getPaymentMethod().getId()))); avatarColumn.setComparator(Comparator.comparing(o -> model.getNumTrades(o.getOffer()))); depositColumn.setComparator(Comparator.comparing(item -> { - boolean isSellOffer = item.getOffer().getDirection() == OfferPayload.Direction.SELL; + boolean isSellOffer = item.getOffer().getDirection() == OfferDirection.SELL; Coin deposit = isSellOffer ? item.getOffer().getBuyerSecurityDeposit() : item.getOffer().getSellerSecurityDeposit(); @@ -314,7 +318,7 @@ public class OfferBookView extends ActivatableViewAndModel { createOfferButton.setDisable(true); - offerActionHandler.onCreateOffer(model.getSelectedTradeCurrency()); + offerActionHandler.onCreateOffer(selectedTradeCurrency, selectedPaymentMethod); }) .secondaryActionButtonText(Res.get("offerbook.setupNewAccount")) .onSecondaryAction(() -> { @@ -609,11 +615,11 @@ public class OfferBookView extends ActivatableViewAndModel offerDetailsWindow.show(item.getOffer())); + field.setOnAction(event -> { + if (offer.isBsqSwapOffer()) { + bsqSwapOfferDetailsWindow.show(offer); + } else { + offerDetailsWindow.show(offer); + } + }); field.setTooltip(new Tooltip(model.getPaymentMethodToolTip(item))); setGraphic(field); } @@ -981,7 +997,7 @@ public class OfferBookView extends ActivatableViewAndModel() { final ImageView iconView = new ImageView(); final AutoTooltipButton button = new AutoTooltipButton(); - OfferFilter.Result canTakeOfferResult = null; + OfferFilterService.Result canTakeOfferResult = null; { button.setGraphic(iconView); @@ -1040,7 +1056,7 @@ public class OfferBookView extends ActivatableViewAndModel onRemoveOpenOffer(offer)); } else { - boolean isSellOffer = offer.getDirection() == OfferPayload.Direction.SELL; + boolean isSellOffer = offer.getDirection() == OfferDirection.SELL; iconView.setId(isSellOffer ? "image-buy-white" : "image-sell-white"); button.setId(isSellOffer ? "buy-button" : "sell-button"); button.setStyle("-fx-text-fill: white"); if (isSellOffer) { title = CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) ? - Res.get("offerbook.takeOfferToBuy", offer.getOfferPayload().getBaseCurrencyCode()) : + Res.get("offerbook.takeOfferToBuy", offer.getBaseCurrencyCode()) : Res.get("offerbook.takeOfferToSell", offer.getCurrencyCode()); } else { title = CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) ? - Res.get("offerbook.takeOfferToSell", offer.getOfferPayload().getBaseCurrencyCode()) : + Res.get("offerbook.takeOfferToSell", offer.getBaseCurrencyCode()) : Res.get("offerbook.takeOfferToBuy", offer.getCurrencyCode()); } button.setTooltip(new Tooltip(Res.get("offerbook.takeOfferButton.tooltip", model.getDirectionLabelTooltip(offer)))); @@ -1089,7 +1105,7 @@ public class OfferBookView extends ActivatableViewAndModel allTradeCurrencies = FXCollections.observableArrayList(); - private OfferPayload.Direction direction; + private OfferDirection direction; final StringProperty tradeCurrencyCode = new SimpleStringProperty(); @@ -152,7 +152,7 @@ class OfferBookViewModel extends ActivatableViewModel { AccountAgeWitnessService accountAgeWitnessService, Navigation navigation, PriceUtil priceUtil, - OfferFilter offerFilter, + OfferFilterService offerFilterService, @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter, BsqFormatter bsqFormatter) { super(); @@ -168,7 +168,7 @@ class OfferBookViewModel extends ActivatableViewModel { this.accountAgeWitnessService = accountAgeWitnessService; this.navigation = navigation; this.priceUtil = priceUtil; - this.offerFilter = offerFilter; + this.offerFilterService = offerFilterService; this.btcFormatter = btcFormatter; this.bsqFormatter = bsqFormatter; @@ -215,7 +215,7 @@ class OfferBookViewModel extends ActivatableViewModel { protected void activate() { filteredItems.addListener(filterItemsListener); - String code = direction == OfferPayload.Direction.BUY ? preferences.getBuyScreenCurrencyCode() : preferences.getSellScreenCurrencyCode(); + String code = direction == OfferDirection.BUY ? preferences.getBuyScreenCurrencyCode() : preferences.getSellScreenCurrencyCode(); if (code != null && !code.isEmpty() && !isShowAllEntry(code) && CurrencyUtil.getTradeCurrency(code).isPresent()) { showAllTradeCurrenciesProperty.set(false); @@ -251,7 +251,7 @@ class OfferBookViewModel extends ActivatableViewModel { // API /////////////////////////////////////////////////////////////////////////////////////////// - void initWithDirection(OfferPayload.Direction direction) { + void initWithDirection(OfferDirection direction) { this.direction = direction; } @@ -279,7 +279,7 @@ class OfferBookViewModel extends ActivatableViewModel { setMarketPriceFeedCurrency(); filterOffers(); - if (direction == OfferPayload.Direction.BUY) + if (direction == OfferDirection.BUY) preferences.setBuyScreenCurrencyCode(code); else preferences.setSellScreenCurrencyCode(code); @@ -341,7 +341,7 @@ class OfferBookViewModel extends ActivatableViewModel { return openOfferManager.isMyOffer(offer); } - OfferPayload.Direction getDirection() { + OfferDirection getDirection() { return direction; } @@ -566,7 +566,7 @@ class OfferBookViewModel extends ActivatableViewModel { } boolean canCreateOrTakeOffer() { - return GUIUtil.canCreateOrTakeOfferOrShowPopup(user, navigation) && + return GUIUtil.canCreateOrTakeOfferOrShowPopup(user, navigation, selectedTradeCurrency) && GUIUtil.isChainHeightSyncedWithinToleranceOrShowPopup(walletsSetup) && GUIUtil.isBootstrappedOrShowPopup(p2PService); } @@ -600,11 +600,11 @@ class OfferBookViewModel extends ActivatableViewModel { // This code duplicates code in the view at the button column. We need there the different results for // display in popups so we cannot replace that with the predicate. Any change need to be applied in both // places. - return offerBookListItem -> offerFilter.canTakeOffer(offerBookListItem.getOffer(), false).isValid(); + return offerBookListItem -> offerFilterService.canTakeOffer(offerBookListItem.getOffer(), false).isValid(); } boolean isOfferBanned(Offer offer) { - return offerFilter.isOfferBanned(offer); + return offerFilterService.isOfferBanned(offer); } private boolean isShowAllEntry(String id) { @@ -647,11 +647,11 @@ class OfferBookViewModel extends ActivatableViewModel { bsqFormatter.formatCoinWithCode(offer.getMakerFee()); } - private static String getDirectionWithCodeDetailed(OfferPayload.Direction direction, String currencyCode) { + private static String getDirectionWithCodeDetailed(OfferDirection direction, String currencyCode) { if (CurrencyUtil.isFiatCurrency(currencyCode)) - return (direction == OfferPayload.Direction.BUY) ? Res.get("shared.buyingBTCWith", currencyCode) : Res.get("shared.sellingBTCFor", currencyCode); + return (direction == OfferDirection.BUY) ? Res.get("shared.buyingBTCWith", currencyCode) : Res.get("shared.sellingBTCFor", currencyCode); else - return (direction == OfferPayload.Direction.SELL) ? Res.get("shared.buyingCurrency", currencyCode) : Res.get("shared.sellingCurrency", currencyCode); + return (direction == OfferDirection.SELL) ? Res.get("shared.buyingCurrency", currencyCode) : Res.get("shared.sellingCurrency", currencyCode); } public String formatDepositString(Coin deposit, long amount) { @@ -667,7 +667,7 @@ class OfferBookViewModel extends ActivatableViewModel { return new CryptoCurrency(GUIUtil.EDIT_FLAG, ""); } - private PaymentMethod getShowAllEntryForPaymentMethod() { + PaymentMethod getShowAllEntryForPaymentMethod() { return PaymentMethod.getDummyPaymentMethod(GUIUtil.SHOW_ALL_FLAG); } } diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/editor/PeerInfoWithTagEditor.java b/desktop/src/main/java/bisq/desktop/main/overlays/editor/PeerInfoWithTagEditor.java index e0aa093866..02354cdf9e 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/editor/PeerInfoWithTagEditor.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/editor/PeerInfoWithTagEditor.java @@ -25,7 +25,7 @@ import bisq.core.alert.PrivateNotificationManager; import bisq.core.locale.GlobalSettings; import bisq.core.locale.Res; import bisq.core.offer.Offer; -import bisq.core.trade.Trade; +import bisq.core.trade.model.TradeModel; import bisq.core.user.Preferences; import bisq.common.UserThread; @@ -87,7 +87,7 @@ public class PeerInfoWithTagEditor extends Overlay { private ChangeListener focusListener; private final PrivateNotificationManager privateNotificationManager; @Nullable - private final Trade trade; + private final TradeModel tradeModel; private final Offer offer; private final Preferences preferences; private EventHandler keyEventEventHandler; @@ -102,12 +102,12 @@ public class PeerInfoWithTagEditor extends Overlay { private String signAgeInfo; public PeerInfoWithTagEditor(PrivateNotificationManager privateNotificationManager, - @Nullable Trade trade, + @Nullable TradeModel tradeModel, Offer offer, Preferences preferences, boolean useDevPrivilegeKeys) { this.privateNotificationManager = privateNotificationManager; - this.trade = trade; + this.tradeModel = tradeModel; this.offer = offer; this.preferences = preferences; this.useDevPrivilegeKeys = useDevPrivilegeKeys; @@ -250,8 +250,8 @@ public class PeerInfoWithTagEditor extends Overlay { doClose(); UserThread.runAfter(() -> { PubKeyRing peersPubKeyRing = null; - if (trade != null) { - peersPubKeyRing = trade.getProcessModel().getTradingPeer().getPubKeyRing(); + if (tradeModel != null) { + peersPubKeyRing = tradeModel.getTradeProtocolModel().getTradePeer().getPubKeyRing(); } else if (offer != null) { peersPubKeyRing = offer.getPubKeyRing(); } diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/notifications/NotificationCenter.java b/desktop/src/main/java/bisq/desktop/main/overlays/notifications/NotificationCenter.java index 994a976867..b86f87a82d 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/notifications/NotificationCenter.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/notifications/NotificationCenter.java @@ -29,11 +29,11 @@ import bisq.desktop.main.support.dispute.client.refund.RefundClientView; import bisq.core.locale.Res; import bisq.core.support.dispute.mediation.MediationManager; import bisq.core.support.dispute.refund.RefundManager; -import bisq.core.trade.BuyerTrade; -import bisq.core.trade.MakerTrade; -import bisq.core.trade.SellerTrade; -import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; +import bisq.core.trade.model.MakerTrade; +import bisq.core.trade.model.bisq_v1.BuyerTrade; +import bisq.core.trade.model.bisq_v1.SellerTrade; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.user.DontShowAgainLookup; import bisq.core.user.Preferences; diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/BsqEmptyWalletWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/BsqEmptyWalletWindow.java index 493f983ec4..0d287d40d4 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/BsqEmptyWalletWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/BsqEmptyWalletWindow.java @@ -60,7 +60,7 @@ public final class BsqEmptyWalletWindow extends Overlay { gridPane.getColumnConstraints().remove(1); addTopLabelTextField(gridPane, ++rowIndex, Res.get("emptyWalletWindow.balance"), - bsqFormatter.formatCoinWithCode(bsqWalletService.getAvailableConfirmedBalance()), 10); + bsqFormatter.formatCoinWithCode(bsqWalletService.getAvailableBalance()), 10); addTopLabelTextField(gridPane, ++rowIndex, Res.get("emptyWalletWindow.bsq.btcBalance"), bsqFormatter.formatBTCWithCode(bsqWalletService.getAvailableNonBsqBalance().value), 10); diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/BsqSwapOfferDetailsWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/BsqSwapOfferDetailsWindow.java new file mode 100644 index 0000000000..ade88cb5fa --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/BsqSwapOfferDetailsWindow.java @@ -0,0 +1,318 @@ +/* + * 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.desktop.main.overlays.windows; + +import bisq.desktop.components.AutoTooltipButton; +import bisq.desktop.components.BusyAnimation; +import bisq.desktop.main.overlays.Overlay; +import bisq.desktop.util.DisplayUtils; +import bisq.desktop.util.Layout; + +import bisq.core.locale.Res; +import bisq.core.monetary.Price; +import bisq.core.monetary.Volume; +import bisq.core.offer.Offer; +import bisq.core.offer.OfferDirection; +import bisq.core.payment.PaymentAccount; +import bisq.core.payment.payload.PaymentMethod; +import bisq.core.user.User; +import bisq.core.util.FormattingUtils; +import bisq.core.util.VolumeUtil; +import bisq.core.util.coin.CoinFormatter; + +import bisq.common.crypto.KeyRing; +import bisq.common.util.Tuple4; + +import org.bitcoinj.core.Coin; + +import javax.inject.Inject; +import javax.inject.Named; + +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.image.ImageView; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; + +import javafx.geometry.HPos; +import javafx.geometry.Insets; + +import java.util.Optional; + +import lombok.extern.slf4j.Slf4j; + +import static bisq.desktop.util.FormBuilder.*; + +@Slf4j +public class BsqSwapOfferDetailsWindow extends Overlay { + private final CoinFormatter formatter; + private final User user; + private final KeyRing keyRing; + private Offer offer; + private Coin tradeAmount; + private Price tradePrice; + private Optional placeOfferHandlerOptional = Optional.empty(); + private Optional takeOfferHandlerOptional = Optional.empty(); + private BusyAnimation busyAnimation; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Public API + /////////////////////////////////////////////////////////////////////////////////////////// + + @Inject + public BsqSwapOfferDetailsWindow(@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter, + User user, + KeyRing keyRing) { + this.formatter = formatter; + this.user = user; + this.keyRing = keyRing; + type = Type.Confirmation; + } + + public void show(Offer offer, Coin tradeAmount, Price tradePrice) { + this.offer = offer; + this.tradeAmount = tradeAmount; + this.tradePrice = tradePrice; + + rowIndex = -1; + width = 1118; + createGridPane(); + addContent(); + display(); + } + + public void show(Offer offer) { + this.offer = offer; + rowIndex = -1; + width = 1118; + createGridPane(); + addContent(); + display(); + } + + public BsqSwapOfferDetailsWindow onPlaceOffer(Runnable placeOfferHandler) { + this.placeOfferHandlerOptional = Optional.of(placeOfferHandler); + return this; + } + + public BsqSwapOfferDetailsWindow onTakeOffer(Runnable takeOfferHandler) { + this.takeOfferHandlerOptional = Optional.of(takeOfferHandler); + return this; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Protected + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + protected void onHidden() { + if (busyAnimation != null) + busyAnimation.stop(); + } + + @Override + protected void createGridPane() { + super.createGridPane(); + gridPane.setPadding(new Insets(35, 40, 30, 40)); + gridPane.getStyleClass().add("grid-pane"); + } + + private void addContent() { + gridPane.getColumnConstraints().get(0).setMinWidth(224); + + int rows = 5; + boolean isTakeOfferScreen = takeOfferHandlerOptional.isPresent(); + boolean isMakeOfferScreen = placeOfferHandlerOptional.isPresent(); + boolean isMyOffer = offer.isMyOffer(keyRing); + + if (!isTakeOfferScreen) + rows++; + + addTitledGroupBg(gridPane, ++rowIndex, rows, Res.get("shared.Offer")); + + String bsqDirectionInfo; + String btcDirectionInfo; + OfferDirection direction = offer.getDirection(); + String currencyCode = offer.getCurrencyCode(); + String offerTypeLabel = Res.get("shared.offerType"); + String toReceive = " " + Res.get("shared.toReceive"); + String toSpend = " " + Res.get("shared.toSpend"); + String minus = " - "; + String plus = " + "; + String minerFeePostFix = Res.get("tradeDetailsWindow.txFee"); + String tradeFeePostFix = Res.get("shared.tradeFee"); + String btcAmount; + String bsqAmount; + double firstRowDistance = Layout.TWICE_FIRST_ROW_DISTANCE; + boolean isSellOffer = direction == OfferDirection.SELL; + boolean isBuyOffer = direction == OfferDirection.BUY; + boolean isBuyer; + String offerType; + Coin amount = isTakeOfferScreen ? tradeAmount : offer.getAmount(); + Volume volume = isTakeOfferScreen ? offer.getVolumeByAmount(tradeAmount) : offer.getVolume(); + btcAmount = formatter.formatCoinWithCode(amount); + bsqAmount = VolumeUtil.formatVolumeWithCode(volume); + boolean isMaker = isMakeOfferScreen || isMyOffer; + boolean isTaker = !isMaker; + + if (isTaker) { + bsqDirectionInfo = isBuyOffer ? toReceive : toSpend; + btcDirectionInfo = isSellOffer ? toReceive : toSpend; + isBuyer = isSellOffer; + } else { + bsqDirectionInfo = isSellOffer ? toReceive : toSpend; + btcDirectionInfo = isBuyOffer ? toReceive : toSpend; + isBuyer = isBuyOffer; + } + if (isTakeOfferScreen) { + offerType = DisplayUtils.getDirectionForTakeOffer(direction, currencyCode); + } else if (isMakeOfferScreen) { + offerType = DisplayUtils.getOfferDirectionForCreateOffer(direction, currencyCode); + } else { + offerType = isBuyer ? + DisplayUtils.getDirectionForBuyer(isMyOffer, offer.getCurrencyCode()) : + DisplayUtils.getDirectionForSeller(isMyOffer, offer.getCurrencyCode()); + } + if (!isTakeOfferScreen && + offer.getVolume() != null && + offer.getMinVolume() != null && + !offer.getVolume().equals(offer.getMinVolume())) { + bsqAmount += " " + Res.get("offerDetailsWindow.min", VolumeUtil.formatVolumeWithCode(offer.getMinVolume())); + } + + addConfirmationLabelLabel(gridPane, rowIndex, offerTypeLabel, offerType, firstRowDistance); + + if (isBuyer) { + btcAmount += minus + minerFeePostFix; + bsqAmount += plus + tradeFeePostFix; + } else { + btcAmount += plus + minerFeePostFix; + bsqAmount += minus + tradeFeePostFix; + } + + String btcAmountTitle = Res.get("shared.btcAmount"); + addConfirmationLabelLabel(gridPane, ++rowIndex, btcAmountTitle + btcDirectionInfo, btcAmount); + if (!isTakeOfferScreen) { + addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("offerDetailsWindow.minBtcAmount"), + formatter.formatCoinWithCode(offer.getMinAmount())); + + } + addConfirmationLabelLabel(gridPane, ++rowIndex, + VolumeUtil.formatVolumeLabel(currencyCode) + bsqDirectionInfo, bsqAmount); + + String priceLabel = Res.get("shared.price"); + if (isTakeOfferScreen) { + addConfirmationLabelLabel(gridPane, ++rowIndex, priceLabel, FormattingUtils.formatPrice(tradePrice)); + } else { + addConfirmationLabelLabel(gridPane, ++rowIndex, priceLabel, FormattingUtils.formatPrice(offer.getPrice())); + } + PaymentMethod paymentMethod = offer.getPaymentMethod(); + String makerPaymentAccountId = offer.getMakerPaymentAccountId(); + PaymentAccount myPaymentAccount = user.getPaymentAccount(makerPaymentAccountId); + if (isMyOffer && makerPaymentAccountId != null && myPaymentAccount != null) { + addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("offerDetailsWindow.myTradingAccount"), myPaymentAccount.getAccountName()); + } else { + String method = Res.get(paymentMethod.getId()); + addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.paymentMethod"), method); + } + + rows = 3; + + // details + + addTitledGroupBg(gridPane, ++rowIndex, rows, Res.get("shared.details"), Layout.GROUP_DISTANCE); + addConfirmationLabelTextFieldWithCopyIcon(gridPane, rowIndex, Res.get("shared.offerId"), offer.getId(), + Layout.TWICE_FIRST_ROW_AND_GROUP_DISTANCE); + addConfirmationLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, Res.get("offerDetailsWindow.makersOnion"), + offer.getMakerNodeAddress().getFullAddress()); + addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("offerDetailsWindow.creationDate"), + DisplayUtils.formatDateTime(offer.getDate())); + + // commitment + + if (isMakeOfferScreen) { + addConfirmAndCancelButtons(true); + } else if (isTakeOfferScreen) { + addConfirmAndCancelButtons(false); + } else { + Button closeButton = addButtonAfterGroup(gridPane, ++rowIndex, Res.get("shared.close")); + GridPane.setColumnIndex(closeButton, 1); + GridPane.setHalignment(closeButton, HPos.RIGHT); + + closeButton.setOnAction(e -> { + closeHandlerOptional.ifPresent(Runnable::run); + hide(); + }); + } + } + + private void addConfirmAndCancelButtons(boolean isPlaceOffer) { + boolean isBuyOffer = offer.isBuyOffer(); + boolean isBuyerRole = isPlaceOffer == isBuyOffer; + String placeOfferButtonText = isBuyerRole ? + Res.get("offerDetailsWindow.confirm.maker", Res.get("shared.buy")) : + Res.get("offerDetailsWindow.confirm.maker", Res.get("shared.sell")); + String takeOfferButtonText = isBuyerRole ? + Res.get("offerDetailsWindow.confirm.taker", Res.get("shared.buy")) : + Res.get("offerDetailsWindow.confirm.taker", Res.get("shared.sell")); + + ImageView iconView = new ImageView(); + iconView.setId(isBuyerRole ? "image-buy-white" : "image-sell-white"); + + Tuple4 placeOfferTuple = addButtonBusyAnimationLabelAfterGroup(gridPane, + ++rowIndex, 1, + isPlaceOffer ? placeOfferButtonText : takeOfferButtonText); + + AutoTooltipButton button = (AutoTooltipButton) placeOfferTuple.first; + button.setMinHeight(40); + button.setPadding(new Insets(0, 20, 0, 20)); + button.setGraphic(iconView); + button.setGraphicTextGap(10); + button.setId(isBuyerRole ? "buy-button-big" : "sell-button-big"); + button.updateText(isPlaceOffer ? placeOfferButtonText : takeOfferButtonText); + + busyAnimation = placeOfferTuple.second; + Label spinnerInfoLabel = placeOfferTuple.third; + + Button cancelButton = new AutoTooltipButton(Res.get("shared.cancel")); + cancelButton.setDefaultButton(false); + cancelButton.setOnAction(e -> { + closeHandlerOptional.ifPresent(Runnable::run); + hide(); + }); + + placeOfferTuple.fourth.getChildren().add(cancelButton); + + button.setOnAction(e -> { + button.setDisable(true); + cancelButton.setDisable(true); + // temporarily disabled due to high CPU usage (per issue #4649) + // busyAnimation.play(); + if (isPlaceOffer) { + spinnerInfoLabel.setText(Res.get("createOffer.fundsBox.placeOfferSpinnerInfo")); + placeOfferHandlerOptional.ifPresent(Runnable::run); + } else { + spinnerInfoLabel.setText(Res.get("takeOffer.fundsBox.takeOfferSpinnerInfo")); + takeOfferHandlerOptional.ifPresent(Runnable::run); + } + hide(); + }); + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/BsqTradeDetailsWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/BsqTradeDetailsWindow.java new file mode 100644 index 0000000000..cccd6040a9 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/BsqTradeDetailsWindow.java @@ -0,0 +1,216 @@ +/* + * 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.desktop.main.overlays.windows; + +import bisq.desktop.main.overlays.Overlay; +import bisq.desktop.util.DisplayUtils; +import bisq.desktop.util.Layout; + +import bisq.core.btc.wallet.BsqWalletService; +import bisq.core.locale.Res; +import bisq.core.offer.Offer; +import bisq.core.trade.TradeManager; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.util.FormattingUtils; +import bisq.core.util.VolumeUtil; +import bisq.core.util.coin.BsqFormatter; +import bisq.core.util.coin.CoinFormatter; + +import org.bitcoinj.core.Transaction; + +import javax.inject.Inject; +import javax.inject.Named; + +import javafx.scene.control.Button; +import javafx.scene.control.TextArea; +import javafx.scene.layout.GridPane; + +import javafx.geometry.HPos; +import javafx.geometry.Insets; + +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.value.ChangeListener; + +import static bisq.desktop.util.FormBuilder.*; + +public class BsqTradeDetailsWindow extends Overlay { + private final CoinFormatter formatter; + private BsqFormatter bsqFormatter; + private final TradeManager tradeManager; + private final BsqWalletService bsqWalletService; + private BsqSwapTrade bsqSwapTrade; + private ChangeListener changeListener; + private TextArea textArea; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Public API + /////////////////////////////////////////////////////////////////////////////////////////// + + @Inject + public BsqTradeDetailsWindow(@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter, + BsqFormatter bsqFormatter, + TradeManager tradeManager, + BsqWalletService bsqWalletService) { + this.formatter = formatter; + this.bsqFormatter = bsqFormatter; + this.tradeManager = tradeManager; + this.bsqWalletService = bsqWalletService; + type = Type.Confirmation; + } + + public void show(BsqSwapTrade bsqSwapTrade) { + this.bsqSwapTrade = bsqSwapTrade; + + rowIndex = -1; + width = 918; + createGridPane(); + addContent(); + display(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Protected + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + protected void cleanup() { + if (textArea != null) + textArea.scrollTopProperty().addListener(changeListener); + } + + @Override + protected void createGridPane() { + super.createGridPane(); + gridPane.setPadding(new Insets(35, 40, 30, 40)); + gridPane.getStyleClass().add("grid-pane"); + } + + private void addContent() { + Offer offer = bsqSwapTrade.getOffer(); + + int rows = 5; + addTitledGroupBg(gridPane, ++rowIndex, rows, Res.get("tradeDetailsWindow.bsqSwap.headline")); + + boolean myOffer = tradeManager.isMyOffer(offer); + String bsqDirectionInfo; + String btcDirectionInfo; + String toReceive = " " + Res.get("shared.toReceive"); + String toSpend = " " + Res.get("shared.toSpend"); + String offerType = Res.get("shared.offerType"); + String minus = " (- "; + String plus = " (+ "; + String minerFeePostFix = Res.get("tradeDetailsWindow.txFee") + ")"; + String tradeFeePostFix = Res.get("shared.tradeFee") + ")"; + String btcAmount = formatter.formatCoinWithCode(bsqSwapTrade.getAmountAsLong()); + String bsqAmount = VolumeUtil.formatVolumeWithCode(bsqSwapTrade.getVolume()); + if (tradeManager.isBuyer(offer)) { + addConfirmationLabelLabel(gridPane, rowIndex, offerType, + DisplayUtils.getDirectionForBuyer(myOffer, offer.getCurrencyCode()), Layout.TWICE_FIRST_ROW_DISTANCE); + bsqDirectionInfo = toSpend; + btcDirectionInfo = toReceive; + btcAmount += minus + minerFeePostFix; + bsqAmount += plus + tradeFeePostFix; + } else { + addConfirmationLabelLabel(gridPane, rowIndex, offerType, + DisplayUtils.getDirectionForSeller(myOffer, offer.getCurrencyCode()), Layout.TWICE_FIRST_ROW_DISTANCE); + bsqDirectionInfo = toReceive; + btcDirectionInfo = toSpend; + btcAmount += plus + minerFeePostFix; + bsqAmount += minus + tradeFeePostFix; + } + + addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.btcAmount") + btcDirectionInfo, btcAmount); + + + addConfirmationLabelLabel(gridPane, ++rowIndex, + VolumeUtil.formatVolumeLabel(offer.getCurrencyCode()) + bsqDirectionInfo, + bsqAmount); + + addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.tradePrice"), + FormattingUtils.formatPrice(bsqSwapTrade.getPrice())); + String paymentMethodText = Res.get(offer.getPaymentMethod().getId()); + addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.paymentMethod"), paymentMethodText); + + // details + rows = 3; + Transaction transaction = bsqSwapTrade.getTransaction(bsqWalletService); + if (transaction != null) + rows++; + if (bsqSwapTrade.hasFailed()) + rows += 2; + if (bsqSwapTrade.getTradingPeerNodeAddress() != null) + rows++; + + addTitledGroupBg(gridPane, ++rowIndex, rows, Res.get("shared.details"), Layout.GROUP_DISTANCE); + addConfirmationLabelTextFieldWithCopyIcon(gridPane, rowIndex, Res.get("shared.tradeId"), + bsqSwapTrade.getId(), Layout.TWICE_FIRST_ROW_AND_GROUP_DISTANCE); + addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("tradeDetailsWindow.tradeDate"), + DisplayUtils.formatDateTime(bsqSwapTrade.getDate())); + + // tx fee, would be good to store it in process model to not need to re-calculate it here + /* String txFee = Res.get("shared.makerTxFee", formatter.formatCoinWithCode(offer.getTxFee())) + + " / " + Res.get("shared.takerTxFee", formatter.formatCoinWithCode(bsqSwapTrade.gettx())); //todo + addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("tradeDetailsWindow.txFee"), txFee);*/ + + String tradeFee = Res.get("shared.makerTxFee", bsqFormatter.formatCoinWithCode(bsqSwapTrade.getMakerFeeAsLong())) + + " / " + Res.get("shared.takerTxFee", bsqFormatter.formatCoinWithCode(bsqSwapTrade.getTakerFeeAsLong())); + addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.tradeFee"), tradeFee); + + + if (bsqSwapTrade.getTradingPeerNodeAddress() != null) + addConfirmationLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, Res.get("tradeDetailsWindow.tradingPeersOnion"), + bsqSwapTrade.getTradingPeerNodeAddress().getFullAddress()); + + + if (transaction != null) + addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("tradeDetailsWindow.bsqSwap.txId"), + transaction.getTxId().toString()); + + if (bsqSwapTrade.hasFailed()) { + textArea = addConfirmationLabelTextArea(gridPane, ++rowIndex, + Res.get("shared.errorMessage"), "", 0).second; + textArea.setText(bsqSwapTrade.getErrorMessage()); + textArea.setEditable(false); + //TODO paint red + + IntegerProperty count = new SimpleIntegerProperty(20); + int rowHeight = 10; + textArea.prefHeightProperty().bindBidirectional(count); + changeListener = (ov, old, newVal) -> { + if (newVal.intValue() > rowHeight) + count.setValue(count.get() + newVal.intValue() + 10); + }; + textArea.scrollTopProperty().addListener(changeListener); + textArea.setScrollTop(30); + + addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("tradeDetailsWindow.tradeState"), + bsqSwapTrade.getState().name()); + } + + Button closeButton = addButtonAfterGroup(gridPane, ++rowIndex, Res.get("shared.close")); + GridPane.setColumnIndex(closeButton, 1); + GridPane.setHalignment(closeButton, HPos.RIGHT); + closeButton.setOnAction(e -> { + closeHandlerOptional.ifPresent(Runnable::run); + hide(); + }); + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/BtcEmptyWalletWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/BtcEmptyWalletWindow.java index 9d5c77b650..12c675e2f4 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/BtcEmptyWalletWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/BtcEmptyWalletWindow.java @@ -103,7 +103,7 @@ public final class BtcEmptyWalletWindow extends Overlay { private void addContent() { addMultilineLabel(gridPane, ++rowIndex, Res.get("emptyWalletWindow.info"), 0); - Coin totalBalance = btcWalletService.getAvailableConfirmedBalance(); + Coin totalBalance = btcWalletService.getAvailableBalance(); balanceTextField = addTopLabelTextField(gridPane, ++rowIndex, Res.get("emptyWalletWindow.balance"), btcFormatter.formatCoinWithCode(totalBalance), 10).second; @@ -164,7 +164,7 @@ public final class BtcEmptyWalletWindow extends Overlay { aesKey, () -> { closeButton.updateText(Res.get("shared.close")); - balanceTextField.setText(btcFormatter.formatCoinWithCode(btcWalletService.getAvailableConfirmedBalance())); + balanceTextField.setText(btcFormatter.formatCoinWithCode(btcWalletService.getAvailableBalance())); emptyWalletButton.setDisable(true); log.debug("wallet empty successful"); onClose(() -> UserThread.runAfter(() -> new Popup() diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java index c51d9ed8ba..fca374a9b8 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java @@ -37,7 +37,7 @@ import bisq.core.support.dispute.agent.DisputeAgentLookupMap; import bisq.core.support.dispute.arbitration.ArbitrationManager; import bisq.core.support.dispute.mediation.MediationManager; import bisq.core.support.dispute.refund.RefundManager; -import bisq.core.trade.Contract; +import bisq.core.trade.model.bisq_v1.Contract; import bisq.core.util.FormattingUtils; import bisq.core.util.VolumeUtil; import bisq.core.util.coin.CoinFormatter; diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java index dd632b0e08..ffbba10522 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java @@ -46,8 +46,8 @@ import bisq.core.support.dispute.DisputeManager; import bisq.core.support.dispute.DisputeResult; import bisq.core.support.dispute.mediation.MediationManager; import bisq.core.support.dispute.refund.RefundManager; -import bisq.core.trade.Contract; -import bisq.core.trade.TradeDataValidation; +import bisq.core.trade.bisq_v1.TradeDataValidation; +import bisq.core.trade.model.bisq_v1.Contract; import bisq.core.util.FormattingUtils; import bisq.core.util.ParsingUtils; import bisq.core.util.VolumeUtil; diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/FilterWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/FilterWindow.java index 017ec644d3..a2fe76adb9 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/FilterWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/FilterWindow.java @@ -173,6 +173,11 @@ public class FilterWindow extends Overlay { Res.get("filterWindow.disableMempoolValidation")); CheckBox disableApiCheckBox = addLabelCheckBox(gridPane, ++rowIndex, Res.get("filterWindow.disableApi")); + CheckBox disablePowMessage = addLabelCheckBox(gridPane, ++rowIndex, + Res.get("filterWindow.disablePowMessage")); + InputTextField powDifficultyTF = addInputTextField(gridPane, ++rowIndex, + Res.get("filterWindow.powDifficulty")); + powDifficultyTF.setText("0"); Filter filter = filterManager.getDevFilter(); if (filter != null) { @@ -200,6 +205,8 @@ public class FilterWindow extends Overlay { disableTradeBelowVersionTF.setText(filter.getDisableTradeBelowVersion()); disableMempoolValidationCheckBox.setSelected(filter.isDisableMempoolValidation()); disableApiCheckBox.setSelected(filter.isDisableApi()); + disablePowMessage.setSelected(filter.isDisablePowMessage()); + powDifficultyTF.setText(String.valueOf(filter.getPowDifficulty())); } Button removeFilterMessageButton = new AutoTooltipButton(Res.get("filterWindow.remove")); @@ -235,7 +242,9 @@ public class FilterWindow extends Overlay { readAsList(autoConfExplorersTF), new HashSet<>(readAsList(bannedFromNetworkTF)), disableMempoolValidationCheckBox.isSelected(), - disableApiCheckBox.isSelected() + disableApiCheckBox.isSelected(), + disablePowMessage.isSelected(), + Integer.parseInt(powDifficultyTF.getText()) ); // We remove first the old filter @@ -244,8 +253,11 @@ public class FilterWindow extends Overlay { // working as expected) if (filterManager.canRemoveDevFilter(privKeyString)) { filterManager.removeDevFilter(privKeyString); - UserThread.runAfter(() -> addDevFilter(removeFilterMessageButton, privKeyString, newFilter), - 5); + if (DevEnv.isDevMode()) { + addDevFilter(removeFilterMessageButton, privKeyString, newFilter); + } else { + UserThread.runAfter(() -> addDevFilter(removeFilterMessageButton, privKeyString, newFilter), 5); + } } else { addDevFilter(removeFilterMessageButton, privKeyString, newFilter); } diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/OfferDetailsWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/OfferDetailsWindow.java index 531469bb17..55471788da 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/OfferDetailsWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/OfferDetailsWindow.java @@ -29,10 +29,11 @@ import bisq.desktop.util.Layout; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.locale.CountryUtil; +import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; import bisq.core.monetary.Price; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferDirection; import bisq.core.offer.OfferUtil; import bisq.core.payment.PaymentAccount; import bisq.core.payment.payload.PaymentMethod; @@ -175,7 +176,7 @@ public class OfferDetailsWindow extends Overlay { if (isF2F) rows++; - boolean showXmrAutoConf = offer.isXmr() && offer.getDirection() == OfferPayload.Direction.SELL; + boolean showXmrAutoConf = offer.isXmr() && offer.getDirection() == OfferDirection.SELL; if (showXmrAutoConf) { rows++; } @@ -184,7 +185,7 @@ public class OfferDetailsWindow extends Overlay { String fiatDirectionInfo = ""; String btcDirectionInfo = ""; - OfferPayload.Direction direction = offer.getDirection(); + OfferDirection direction = offer.getDirection(); String currencyCode = offer.getCurrencyCode(); String offerTypeLabel = Res.get("shared.offerType"); String toReceive = " " + Res.get("shared.toReceive"); @@ -193,13 +194,13 @@ public class OfferDetailsWindow extends Overlay { if (takeOfferHandlerOptional.isPresent()) { addConfirmationLabelLabel(gridPane, rowIndex, offerTypeLabel, DisplayUtils.getDirectionForTakeOffer(direction, currencyCode), firstRowDistance); - fiatDirectionInfo = direction == OfferPayload.Direction.BUY ? toReceive : toSpend; - btcDirectionInfo = direction == OfferPayload.Direction.SELL ? toReceive : toSpend; + fiatDirectionInfo = direction == OfferDirection.BUY ? toReceive : toSpend; + btcDirectionInfo = direction == OfferDirection.SELL ? toReceive : toSpend; } else if (placeOfferHandlerOptional.isPresent()) { addConfirmationLabelLabel(gridPane, rowIndex, offerTypeLabel, DisplayUtils.getOfferDirectionForCreateOffer(direction, currencyCode), firstRowDistance); - fiatDirectionInfo = direction == OfferPayload.Direction.SELL ? toReceive : toSpend; - btcDirectionInfo = direction == OfferPayload.Direction.BUY ? toReceive : toSpend; + fiatDirectionInfo = direction == OfferDirection.SELL ? toReceive : toSpend; + btcDirectionInfo = direction == OfferDirection.BUY ? toReceive : toSpend; } else { addConfirmationLabelLabel(gridPane, rowIndex, offerTypeLabel, DisplayUtils.getDirectionBothSides(direction, currencyCode), firstRowDistance); @@ -423,7 +424,8 @@ public class OfferDetailsWindow extends Overlay { placeOfferTuple.fourth.getChildren().add(cancelButton); button.setOnAction(e -> { - if (GUIUtil.canCreateOrTakeOfferOrShowPopup(user, navigation)) { + if (GUIUtil.canCreateOrTakeOfferOrShowPopup(user, navigation, + CurrencyUtil.getTradeCurrency(offer.getCurrencyCode()).get())) { button.setDisable(true); cancelButton.setDisable(true); // temporarily disabled due to high CPU usage (per issue #4649) diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/SignPaymentAccountsWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/SignPaymentAccountsWindow.java index bab6e7e1c2..36c32ee322 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/SignPaymentAccountsWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/SignPaymentAccountsWindow.java @@ -171,7 +171,7 @@ public class SignPaymentAccountsWindow extends Overlay getPaymentMethods() { return PaymentMethod.getPaymentMethods().stream() - .filter(paymentMethod -> !paymentMethod.isAsset()) + .filter(PaymentMethod::isFiat) .filter(PaymentMethod::hasChargebackRisk) .collect(Collectors.toList()); } diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/SwiftPaymentDetails.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/SwiftPaymentDetails.java index d652aa067c..9626ace058 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/SwiftPaymentDetails.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/SwiftPaymentDetails.java @@ -22,7 +22,7 @@ import bisq.desktop.main.overlays.Overlay; import bisq.core.locale.CountryUtil; import bisq.core.locale.Res; import bisq.core.payment.payload.SwiftAccountPayload; -import bisq.core.trade.Trade; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.util.VolumeUtil; import javafx.scene.control.Label; @@ -74,7 +74,7 @@ public class SwiftPaymentDetails extends Overlay { gridPane.add(new Label(""), 0, ++rowIndex); // spacer addLabelsAndCopy(Res.get("portfolio.pending.step2_buyer.amountToTransfer"), - VolumeUtil.formatVolumeWithCode(trade.getTradeVolume())); + VolumeUtil.formatVolumeWithCode(trade.getVolume())); addLabelsAndCopy(Res.get(SWIFT_CODE + BANKPOSTFIX), payload.getBankSwiftCode()); addLabelsAndCopy(Res.get(SNAME + BANKPOSTFIX), payload.getBankName()); addLabelsAndCopy(Res.get(BRANCH + BANKPOSTFIX), payload.getBankBranch()); diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java index 332133d61c..f04db86188 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java @@ -33,9 +33,9 @@ import bisq.core.offer.Offer; import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.support.dispute.agent.DisputeAgentLookupMap; import bisq.core.support.dispute.arbitration.ArbitrationManager; -import bisq.core.trade.Contract; -import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; +import bisq.core.trade.model.bisq_v1.Contract; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.trade.txproof.AssetTxProofResult; import bisq.core.util.FormattingUtils; import bisq.core.util.VolumeUtil; @@ -166,12 +166,12 @@ public class TradeDetailsWindow extends Overlay { } addConfirmationLabelTextField(gridPane, ++rowIndex, Res.get("shared.btcAmount") + btcDirectionInfo, - formatter.formatCoinWithCode(trade.getTradeAmount())); + formatter.formatCoinWithCode(trade.getAmount())); addConfirmationLabelTextField(gridPane, ++rowIndex, VolumeUtil.formatVolumeLabel(offer.getCurrencyCode()) + fiatDirectionInfo, - VolumeUtil.formatVolumeWithCode(trade.getTradeVolume())); + VolumeUtil.formatVolumeWithCode(trade.getVolume())); addConfirmationLabelTextField(gridPane, ++rowIndex, Res.get("shared.tradePrice"), - FormattingUtils.formatPrice(trade.getTradePrice())); + FormattingUtils.formatPrice(trade.getPrice())); String paymentMethodText = Res.get(offer.getPaymentMethod().getId()); addConfirmationLabelTextField(gridPane, ++rowIndex, Res.get("shared.paymentMethod"), paymentMethodText); @@ -228,7 +228,7 @@ public class TradeDetailsWindow extends Overlay { String txFee = Res.get("shared.makerTxFee", formatter.formatCoinWithCode(offer.getTxFee())) + " / " + - Res.get("shared.takerTxFee", formatter.formatCoinWithCode(trade.getTxFee().multiply(3))); + Res.get("shared.takerTxFee", formatter.formatCoinWithCode(trade.getTradeTxFee().multiply(3))); addConfirmationLabelTextField(gridPane, ++rowIndex, Res.get("tradeDetailsWindow.txFee"), txFee); NodeAddress arbitratorNodeAddress = trade.getArbitratorNodeAddress(); @@ -324,7 +324,7 @@ public class TradeDetailsWindow extends Overlay { textArea.scrollTopProperty().addListener(changeListener); textArea.setScrollTop(30); - addConfirmationLabelTextField(gridPane, ++rowIndex, Res.get("tradeDetailsWindow.tradeState"), trade.getState().getPhase().name()); + addConfirmationLabelTextField(gridPane, ++rowIndex, Res.get("tradeDetailsWindow.tradeState"), trade.getTradePhase().name()); } Tuple3 tuple = add2ButtonsWithBox(gridPane, ++rowIndex, diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/PortfolioView.fxml b/desktop/src/main/java/bisq/desktop/main/portfolio/PortfolioView.fxml index 1050c991f1..e115abc109 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/PortfolioView.fxml +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/PortfolioView.fxml @@ -27,6 +27,7 @@ + diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/PortfolioView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/PortfolioView.java index 52809d5b19..872000d8f3 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/PortfolioView.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/PortfolioView.java @@ -23,6 +23,7 @@ import bisq.desktop.common.view.CachingViewLoader; import bisq.desktop.common.view.FxmlView; import bisq.desktop.common.view.View; import bisq.desktop.main.MainView; +import bisq.desktop.main.portfolio.bsqswaps.UnconfirmedBsqSwapsView; import bisq.desktop.main.portfolio.closedtrades.ClosedTradesView; import bisq.desktop.main.portfolio.duplicateoffer.DuplicateOfferView; import bisq.desktop.main.portfolio.editoffer.EditOfferView; @@ -31,10 +32,10 @@ import bisq.desktop.main.portfolio.openoffer.OpenOffersView; import bisq.desktop.main.portfolio.pendingtrades.PendingTradesView; import bisq.core.locale.Res; -import bisq.core.offer.OfferPayload; import bisq.core.offer.OpenOffer; -import bisq.core.trade.Trade; -import bisq.core.trade.failed.FailedTradesManager; +import bisq.core.offer.bisq_v1.OfferPayload; +import bisq.core.trade.bisq_v1.FailedTradesManager; +import bisq.core.trade.model.bisq_v1.Trade; import javax.inject.Inject; @@ -55,7 +56,7 @@ import javax.annotation.Nullable; public class PortfolioView extends ActivatableView { @FXML - Tab openOffersTab, pendingTradesTab, closedTradesTab; + Tab openOffersTab, pendingTradesTab, closedTradesTab, bsqSwapTradesTab; private Tab editOpenOfferTab, duplicateOfferTab; private final Tab failedTradesTab = new Tab(Res.get("portfolio.tab.failed").toUpperCase()); private Tab currentTab; @@ -87,6 +88,7 @@ public class PortfolioView extends ActivatableView { openOffersTab.setText(Res.get("portfolio.tab.openOffers").toUpperCase()); pendingTradesTab.setText(Res.get("portfolio.tab.pendingTrades").toUpperCase()); closedTradesTab.setText(Res.get("portfolio.tab.history").toUpperCase()); + bsqSwapTradesTab.setText(Res.get("portfolio.tab.bsqSwap").toUpperCase()); navigationListener = (viewPath, data) -> { if (viewPath.size() == 3 && viewPath.indexOf(PortfolioView.class) == 1) @@ -100,6 +102,8 @@ public class PortfolioView extends ActivatableView { navigation.navigateTo(MainView.class, PortfolioView.class, PendingTradesView.class); else if (newValue == closedTradesTab) navigation.navigateTo(MainView.class, PortfolioView.class, ClosedTradesView.class); + else if (newValue == bsqSwapTradesTab) + navigation.navigateTo(MainView.class, PortfolioView.class, UnconfirmedBsqSwapsView.class); else if (newValue == failedTradesTab) navigation.navigateTo(MainView.class, PortfolioView.class, FailedTradesView.class); else if (newValue == editOpenOfferTab) @@ -163,6 +167,8 @@ public class PortfolioView extends ActivatableView { navigation.navigateTo(MainView.class, PortfolioView.class, PendingTradesView.class); else if (root.getSelectionModel().getSelectedItem() == closedTradesTab) navigation.navigateTo(MainView.class, PortfolioView.class, ClosedTradesView.class); + else if (root.getSelectionModel().getSelectedItem() == bsqSwapTradesTab) + navigation.navigateTo(MainView.class, PortfolioView.class, UnconfirmedBsqSwapsView.class); else if (root.getSelectionModel().getSelectedItem() == failedTradesTab) navigation.navigateTo(MainView.class, PortfolioView.class, FailedTradesView.class); else if (root.getSelectionModel().getSelectedItem() == editOpenOfferTab) { @@ -196,6 +202,8 @@ public class PortfolioView extends ActivatableView { currentTab = pendingTradesTab; } else if (view instanceof ClosedTradesView) { currentTab = closedTradesTab; + } else if (view instanceof UnconfirmedBsqSwapsView) { + currentTab = bsqSwapTradesTab; } else if (view instanceof FailedTradesView) { currentTab = failedTradesTab; } else if (view instanceof EditOfferView) { @@ -218,7 +226,7 @@ public class PortfolioView extends ActivatableView { selectOpenOffersView((OpenOffersView) view); } } else if (view instanceof DuplicateOfferView) { - if (duplicateOfferView == null && data instanceof OfferPayload && data != null) { + if (duplicateOfferView == null && data instanceof OfferPayload) { viewLoader.removeFromCache(viewClass); // remove cached dialog view = viewLoader.load(viewClass); // and load a fresh one duplicateOfferView = (DuplicateOfferView) view; diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/bsqswaps/UnconfirmedBsqSwapsDataModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/bsqswaps/UnconfirmedBsqSwapsDataModel.java new file mode 100644 index 0000000000..3f01e7b9db --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/bsqswaps/UnconfirmedBsqSwapsDataModel.java @@ -0,0 +1,88 @@ +/* + * 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.desktop.main.portfolio.bsqswaps; + +import bisq.desktop.common.model.ActivatableDataModel; + +import bisq.core.btc.listeners.BsqBalanceListener; +import bisq.core.btc.wallet.BsqWalletService; +import bisq.core.offer.Offer; +import bisq.core.offer.OfferDirection; +import bisq.core.trade.bsq_swap.BsqSwapTradeManager; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; + +import com.google.inject.Inject; + +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; + +import java.util.stream.Collectors; + +class UnconfirmedBsqSwapsDataModel extends ActivatableDataModel { + + final BsqSwapTradeManager bsqSwapTradeManager; + private final BsqWalletService bsqWalletService; + private final ObservableList list = FXCollections.observableArrayList(); + private final ListChangeListener tradesListChangeListener; + private final BsqBalanceListener bsqBalanceListener; + + @Inject + public UnconfirmedBsqSwapsDataModel(BsqSwapTradeManager bsqSwapTradeManager, + BsqWalletService bsqWalletService) { + this.bsqSwapTradeManager = bsqSwapTradeManager; + this.bsqWalletService = bsqWalletService; + + tradesListChangeListener = change -> applyList(); + bsqBalanceListener = (availableBalance, availableNonBsqBalance, unverifiedBalance, + unconfirmedChangeBalance, lockedForVotingBalance, lockedInBondsBalance, + unlockingBondsBalance) -> applyList(); + } + + @Override + protected void activate() { + applyList(); + bsqSwapTradeManager.getObservableList().addListener(tradesListChangeListener); + bsqWalletService.addBsqBalanceListener(bsqBalanceListener); + } + + @Override + protected void deactivate() { + bsqSwapTradeManager.getObservableList().removeListener(tradesListChangeListener); + bsqWalletService.removeBsqBalanceListener(bsqBalanceListener); + } + + public ObservableList getList() { + return list; + } + + public OfferDirection getDirection(Offer offer) { + return bsqSwapTradeManager.wasMyOffer(offer) ? offer.getDirection() : offer.getMirroredDirection(); + } + + private void applyList() { + list.clear(); + + list.addAll(bsqSwapTradeManager.getUnconfirmedBsqSwapTrades() + .map(bsqSwapTrade -> new UnconfirmedBsqSwapsListItem(bsqWalletService, bsqSwapTrade)) + .collect(Collectors.toList())); + + // we sort by date, the earliest first + list.sort((o1, o2) -> o2.getBsqSwapTrade().getDate().compareTo(o1.getBsqSwapTrade().getDate())); + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/bsqswaps/UnconfirmedBsqSwapsListItem.java b/desktop/src/main/java/bisq/desktop/main/portfolio/bsqswaps/UnconfirmedBsqSwapsListItem.java new file mode 100644 index 0000000000..5ccc33f71d --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/bsqswaps/UnconfirmedBsqSwapsListItem.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.desktop.main.portfolio.bsqswaps; + +import bisq.desktop.components.indicator.TxConfidenceIndicator; +import bisq.desktop.util.GUIUtil; + +import bisq.core.btc.listeners.TxConfidenceListener; +import bisq.core.btc.wallet.BsqWalletService; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; + +import org.bitcoinj.core.TransactionConfidence; + +import javafx.scene.control.Tooltip; + +import lombok.Getter; + +import javax.annotation.Nullable; + +class UnconfirmedBsqSwapsListItem { + @Getter + private final BsqSwapTrade bsqSwapTrade; + private final BsqWalletService bsqWalletService; + private final String txId; + @Getter + private int confirmations = 0; + @Getter + private TxConfidenceIndicator txConfidenceIndicator; + private TxConfidenceListener txConfidenceListener; + + UnconfirmedBsqSwapsListItem(BsqWalletService bsqWalletService, BsqSwapTrade bsqSwapTrade) { + this.bsqSwapTrade = bsqSwapTrade; + this.bsqWalletService = bsqWalletService; + + txId = bsqSwapTrade.getTxId(); + txConfidenceIndicator = new TxConfidenceIndicator(); + txConfidenceIndicator.setId("funds-confidence"); + Tooltip tooltip = new Tooltip(); + txConfidenceIndicator.setProgress(0); + txConfidenceIndicator.setPrefSize(24, 24); + txConfidenceIndicator.setTooltip(tooltip); + + txConfidenceListener = new TxConfidenceListener(txId) { + @Override + public void onTransactionConfidenceChanged(TransactionConfidence confidence) { + updateConfidence(confidence, tooltip); + } + }; + bsqWalletService.addTxConfidenceListener(txConfidenceListener); + updateConfidence(bsqWalletService.getConfidenceForTxId(txId), tooltip); + } + + UnconfirmedBsqSwapsListItem() { + bsqSwapTrade = null; + bsqWalletService = null; + txId = null; + + } + + private void updateConfidence(@Nullable TransactionConfidence confidence, Tooltip tooltip) { + if (confidence != null) { + GUIUtil.updateConfidence(confidence, tooltip, txConfidenceIndicator); + confirmations = confidence.getDepthInBlocks(); + } + } + + public void cleanup() { + bsqWalletService.removeTxConfidenceListener(txConfidenceListener); + } + +} diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/bsqswaps/UnconfirmedBsqSwapsView.fxml b/desktop/src/main/java/bisq/desktop/main/portfolio/bsqswaps/UnconfirmedBsqSwapsView.fxml new file mode 100644 index 0000000000..0231c587ee --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/bsqswaps/UnconfirmedBsqSwapsView.fxml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/bsqswaps/UnconfirmedBsqSwapsView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/bsqswaps/UnconfirmedBsqSwapsView.java new file mode 100644 index 0000000000..e1282226c8 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/bsqswaps/UnconfirmedBsqSwapsView.java @@ -0,0 +1,593 @@ +/* + * 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.desktop.main.portfolio.bsqswaps; + +import bisq.desktop.common.view.ActivatableViewAndModel; +import bisq.desktop.common.view.FxmlView; +import bisq.desktop.components.AutoTooltipButton; +import bisq.desktop.components.AutoTooltipLabel; +import bisq.desktop.components.HyperlinkWithIcon; +import bisq.desktop.components.InputTextField; +import bisq.desktop.components.PeerInfoIconTrading; +import bisq.desktop.main.overlays.windows.BsqTradeDetailsWindow; +import bisq.desktop.util.GUIUtil; + +import bisq.core.alert.PrivateNotificationManager; +import bisq.core.locale.Res; +import bisq.core.offer.Offer; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.user.Preferences; + +import bisq.network.p2p.NodeAddress; + +import bisq.common.config.Config; + +import com.googlecode.jcsv.writer.CSVEntryConverter; + +import javax.inject.Inject; +import javax.inject.Named; + +import javafx.fxml.FXML; + +import javafx.stage.Stage; + +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; + +import javafx.geometry.Insets; + +import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.beans.value.ChangeListener; + +import javafx.collections.transformation.FilteredList; +import javafx.collections.transformation.SortedList; + +import javafx.util.Callback; + +import java.util.Comparator; +import java.util.function.Function; + +@FxmlView +public class UnconfirmedBsqSwapsView extends ActivatableViewAndModel { + private final boolean useDevPrivilegeKeys; + + private enum ColumnNames { + TRADE_ID(Res.get("shared.tradeId")), + DATE(Res.get("shared.dateTime")), + MARKET(Res.get("shared.market")), + PRICE(Res.get("shared.price")), + AMOUNT(Res.get("shared.amountWithCur", Res.getBaseCurrencyCode())), + VOLUME(Res.get("shared.amount")), + TX_FEE(Res.get("shared.txFee")), + TRADE_FEE(Res.get("shared.tradeFee")), + OFFER_TYPE(Res.get("shared.offerType")), + CONF(Res.get("shared.confirmations")); + + private final String text; + + ColumnNames(String text) { + this.text = text; + } + + @Override + public String toString() { + return text; + } + } + + @FXML + TableView tableView; + @FXML + TableColumn + priceColumn, + amountColumn, + volumeColumn, + txFeeColumn, + tradeFeeColumn, + marketColumn, + directionColumn, + dateColumn, + tradeIdColumn, + confidenceColumn, + avatarColumn; + @FXML + HBox searchBox; + @FXML + AutoTooltipLabel filterLabel; + @FXML + InputTextField filterTextField; + @FXML + Pane searchBoxSpacer; + @FXML + AutoTooltipButton exportButton; + @FXML + Label numItems; + @FXML + Region footerSpacer; + + private final BsqTradeDetailsWindow window; + private final Preferences preferences; + private final PrivateNotificationManager privateNotificationManager; + private SortedList sortedList; + private FilteredList filteredList; + private ChangeListener filterTextFieldListener; + private ChangeListener widthListener; + + @Inject + public UnconfirmedBsqSwapsView(UnconfirmedBsqSwapsViewModel model, + BsqTradeDetailsWindow bsqTradeDetailsWindow, + Preferences preferences, + PrivateNotificationManager privateNotificationManager, + @Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) { + super(model); + this.window = bsqTradeDetailsWindow; + this.preferences = preferences; + this.privateNotificationManager = privateNotificationManager; + this.useDevPrivilegeKeys = useDevPrivilegeKeys; + } + + @Override + public void initialize() { + widthListener = (observable, oldValue, newValue) -> onWidthChange((double) newValue); + txFeeColumn.setGraphic(new AutoTooltipLabel(ColumnNames.TX_FEE.toString())); + tradeFeeColumn.setGraphic(new AutoTooltipLabel(ColumnNames.TRADE_FEE.toString())); + priceColumn.setGraphic(new AutoTooltipLabel(ColumnNames.PRICE.toString())); + amountColumn.setGraphic(new AutoTooltipLabel(ColumnNames.AMOUNT.toString())); + volumeColumn.setGraphic(new AutoTooltipLabel(ColumnNames.VOLUME.toString())); + marketColumn.setGraphic(new AutoTooltipLabel(ColumnNames.MARKET.toString())); + directionColumn.setGraphic(new AutoTooltipLabel(ColumnNames.OFFER_TYPE.toString())); + dateColumn.setGraphic(new AutoTooltipLabel(ColumnNames.DATE.toString())); + tradeIdColumn.setGraphic(new AutoTooltipLabel(ColumnNames.TRADE_ID.toString())); + confidenceColumn.setGraphic(new AutoTooltipLabel(ColumnNames.CONF.toString())); + avatarColumn.setText(""); + + tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + tableView.setPlaceholder(new AutoTooltipLabel(Res.get("table.placeholder.noItems", Res.get("shared.trades")))); + + setTradeIdColumnCellFactory(); + setDirectionColumnCellFactory(); + setAmountColumnCellFactory(); + setTxFeeColumnCellFactory(); + setTradeFeeColumnCellFactory(); + setPriceColumnCellFactory(); + setVolumeColumnCellFactory(); + setDateColumnCellFactory(); + setMarketColumnCellFactory(); + setConfidenceColumnCellFactory(); + setAvatarColumnCellFactory(); + + tradeIdColumn.setComparator(Comparator.comparing(o -> o.getBsqSwapTrade().getId())); + dateColumn.setComparator(Comparator.comparing(o -> o.getBsqSwapTrade().getDate())); + directionColumn.setComparator(Comparator.comparing(o -> o.getBsqSwapTrade().getOffer().getDirection())); + marketColumn.setComparator(Comparator.comparing(model::getMarketLabel)); + priceColumn.setComparator(Comparator.comparing(model::getPrice, Comparator.nullsFirst(Comparator.naturalOrder()))); + volumeColumn.setComparator(nullsFirstComparingAsTrade(BsqSwapTrade::getVolume)); + amountColumn.setComparator(Comparator.comparing(model::getAmount, Comparator.nullsFirst(Comparator.naturalOrder()))); + avatarColumn.setComparator(Comparator.comparing( + o -> model.getNumPastTrades(o.getBsqSwapTrade()), + Comparator.nullsFirst(Comparator.naturalOrder()) + )); + txFeeColumn.setComparator(nullsFirstComparing(BsqSwapTrade::getTxFeePerVbyte)); + txFeeColumn.setComparator(Comparator.comparing(model::getTxFee, Comparator.nullsFirst(Comparator.naturalOrder()))); + + // + tradeFeeColumn.setComparator(Comparator.comparing(item -> { + String tradeFee = model.getTradeFee(item); + // We want to separate BSQ and BTC fees so we use a prefix + if (item.getBsqSwapTrade().getOffer().isCurrencyForMakerFeeBtc()) { + return "BTC" + tradeFee; + } else { + return "BSQ" + tradeFee; + } + }, Comparator.nullsFirst(Comparator.naturalOrder()))); + confidenceColumn.setComparator(Comparator.comparing(model::getConfidence)); + + dateColumn.setSortType(TableColumn.SortType.DESCENDING); + tableView.getSortOrder().add(dateColumn); + + filterLabel.setText(Res.get("shared.filter")); + HBox.setMargin(filterLabel, new Insets(5, 0, 0, 10)); + filterTextFieldListener = (observable, oldValue, newValue) -> applyFilteredListPredicate(filterTextField.getText()); + searchBox.setSpacing(5); + HBox.setHgrow(searchBoxSpacer, Priority.ALWAYS); + + numItems.setId("num-offers"); + numItems.setPadding(new Insets(-5, 0, 0, 10)); + HBox.setHgrow(footerSpacer, Priority.ALWAYS); + HBox.setMargin(exportButton, new Insets(0, 10, 0, 0)); + exportButton.updateText(Res.get("shared.exportCSV")); + } + + @Override + protected void activate() { + filteredList = new FilteredList<>(model.getList()); + + sortedList = new SortedList<>(filteredList); + sortedList.comparatorProperty().bind(tableView.comparatorProperty()); + + tableView.setItems(sortedList); + + numItems.setText(Res.get("shared.numItemsLabel", sortedList.size())); + exportButton.setOnAction(event -> { + CSVEntryConverter headerConverter = item -> { + String[] columns = new String[ColumnNames.values().length]; + for (ColumnNames m : ColumnNames.values()) { + columns[m.ordinal()] = m.toString(); + } + return columns; + }; + CSVEntryConverter contentConverter = item -> { + String[] columns = new String[ColumnNames.values().length]; + columns[ColumnNames.TRADE_ID.ordinal()] = model.getTradeId(item); + columns[ColumnNames.DATE.ordinal()] = model.getDate(item); + columns[ColumnNames.MARKET.ordinal()] = model.getMarketLabel(item); + columns[ColumnNames.PRICE.ordinal()] = model.getPrice(item); + columns[ColumnNames.AMOUNT.ordinal()] = model.getAmount(item); + columns[ColumnNames.VOLUME.ordinal()] = model.getVolume(item); + columns[ColumnNames.TX_FEE.ordinal()] = model.getTxFee(item); + columns[ColumnNames.TRADE_FEE.ordinal()] = model.getTradeFee(item); + columns[ColumnNames.OFFER_TYPE.ordinal()] = model.getDirectionLabel(item); + columns[ColumnNames.CONF.ordinal()] = String.valueOf(model.getConfidence(item)); + return columns; + }; + + GUIUtil.exportCSV("bsqSwapHistory.csv", headerConverter, contentConverter, + new UnconfirmedBsqSwapsListItem(), sortedList, (Stage) root.getScene().getWindow()); + }); + + filterTextField.textProperty().addListener(filterTextFieldListener); + applyFilteredListPredicate(filterTextField.getText()); + root.widthProperty().addListener(widthListener); + onWidthChange(root.getWidth()); + } + + @Override + protected void deactivate() { + sortedList.comparatorProperty().unbind(); + exportButton.setOnAction(null); + + filterTextField.textProperty().removeListener(filterTextFieldListener); + root.widthProperty().removeListener(widthListener); + } + + private static > Comparator nullsFirstComparing( + Function keyExtractor) { + return Comparator.comparing( + o -> o.getBsqSwapTrade() != null ? keyExtractor.apply(o.getBsqSwapTrade()) : null, + Comparator.nullsFirst(Comparator.naturalOrder()) + ); + } + + private static > Comparator nullsFirstComparingAsTrade( + Function keyExtractor) { + return Comparator.comparing( + o -> keyExtractor.apply(o.getBsqSwapTrade()), + Comparator.nullsFirst(Comparator.naturalOrder()) + ); + } + + private void onWidthChange(double width) { + txFeeColumn.setVisible(width > 1200); + tradeFeeColumn.setVisible(width > 1300); + } + + private void applyFilteredListPredicate(String filterString) { + filteredList.setPredicate(item -> { + if (filterString.isEmpty()) + return true; + + BsqSwapTrade bsqSwapTrade = item.getBsqSwapTrade(); + Offer offer = bsqSwapTrade.getOffer(); + if (offer.getId().contains(filterString)) { + return true; + } + if (model.getDate(item).contains(filterString)) { + return true; + } + if (model.getMarketLabel(item).contains(filterString)) { + return true; + } + if (model.getPrice(item).contains(filterString)) { + return true; + } + if (model.getVolume(item).contains(filterString)) { + return true; + } + if (model.getAmount(item).contains(filterString)) { + return true; + } + if (model.getTradeFee(item).contains(filterString)) { + return true; + } + if (model.getTxFee(item).contains(filterString)) { + return true; + } + if (String.valueOf(model.getConfidence(item)).contains(filterString)) { + return true; + } + if (model.getDirectionLabel(item).contains(filterString)) { + return true; + } + if (offer.getPaymentMethod().getDisplayString().contains(filterString)) { + return true; + } + if (bsqSwapTrade.getTxId() != null && bsqSwapTrade.getTxId().contains(filterString)) { + return true; + } + if (bsqSwapTrade.getTradeProtocolModel().getTempTradingPeerNodeAddress().getFullAddress().contains(filterString)) { + return true; + } + + return false; + }); + } + + private void setTradeIdColumnCellFactory() { + tradeIdColumn.getStyleClass().add("first-column"); + tradeIdColumn.setCellValueFactory((offerListItem) -> new ReadOnlyObjectWrapper<>(offerListItem.getValue())); + tradeIdColumn.setCellFactory( + new Callback<>() { + + @Override + public TableCell call(TableColumn column) { + return new TableCell<>() { + private HyperlinkWithIcon field; + + @Override + public void updateItem(final UnconfirmedBsqSwapsListItem item, boolean empty) { + super.updateItem(item, empty); + if (item != null && !empty) { + field = new HyperlinkWithIcon(model.getTradeId(item)); + field.setOnAction(event -> { + window.show(item.getBsqSwapTrade()); + }); + field.setTooltip(new Tooltip(Res.get("tooltip.openPopupForDetails"))); + setGraphic(field); + } else { + setGraphic(null); + if (field != null) + field.setOnAction(null); + } + } + }; + } + }); + } + + private void setDateColumnCellFactory() { + dateColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue())); + dateColumn.setCellFactory( + new Callback<>() { + @Override + public TableCell call( + TableColumn column) { + return new TableCell<>() { + @Override + public void updateItem(final UnconfirmedBsqSwapsListItem item, boolean empty) { + super.updateItem(item, empty); + if (item != null) + setGraphic(new AutoTooltipLabel(model.getDate(item))); + else + setGraphic(null); + } + }; + } + }); + } + + private void setMarketColumnCellFactory() { + marketColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue())); + marketColumn.setCellFactory( + new Callback<>() { + @Override + public TableCell call( + TableColumn column) { + return new TableCell<>() { + @Override + public void updateItem(final UnconfirmedBsqSwapsListItem item, boolean empty) { + super.updateItem(item, empty); + setGraphic(new AutoTooltipLabel(model.getMarketLabel(item))); + } + }; + } + }); + } + + private void setConfidenceColumnCellFactory() { + confidenceColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue())); + confidenceColumn.setCellFactory( + new Callback<>() { + @Override + public TableCell call( + TableColumn column) { + return new TableCell<>() { + @Override + public void updateItem(final UnconfirmedBsqSwapsListItem item, boolean empty) { + super.updateItem(item, empty); + + if (item != null && !empty) { + setGraphic(item.getTxConfidenceIndicator()); + } else { + setGraphic(null); + } + } + }; + } + }); + } + + @SuppressWarnings("UnusedReturnValue") + private TableColumn setAvatarColumnCellFactory() { + avatarColumn.getStyleClass().addAll("last-column", "avatar-column"); + avatarColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue())); + avatarColumn.setCellFactory( + new Callback<>() { + @Override + public TableCell call(TableColumn column) { + return new TableCell<>() { + + @Override + public void updateItem(final UnconfirmedBsqSwapsListItem newItem, boolean empty) { + super.updateItem(newItem, empty); + + if (newItem != null && !empty/* && newItem.getAtomicTrade() instanceof Trade*/) { + var bsqSwapTrade = newItem.getBsqSwapTrade(); + int numPastTrades = model.getNumPastTrades(bsqSwapTrade); + final NodeAddress tradingPeerNodeAddress = bsqSwapTrade.getTradingPeerNodeAddress(); + String role = Res.get("peerInfoIcon.tooltip.tradePeer"); + Node peerInfoIcon = new PeerInfoIconTrading(tradingPeerNodeAddress, + role, + numPastTrades, + privateNotificationManager, + bsqSwapTrade.getOffer(), + preferences, + model.accountAgeWitnessService, + useDevPrivilegeKeys); + setPadding(new Insets(1, 15, 0, 0)); + setGraphic(peerInfoIcon); + } else { + setGraphic(null); + } + } + }; + } + }); + return avatarColumn; + } + + private void setAmountColumnCellFactory() { + amountColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue())); + amountColumn.setCellFactory( + new Callback<>() { + @Override + public TableCell call( + TableColumn column) { + return new TableCell<>() { + @Override + public void updateItem(final UnconfirmedBsqSwapsListItem item, boolean empty) { + super.updateItem(item, empty); + setGraphic(new AutoTooltipLabel(model.getAmount(item))); + } + }; + } + }); + } + + private void setPriceColumnCellFactory() { + priceColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue())); + priceColumn.setCellFactory( + new Callback<>() { + @Override + public TableCell call( + TableColumn column) { + return new TableCell<>() { + @Override + public void updateItem(final UnconfirmedBsqSwapsListItem item, boolean empty) { + super.updateItem(item, empty); + setGraphic(new AutoTooltipLabel(model.getPrice(item))); + } + }; + } + }); + } + + private void setVolumeColumnCellFactory() { + volumeColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue())); + volumeColumn.setCellFactory( + new Callback<>() { + @Override + public TableCell call( + TableColumn column) { + return new TableCell<>() { + @Override + public void updateItem(final UnconfirmedBsqSwapsListItem item, boolean empty) { + super.updateItem(item, empty); + if (item != null) + setGraphic(new AutoTooltipLabel(model.getVolume(item))); + else + setGraphic(null); + } + }; + } + }); + } + + private void setDirectionColumnCellFactory() { + directionColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue())); + directionColumn.setCellFactory( + new Callback<>() { + @Override + public TableCell call( + TableColumn column) { + return new TableCell<>() { + @Override + public void updateItem(final UnconfirmedBsqSwapsListItem item, boolean empty) { + super.updateItem(item, empty); + setGraphic(new AutoTooltipLabel(model.getDirectionLabel(item))); + } + }; + } + }); + } + + private void setTxFeeColumnCellFactory() { + txFeeColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue())); + txFeeColumn.setCellFactory( + new Callback<>() { + @Override + public TableCell call( + TableColumn column) { + return new TableCell<>() { + @Override + public void updateItem(final UnconfirmedBsqSwapsListItem item, boolean empty) { + super.updateItem(item, empty); + setGraphic(new AutoTooltipLabel(model.getTxFee(item))); + } + }; + } + }); + } + + private void setTradeFeeColumnCellFactory() { + tradeFeeColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue())); + tradeFeeColumn.setCellFactory( + new Callback<>() { + @Override + public TableCell call( + TableColumn column) { + return new TableCell<>() { + @Override + public void updateItem(final UnconfirmedBsqSwapsListItem item, boolean empty) { + super.updateItem(item, empty); + setGraphic(new AutoTooltipLabel(model.getTradeFee(item))); + } + }; + } + }); + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/bsqswaps/UnconfirmedBsqSwapsViewModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/bsqswaps/UnconfirmedBsqSwapsViewModel.java new file mode 100644 index 0000000000..78ff626177 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/bsqswaps/UnconfirmedBsqSwapsViewModel.java @@ -0,0 +1,139 @@ +/* + * 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.desktop.main.portfolio.bsqswaps; + +import bisq.desktop.common.model.ActivatableWithDataModel; +import bisq.desktop.common.model.ViewModel; +import bisq.desktop.util.DisplayUtils; + +import bisq.core.account.witness.AccountAgeWitnessService; +import bisq.core.locale.CurrencyUtil; +import bisq.core.trade.ClosedTradableManager; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; +import bisq.core.util.FormattingUtils; +import bisq.core.util.VolumeUtil; +import bisq.core.util.coin.BsqFormatter; +import bisq.core.util.coin.CoinFormatter; + +import org.bitcoinj.core.Coin; + +import com.google.inject.Inject; + +import javax.inject.Named; + +import javafx.collections.ObservableList; + +class UnconfirmedBsqSwapsViewModel extends ActivatableWithDataModel implements ViewModel { + private final BsqFormatter bsqFormatter; + private final CoinFormatter btcFormatter; + final AccountAgeWitnessService accountAgeWitnessService; + private final ClosedTradableManager closedTradableManager; + + @Inject + public UnconfirmedBsqSwapsViewModel(UnconfirmedBsqSwapsDataModel dataModel, + AccountAgeWitnessService accountAgeWitnessService, + ClosedTradableManager closedTradableManager, + BsqFormatter bsqFormatter, + @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter) { + super(dataModel); + this.accountAgeWitnessService = accountAgeWitnessService; + this.closedTradableManager = closedTradableManager; + this.bsqFormatter = bsqFormatter; + this.btcFormatter = btcFormatter; + } + + public ObservableList getList() { + return dataModel.getList(); + } + + String getTradeId(UnconfirmedBsqSwapsListItem item) { + return item.getBsqSwapTrade().getShortId(); + } + + String getAmount(UnconfirmedBsqSwapsListItem item) { + if (item == null) + return ""; + + return btcFormatter.formatCoin(item.getBsqSwapTrade().getAmount()); + } + + String getPrice(UnconfirmedBsqSwapsListItem item) { + if (item == null) + return ""; + + return FormattingUtils.formatPrice(item.getBsqSwapTrade().getPrice()); + } + + String getVolume(UnconfirmedBsqSwapsListItem item) { + if (item == null) + return ""; + + return VolumeUtil.formatVolumeWithCode(item.getBsqSwapTrade().getVolume()); + } + + String getTxFee(UnconfirmedBsqSwapsListItem item) { + if (item == null) + return ""; + + return btcFormatter.formatCoinWithCode(Coin.valueOf(item.getBsqSwapTrade().getBsqSwapProtocolModel().getTxFee())); + } + + String getTradeFee(UnconfirmedBsqSwapsListItem item) { + if (item == null) + return ""; + + if (wasMyOffer(item.getBsqSwapTrade())) { + return bsqFormatter.formatCoinWithCode(item.getBsqSwapTrade().getMakerFeeAsLong()); + } else { + return bsqFormatter.formatCoinWithCode(item.getBsqSwapTrade().getTakerFeeAsLong()); + } + } + + String getDirectionLabel(UnconfirmedBsqSwapsListItem item) { + if (item == null) + return ""; + + return DisplayUtils.getDirectionWithCode(dataModel.getDirection(item.getBsqSwapTrade().getOffer()), + item.getBsqSwapTrade().getOffer().getCurrencyCode()); + } + + String getDate(UnconfirmedBsqSwapsListItem item) { + return DisplayUtils.formatDateTime(item.getBsqSwapTrade().getDate()); + } + + String getMarketLabel(UnconfirmedBsqSwapsListItem item) { + if (item == null) + return ""; + + return CurrencyUtil.getCurrencyPair(item.getBsqSwapTrade().getOffer().getCurrencyCode()); + } + + int getConfidence(UnconfirmedBsqSwapsListItem item) { + if ((item == null)) + return 0; + return item.getConfirmations(); + } + + int getNumPastTrades(BsqSwapTrade bsqSwapTrade) { + return closedTradableManager.getNumPastTrades(bsqSwapTrade); + } + + boolean wasMyOffer(BsqSwapTrade bsqSwapTrade) { + return dataModel.bsqSwapTradeManager.wasMyOffer(bsqSwapTrade.getOffer()); + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesDataModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesDataModel.java index 76026c6da4..406a816edd 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesDataModel.java @@ -18,18 +18,22 @@ package bisq.desktop.main.portfolio.closedtrades; import bisq.desktop.common.model.ActivatableDataModel; -import bisq.desktop.main.PriceUtil; +import bisq.core.account.witness.AccountAgeWitnessService; +import bisq.core.btc.listeners.BsqBalanceListener; +import bisq.core.btc.wallet.BsqWalletService; import bisq.core.monetary.Price; import bisq.core.monetary.Volume; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferDirection; import bisq.core.provider.price.MarketPrice; import bisq.core.provider.price.PriceFeedService; -import bisq.core.trade.Tradable; -import bisq.core.trade.closed.ClosedTradableManager; -import bisq.core.trade.closed.ClosedTradeUtil; +import bisq.core.trade.ClosedTradableManager; +import bisq.core.trade.ClosedTradableUtil; +import bisq.core.trade.bsq_swap.BsqSwapTradeManager; +import bisq.core.trade.model.Tradable; import bisq.core.user.Preferences; +import bisq.core.util.PriceUtil; import bisq.core.util.VolumeUtil; import org.bitcoinj.core.Coin; @@ -40,103 +44,112 @@ import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; -import java.util.List; -import java.util.Map; import java.util.Optional; -import java.util.function.Supplier; import java.util.stream.Collectors; +import java.util.stream.Stream; class ClosedTradesDataModel extends ActivatableDataModel { final ClosedTradableManager closedTradableManager; - private final ClosedTradeUtil closedTradeUtil; + private final BsqWalletService bsqWalletService; + private final BsqSwapTradeManager bsqSwapTradeManager; private final Preferences preferences; private final PriceFeedService priceFeedService; - private final ObservableList list = FXCollections.observableArrayList(); + final AccountAgeWitnessService accountAgeWitnessService; + private final ObservableList list = FXCollections.observableArrayList(); private final ListChangeListener tradesListChangeListener; + private final BsqBalanceListener bsqBalanceListener; @Inject public ClosedTradesDataModel(ClosedTradableManager closedTradableManager, - ClosedTradeUtil closedTradeUtil, + BsqSwapTradeManager bsqSwapTradeManager, + BsqWalletService bsqWalletService, Preferences preferences, - PriceFeedService priceFeedService) { + PriceFeedService priceFeedService, + AccountAgeWitnessService accountAgeWitnessService) { this.closedTradableManager = closedTradableManager; - this.closedTradeUtil = closedTradeUtil; + this.bsqSwapTradeManager = bsqSwapTradeManager; + this.bsqWalletService = bsqWalletService; this.preferences = preferences; this.priceFeedService = priceFeedService; + this.accountAgeWitnessService = accountAgeWitnessService; tradesListChangeListener = change -> applyList(); + bsqBalanceListener = (availableBalance, availableNonBsqBalance, unverifiedBalance, + unconfirmedChangeBalance, lockedForVotingBalance, lockedInBondsBalance, + unlockingBondsBalance) -> applyList(); } @Override protected void activate() { applyList(); closedTradableManager.getObservableList().addListener(tradesListChangeListener); + bsqSwapTradeManager.getObservableList().addListener(tradesListChangeListener); + bsqWalletService.addBsqBalanceListener(bsqBalanceListener); } @Override protected void deactivate() { closedTradableManager.getObservableList().removeListener(tradesListChangeListener); + bsqSwapTradeManager.getObservableList().removeListener(tradesListChangeListener); + bsqWalletService.removeBsqBalanceListener(bsqBalanceListener); } - public ObservableList getList() { + ObservableList getList() { return list; } - /** - * Supplies a List from this JFX ObservableList - * collection, for passing to core's ClosedTradeUtil which has no dependency on JFX. - */ - public final Supplier> tradableList = () -> list.stream() - .map(ClosedTradableListItem::getTradable) - .collect(Collectors.toList()); - - public OfferPayload.Direction getDirection(Offer offer) { + OfferDirection getDirection(Offer offer) { return closedTradableManager.wasMyOffer(offer) ? offer.getDirection() : offer.getMirroredDirection(); } - private void applyList() { - list.clear(); - - list.addAll(closedTradableManager.getObservableList().stream().map(ClosedTradableListItem::new).collect(Collectors.toList())); - - // we sort by date, earliest first - list.sort((o1, o2) -> o2.getTradable().getDate().compareTo(o1.getTradable().getDate())); - } - Coin getTotalAmount() { - return closedTradeUtil.getTotalAmount(tradableList.get()); + return ClosedTradableUtil.getTotalAmount(list); } - Map getTotalVolumeByCurrency() { - return closedTradeUtil.getTotalVolumeByCurrency(tradableList.get()); - } - - public Optional getVolumeInUserFiatCurrency(Coin amount) { + Optional getVolumeInUserFiatCurrency(Coin amount) { return getVolume(amount, preferences.getPreferredTradeCurrency().getCode()); } - public Optional getVolume(Coin amount, String currencyCode) { + Optional getVolume(Coin amount, String currencyCode) { MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode); if (marketPrice == null) { return Optional.empty(); } - // TODO Move PriceUtil & it's validators to core.util & core.util.validation - // before refactoring this.getVolume() to ClosedTradeUtil. Price price = PriceUtil.marketPriceToPrice(marketPrice); return Optional.of(VolumeUtil.getVolume(amount, price)); } - public Volume getBsqVolumeInUsdWithAveragePrice(Coin amount) { - return closedTradeUtil.getBsqVolumeInUsdWithAveragePrice(amount); + Volume getBsqVolumeInUsdWithAveragePrice(Coin amount) { + return closedTradableManager.getBsqVolumeInUsdWithAveragePrice(amount); } - public Coin getTotalTxFee() { - return closedTradeUtil.getTotalTxFee(tradableList.get()); + Coin getTotalTxFee() { + return ClosedTradableUtil.getTotalTxFee(list); } - public Coin getTotalTradeFee(boolean expectBtcFee) { - return closedTradeUtil.getTotalTradeFee(tradableList.get(), expectBtcFee); + Coin getTotalTradeFee(boolean expectBtcFee) { + return closedTradableManager.getTotalTradeFee(list, expectBtcFee); + } + + int getNumPastTrades(Tradable tradable) { + return closedTradableManager.getNumPastTrades(tradable); + } + + boolean isCurrencyForTradeFeeBtc(Tradable item) { + return item != null && closedTradableManager.isCurrencyForTradeFeeBtc(item); + } + + private void applyList() { + list.clear(); + list.addAll(getTradableStream().collect(Collectors.toList())); + // We sort by date, the earliest first + list.sort((o1, o2) -> o2.getDate().compareTo(o1.getDate())); + } + + private Stream getTradableStream() { + return Stream.concat(bsqSwapTradeManager.getConfirmedBsqSwapTrades(), + closedTradableManager.getObservableList().stream()); } } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesView.java index b4be92daf2..8a3b5980b2 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesView.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesView.java @@ -28,6 +28,7 @@ import bisq.desktop.components.InputTextField; import bisq.desktop.components.PeerInfoIconTrading; import bisq.desktop.main.MainView; import bisq.desktop.main.overlays.popups.Popup; +import bisq.desktop.main.overlays.windows.BsqTradeDetailsWindow; import bisq.desktop.main.overlays.windows.ClosedTradesSummaryWindow; import bisq.desktop.main.overlays.windows.OfferDetailsWindow; import bisq.desktop.main.overlays.windows.TradeDetailsWindow; @@ -38,11 +39,13 @@ import bisq.desktop.util.GUIUtil; import bisq.core.alert.PrivateNotificationManager; import bisq.core.locale.Res; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload; import bisq.core.offer.OpenOffer; -import bisq.core.trade.Contract; -import bisq.core.trade.Tradable; -import bisq.core.trade.Trade; +import bisq.core.offer.bisq_v1.OfferPayload; +import bisq.core.trade.model.Tradable; +import bisq.core.trade.model.TradeModel; +import bisq.core.trade.model.bisq_v1.Contract; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.model.bsq_swap.BsqSwapTrade; import bisq.core.user.Preferences; import bisq.network.p2p.NodeAddress; @@ -50,6 +53,8 @@ import bisq.network.p2p.NodeAddress; import bisq.common.config.Config; import bisq.common.crypto.KeyRing; +import com.google.protobuf.Message; + import com.googlecode.jcsv.writer.CSVEntryConverter; import javax.inject.Inject; @@ -86,6 +91,7 @@ import javafx.collections.transformation.SortedList; import javafx.util.Callback; import java.util.Comparator; +import java.util.Date; import java.util.function.Function; @FxmlView @@ -122,9 +128,9 @@ public class ClosedTradesView extends ActivatableViewAndModel tableView; + TableView tableView; @FXML - TableColumn priceColumn, deviationColumn, amountColumn, volumeColumn, + TableColumn priceColumn, deviationColumn, amountColumn, volumeColumn, txFeeColumn, tradeFeeColumn, buyerSecurityDepositColumn, sellerSecurityDepositColumn, marketColumn, directionColumn, dateColumn, tradeIdColumn, stateColumn, avatarColumn; @FXML @@ -143,19 +149,21 @@ public class ClosedTradesView extends ActivatableViewAndModel sortedList; - private FilteredList filteredList; + private SortedList sortedList; + private FilteredList filteredList; private ChangeListener filterTextFieldListener; private ChangeListener widthListener; @Inject public ClosedTradesView(ClosedTradesViewModel model, OfferDetailsWindow offerDetailsWindow, + BsqTradeDetailsWindow bsqTradeDetailsWindow, Navigation navigation, KeyRing keyRing, Preferences preferences, @@ -164,6 +172,7 @@ public class ClosedTradesView extends ActivatableViewAndModel o.getTradable().getId())); - dateColumn.setComparator(Comparator.comparing(o -> o.getTradable().getDate())); - directionColumn.setComparator(Comparator.comparing(o -> o.getTradable().getOffer().getDirection())); + tradeIdColumn.setComparator(Comparator.comparing(o -> o.getId())); + dateColumn.setComparator(Comparator.comparing(o -> o.getDate())); + directionColumn.setComparator(Comparator.comparing(o -> o.getOffer().getDirection())); marketColumn.setComparator(Comparator.comparing(model::getMarketLabel)); priceColumn.setComparator(Comparator.comparing(model::getPrice, Comparator.nullsFirst(Comparator.naturalOrder()))); deviationColumn.setComparator(Comparator.comparing(o -> - o.getTradable().getOffer().isUseMarketBasedPrice() ? o.getTradable().getOffer().getMarketPriceMargin() : 1, + o.getOffer().isUseMarketBasedPrice() ? o.getOffer().getMarketPriceMargin() : 1, Comparator.nullsFirst(Comparator.naturalOrder()))); - volumeColumn.setComparator(nullsFirstComparingAsTrade(Trade::getTradeVolume)); + volumeColumn.setComparator(nullsFirstComparingAsTrade(TradeModel::getVolume)); amountColumn.setComparator(Comparator.comparing(model::getAmount, Comparator.nullsFirst(Comparator.naturalOrder()))); avatarColumn.setComparator(Comparator.comparing( - o -> model.getNumPastTrades(o.getTradable()), + o -> model.dataModel.getNumPastTrades(o), Comparator.nullsFirst(Comparator.naturalOrder()) )); txFeeColumn.setComparator(nullsFirstComparing(o -> - o instanceof Trade ? ((Trade) o).getTxFee() : o.getOffer().getTxFee() + o instanceof TradeModel ? ((TradeModel) o).getTxFee() : o.getOffer().getTxFee() )); txFeeColumn.setComparator(Comparator.comparing(model::getTxFee, Comparator.nullsFirst(Comparator.naturalOrder()))); @@ -232,7 +241,7 @@ public class ClosedTradesView extends ActivatableViewAndModel { String tradeFee = model.getTradeFee(item, true); // We want to separate BSQ and BTC fees so we use a prefix - if (item.getTradable().getOffer().isCurrencyForMakerFeeBtc()) { + if (item.getOffer().isCurrencyForMakerFeeBtc()) { return "BTC" + tradeFee; } else { return "BSQ" + tradeFee; @@ -251,12 +260,12 @@ public class ClosedTradesView extends ActivatableViewAndModel { - final TableRow row = new TableRow<>(); - final ContextMenu rowMenu = new ContextMenu(); + TableRow row = new TableRow<>(); + ContextMenu rowMenu = new ContextMenu(); MenuItem editItem = new MenuItem(Res.get("portfolio.context.offerLikeThis")); editItem.setOnAction((event) -> { try { - OfferPayload offerPayload = row.getItem().getTradable().getOffer().getOfferPayload(); + OfferPayload offerPayload = row.getItem().getOffer().getOfferPayload().orElseThrow(); if (offerPayload.getPubKeyRing().equals(keyRing.getPubKeyRing())) { navigation.navigateToWithData(offerPayload, MainView.class, PortfolioView.class, DuplicateOfferView.class); } else { @@ -290,7 +299,7 @@ public class ClosedTradesView extends ActivatableViewAndModel(model.getList()); + filteredList = new FilteredList<>(model.dataModel.getList()); sortedList = new SortedList<>(filteredList); sortedList.comparatorProperty().bind(tableView.comparatorProperty()); @@ -299,14 +308,14 @@ public class ClosedTradesView extends ActivatableViewAndModel { - CSVEntryConverter headerConverter = item -> { + CSVEntryConverter headerConverter = item -> { String[] columns = new String[ColumnNames.values().length]; for (ColumnNames m : ColumnNames.values()) { columns[m.ordinal()] = m.toString(); } return columns; }; - CSVEntryConverter contentConverter = item -> { + CSVEntryConverter contentConverter = item -> { String[] columns = new String[ColumnNames.values().length]; columns[ColumnNames.TRADE_ID.ordinal()] = model.getTradeId(item); columns[ColumnNames.DATE.ordinal()] = model.getDate(item); @@ -317,7 +326,7 @@ public class ClosedTradesView extends ActivatableViewAndModel new ClosedTradesSummaryWindow(model).show()); @@ -353,16 +362,16 @@ public class ClosedTradesView extends ActivatableViewAndModel> Comparator nullsFirstComparing(Function keyExtractor) { + private static > Comparator nullsFirstComparing(Function keyExtractor) { return Comparator.comparing( - o -> o.getTradable() != null ? keyExtractor.apply(o.getTradable()) : null, + o -> o != null ? keyExtractor.apply(o) : null, Comparator.nullsFirst(Comparator.naturalOrder()) ); } - private static > Comparator nullsFirstComparingAsTrade(Function keyExtractor) { + private static > Comparator nullsFirstComparingAsTrade(Function keyExtractor) { return Comparator.comparing( - o -> o.getTradable() instanceof Trade ? keyExtractor.apply((Trade) o.getTradable()) : null, + o -> o instanceof TradeModel ? keyExtractor.apply((TradeModel) o) : null, Comparator.nullsFirst(Comparator.naturalOrder()) ); } @@ -375,59 +384,69 @@ public class ClosedTradesView extends ActivatableViewAndModel { + filteredList.setPredicate(tradable -> { if (filterString.isEmpty()) return true; - Tradable tradable = item.getTradable(); Offer offer = tradable.getOffer(); if (offer.getId().contains(filterString)) { return true; } - if (model.getDate(item).contains(filterString)) { + if (model.getDate(tradable).contains(filterString)) { return true; } - if (model.getMarketLabel(item).contains(filterString)) { + if (model.getMarketLabel(tradable).contains(filterString)) { return true; } - if (model.getPrice(item).contains(filterString)) { + if (model.getPrice(tradable).contains(filterString)) { return true; } - if (model.getPriceDeviation(item).contains(filterString)) { + if (model.getPriceDeviation(tradable).contains(filterString)) { return true; } - if (model.getVolume(item, true).contains(filterString)) { + if (model.getVolume(tradable, true).contains(filterString)) { return true; } - if (model.getAmount(item).contains(filterString)) { + if (model.getAmount(tradable).contains(filterString)) { return true; } - if (model.getTradeFee(item, true).contains(filterString)) { + if (model.getTradeFee(tradable, true).contains(filterString)) { return true; } - if (model.getTxFee(item).contains(filterString)) { + if (model.getTxFee(tradable).contains(filterString)) { return true; } - if (model.getBuyerSecurityDeposit(item).contains(filterString)) { + if (model.getBuyerSecurityDeposit(tradable).contains(filterString)) { return true; } - if (model.getSellerSecurityDeposit(item).contains(filterString)) { + if (model.getSellerSecurityDeposit(tradable).contains(filterString)) { return true; } - if (model.getState(item).contains(filterString)) { + if (model.getState(tradable).contains(filterString)) { return true; } - if (model.getDirectionLabel(item).contains(filterString)) { + if (model.getDirectionLabel(tradable).contains(filterString)) { return true; } if (offer.getPaymentMethod().getDisplayString().contains(filterString)) { return true; } - if (offer.getOfferFeePaymentTxId().contains(filterString)) { + if (offer.getOfferFeePaymentTxId() != null && + offer.getOfferFeePaymentTxId().contains(filterString)) { return true; } + if (tradable instanceof BsqSwapTrade) { + BsqSwapTrade bsqSwapTrade = (BsqSwapTrade) tradable; + if (bsqSwapTrade.getTxId() != null && bsqSwapTrade.getTxId().contains(filterString)) { + return true; + } + if (bsqSwapTrade.getTradeProtocolModel().getTempTradingPeerNodeAddress().getFullAddress().contains(filterString)) { + return true; + } + } + if (tradable instanceof Trade) { Trade trade = (Trade) tradable; if (trade.getTakerFeeTxId() != null && trade.getTakerFeeTxId().contains(filterString)) { @@ -468,22 +487,24 @@ public class ClosedTradesView extends ActivatableViewAndModel() { @Override - public TableCell call(TableColumn column) { + public TableCell call(TableColumn column) { return new TableCell<>() { private HyperlinkWithIcon field; @Override - public void updateItem(final ClosedTradableListItem item, boolean empty) { - super.updateItem(item, empty); - if (item != null && !empty) { - field = new HyperlinkWithIcon(model.getTradeId(item)); + public void updateItem(final Tradable tradable, boolean empty) { + super.updateItem(tradable, empty); + if (tradable != null && !empty) { + field = new HyperlinkWithIcon(model.getTradeId(tradable)); field.setOnAction(event -> { - Tradable tradable = item.getTradable(); - if (tradable instanceof Trade) + if (tradable instanceof Trade) { tradeDetailsWindow.show((Trade) tradable); - else if (tradable instanceof OpenOffer) + } else if (tradable instanceof BsqSwapTrade) { + bsqTradeDetailsWindow.show((BsqSwapTrade) tradable); + } else if (tradable instanceof OpenOffer) { offerDetailsWindow.show(tradable.getOffer()); + } }); field.setTooltip(new Tooltip(Res.get("tooltip.openPopupForDetails"))); setGraphic(field); @@ -503,11 +524,11 @@ public class ClosedTradesView extends ActivatableViewAndModel() { @Override - public TableCell call( - TableColumn column) { + public TableCell call( + TableColumn column) { return new TableCell<>() { @Override - public void updateItem(final ClosedTradableListItem item, boolean empty) { + public void updateItem(final Tradable item, boolean empty) { super.updateItem(item, empty); if (item != null) setGraphic(new AutoTooltipLabel(model.getDate(item))); @@ -524,11 +545,11 @@ public class ClosedTradesView extends ActivatableViewAndModel() { @Override - public TableCell call( - TableColumn column) { + public TableCell call( + TableColumn column) { return new TableCell<>() { @Override - public void updateItem(final ClosedTradableListItem item, boolean empty) { + public void updateItem(final Tradable item, boolean empty) { super.updateItem(item, empty); setGraphic(new AutoTooltipLabel(model.getMarketLabel(item))); } @@ -542,11 +563,11 @@ public class ClosedTradesView extends ActivatableViewAndModel() { @Override - public TableCell call( - TableColumn column) { + public TableCell call( + TableColumn column) { return new TableCell<>() { @Override - public void updateItem(final ClosedTradableListItem item, boolean empty) { + public void updateItem(final Tradable item, boolean empty) { super.updateItem(item, empty); if (item != null) setGraphic(new AutoTooltipLabel(model.getState(item))); @@ -559,31 +580,31 @@ public class ClosedTradesView extends ActivatableViewAndModel setAvatarColumnCellFactory() { + private TableColumn setAvatarColumnCellFactory() { avatarColumn.getStyleClass().addAll("last-column", "avatar-column"); avatarColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue())); avatarColumn.setCellFactory( new Callback<>() { @Override - public TableCell call(TableColumn column) { + public TableCell call(TableColumn column) { return new TableCell<>() { @Override - public void updateItem(final ClosedTradableListItem newItem, boolean empty) { - super.updateItem(newItem, empty); + public void updateItem(final Tradable item, boolean empty) { + super.updateItem(item, empty); - if (newItem != null && !empty && newItem.getTradable() instanceof Trade) { - Trade trade = (Trade) newItem.getTradable(); - int numPastTrades = model.getNumPastTrades(trade); - final NodeAddress tradingPeerNodeAddress = trade.getTradingPeerNodeAddress(); + if (item != null && !empty && item instanceof TradeModel) { + TradeModel tradeModel = (TradeModel) item; + int numPastTrades = model.dataModel.getNumPastTrades(tradeModel); + NodeAddress tradingPeerNodeAddress = tradeModel.getTradingPeerNodeAddress(); String role = Res.get("peerInfoIcon.tooltip.tradePeer"); Node peerInfoIcon = new PeerInfoIconTrading(tradingPeerNodeAddress, role, numPastTrades, privateNotificationManager, - trade, + tradeModel, preferences, - model.accountAgeWitnessService, + model.dataModel.accountAgeWitnessService, useDevPrivilegeKeys); setPadding(new Insets(1, 15, 0, 0)); setGraphic(peerInfoIcon); @@ -602,11 +623,11 @@ public class ClosedTradesView extends ActivatableViewAndModel() { @Override - public TableCell call( - TableColumn column) { + public TableCell call( + TableColumn column) { return new TableCell<>() { @Override - public void updateItem(final ClosedTradableListItem item, boolean empty) { + public void updateItem(final Tradable item, boolean empty) { super.updateItem(item, empty); setGraphic(new AutoTooltipLabel(model.getAmount(item))); } @@ -620,11 +641,11 @@ public class ClosedTradesView extends ActivatableViewAndModel() { @Override - public TableCell call( - TableColumn column) { + public TableCell call( + TableColumn column) { return new TableCell<>() { @Override - public void updateItem(final ClosedTradableListItem item, boolean empty) { + public void updateItem(final Tradable item, boolean empty) { super.updateItem(item, empty); setGraphic(new AutoTooltipLabel(model.getPrice(item))); } @@ -638,11 +659,11 @@ public class ClosedTradesView extends ActivatableViewAndModel() { @Override - public TableCell call( - TableColumn column) { + public TableCell call( + TableColumn column) { return new TableCell<>() { @Override - public void updateItem(final ClosedTradableListItem item, boolean empty) { + public void updateItem(final Tradable item, boolean empty) { super.updateItem(item, empty); setGraphic(new AutoTooltipLabel(model.getPriceDeviation(item))); } @@ -656,11 +677,11 @@ public class ClosedTradesView extends ActivatableViewAndModel() { @Override - public TableCell call( - TableColumn column) { + public TableCell call( + TableColumn column) { return new TableCell<>() { @Override - public void updateItem(final ClosedTradableListItem item, boolean empty) { + public void updateItem(final Tradable item, boolean empty) { super.updateItem(item, empty); if (item != null) setGraphic(new AutoTooltipLabel(model.getVolume(item, true))); @@ -677,11 +698,11 @@ public class ClosedTradesView extends ActivatableViewAndModel() { @Override - public TableCell call( - TableColumn column) { + public TableCell call( + TableColumn column) { return new TableCell<>() { @Override - public void updateItem(final ClosedTradableListItem item, boolean empty) { + public void updateItem(final Tradable item, boolean empty) { super.updateItem(item, empty); setGraphic(new AutoTooltipLabel(model.getDirectionLabel(item))); } @@ -695,11 +716,11 @@ public class ClosedTradesView extends ActivatableViewAndModel() { @Override - public TableCell call( - TableColumn column) { + public TableCell call( + TableColumn column) { return new TableCell<>() { @Override - public void updateItem(final ClosedTradableListItem item, boolean empty) { + public void updateItem(final Tradable item, boolean empty) { super.updateItem(item, empty); setGraphic(new AutoTooltipLabel(model.getTxFee(item))); } @@ -713,11 +734,11 @@ public class ClosedTradesView extends ActivatableViewAndModel() { @Override - public TableCell call( - TableColumn column) { + public TableCell call( + TableColumn column) { return new TableCell<>() { @Override - public void updateItem(final ClosedTradableListItem item, boolean empty) { + public void updateItem(final Tradable item, boolean empty) { super.updateItem(item, empty); setGraphic(new AutoTooltipLabel(model.getTradeFee(item, true))); } @@ -731,11 +752,11 @@ public class ClosedTradesView extends ActivatableViewAndModel() { @Override - public TableCell call( - TableColumn column) { + public TableCell call( + TableColumn column) { return new TableCell<>() { @Override - public void updateItem(final ClosedTradableListItem item, boolean empty) { + public void updateItem(final Tradable item, boolean empty) { super.updateItem(item, empty); setGraphic(new AutoTooltipLabel(model.getBuyerSecurityDeposit(item))); } @@ -749,11 +770,11 @@ public class ClosedTradesView extends ActivatableViewAndModel() { @Override - public TableCell call( - TableColumn column) { + public TableCell call( + TableColumn column) { return new TableCell<>() { @Override - public void updateItem(final ClosedTradableListItem item, boolean empty) { + public void updateItem(final Tradable item, boolean empty) { super.updateItem(item, empty); setGraphic(new AutoTooltipLabel(model.getSellerSecurityDeposit(item))); } @@ -762,4 +783,32 @@ public class ClosedTradesView extends ActivatableViewAndModel implements ViewModel { - private final ClosedTradeUtil closedTradeUtil; - final AccountAgeWitnessService accountAgeWitnessService; + private final ClosedTradableFormatter closedTradableFormatter; @Inject - public ClosedTradesViewModel(ClosedTradesDataModel dataModel, - ClosedTradeUtil closedTradeUtil, - AccountAgeWitnessService accountAgeWitnessService) { + public ClosedTradesViewModel(ClosedTradesDataModel dataModel, ClosedTradableFormatter closedTradableFormatter) { super(dataModel); - this.closedTradeUtil = closedTradeUtil; - this.accountAgeWitnessService = accountAgeWitnessService; + + this.closedTradableFormatter = closedTradableFormatter; } - public ObservableList getList() { - return dataModel.getList(); + String getTradeId(Tradable item) { + return item.getShortId(); } - String getTradeId(ClosedTradableListItem item) { - return item.getTradable().getShortId(); + String getAmount(Tradable item) { + return item != null ? closedTradableFormatter.getAmountAsString(item) : ""; } - String getAmount(ClosedTradableListItem item) { - return item != null ? closedTradeUtil.getAmountAsString(item.getTradable()) : ""; + String getPrice(Tradable item) { + return item != null ? closedTradableFormatter.getPriceAsString(item) : ""; } - String getPrice(ClosedTradableListItem item) { - return item != null ? closedTradeUtil.getPriceAsString(item.getTradable()) : ""; + String getPriceDeviation(Tradable item) { + return item != null ? closedTradableFormatter.getPriceDeviationAsString(item) : ""; } - String getPriceDeviation(ClosedTradableListItem item) { - return item != null ? closedTradeUtil.getPriceDeviationAsString(item.getTradable()) : ""; + String getVolume(Tradable item, boolean appendCode) { + return item != null ? closedTradableFormatter.getVolumeAsString(item, appendCode) : ""; } - String getVolume(ClosedTradableListItem item, boolean appendCode) { - return item != null ? closedTradeUtil.getVolumeAsString(item.getTradable(), appendCode) : ""; + String getVolumeCurrency(Tradable item) { + return item != null ? closedTradableFormatter.getVolumeCurrencyAsString(item) : ""; } - String getVolumeCurrency(ClosedTradableListItem item) { - return item != null ? closedTradeUtil.getVolumeCurrencyAsString(item.getTradable()) : ""; + String getTxFee(Tradable item) { + return item != null ? closedTradableFormatter.getTxFeeAsString(item) : ""; } - String getTxFee(ClosedTradableListItem item) { - return item != null ? closedTradeUtil.getTxFeeAsString(item.getTradable()) : ""; + String getTradeFee(Tradable item, boolean appendCode) { + return item != null ? closedTradableFormatter.getTradeFeeAsString(item, appendCode) : ""; } - boolean isCurrencyForTradeFeeBtc(ClosedTradableListItem item) { - return item != null ? closedTradeUtil.isCurrencyForTradeFeeBtc(item.getTradable()) : false; + String getBuyerSecurityDeposit(Tradable item) { + return item != null ? closedTradableFormatter.getBuyerSecurityDepositAsString(item) : ""; } - String getTradeFee(ClosedTradableListItem item, boolean appendCode) { - return item != null ? closedTradeUtil.getTradeFeeAsString(item.getTradable(), appendCode) : ""; + String getSellerSecurityDeposit(Tradable item) { + return item != null ? closedTradableFormatter.getSellerSecurityDepositAsString(item) : ""; } - String getBuyerSecurityDeposit(ClosedTradableListItem item) { - return item != null ? closedTradeUtil.getBuyerSecurityDepositAsString(item.getTradable()) : ""; + String getDirectionLabel(Tradable item) { + if ((item != null)) { + OfferDirection direction = dataModel.getDirection(item.getOffer()); + String currencyCode = item.getOffer().getCurrencyCode(); + return DisplayUtils.getDirectionWithCode(direction, currencyCode); + } else { + return ""; + } } - String getSellerSecurityDeposit(ClosedTradableListItem item) { - return item != null ? closedTradeUtil.getSellerSecurityDepositAsString(item.getTradable()) : ""; + String getDate(Tradable item) { + return DisplayUtils.formatDateTime(item.getDate()); } - String getDirectionLabel(ClosedTradableListItem item) { - return (item != null) ? DisplayUtils.getDirectionWithCode(dataModel.getDirection(item.getTradable().getOffer()), item.getTradable().getOffer().getCurrencyCode()) : ""; + String getMarketLabel(Tradable item) { + return item != null ? CurrencyUtil.getCurrencyPair(item.getOffer().getCurrencyCode()) : ""; } - String getDate(ClosedTradableListItem item) { - return DisplayUtils.formatDateTime(item.getTradable().getDate()); + String getState(Tradable item) { + return item != null ? closedTradableFormatter.getStateAsString(item) : ""; } - String getMarketLabel(ClosedTradableListItem item) { - return item != null ? closedTradeUtil.getMarketLabel(item.getTradable()) : ""; - } - String getState(ClosedTradableListItem item) { - return item != null ? closedTradeUtil.getStateAsString(item.getTradable()) : ""; - } - - int getNumPastTrades(Tradable tradable) { - return closedTradeUtil.getNumPastTrades(tradable); - } + /////////////////////////////////////////////////////////////////////////////////////////// + // Used in ClosedTradesSummaryWindow + /////////////////////////////////////////////////////////////////////////////////////////// public Coin getTotalTradeAmount() { return dataModel.getTotalAmount(); @@ -121,22 +116,22 @@ public class ClosedTradesViewModel extends ActivatableWithDataModel closedTradeUtil.getTotalAmountWithVolumeAsString(totalTradeAmount, volume)) + .map(volume -> closedTradableFormatter.getTotalAmountWithVolumeAsString(totalTradeAmount, volume)) .orElse(""); } public Map getTotalVolumeByCurrency() { - return closedTradeUtil.getTotalVolumeByCurrencyAsString(dataModel.tradableList.get()); + return closedTradableFormatter.getTotalVolumeByCurrencyAsString(dataModel.getList()); } public String getTotalTxFee(Coin totalTradeAmount) { Coin totalTxFee = dataModel.getTotalTxFee(); - return closedTradeUtil.getTotalTxFeeAsString(totalTradeAmount, totalTxFee); + return closedTradableFormatter.getTotalTxFeeAsString(totalTradeAmount, totalTxFee); } public String getTotalTradeFeeInBtc(Coin totalTradeAmount) { Coin totalTradeFee = dataModel.getTotalTradeFee(true); - return closedTradeUtil.getTotalTradeFeeInBtcAsString(totalTradeAmount, totalTradeFee); + return closedTradableFormatter.getTotalTradeFeeInBtcAsString(totalTradeAmount, totalTradeFee); } public String getTotalTradeFeeInBsq(Coin totalTradeAmount) { @@ -145,7 +140,7 @@ public class ClosedTradesViewModel extends ActivatableWithDataModel { Coin totalTradeFee = dataModel.getTotalTradeFee(false); Volume bsqVolumeInUsd = dataModel.getBsqVolumeInUsdWithAveragePrice(totalTradeFee); // with 4 decimal - return closedTradeUtil.getTotalTradeFeeInBsqAsString(totalTradeFee, + return closedTradableFormatter.getTotalTradeFeeInBsqAsString(totalTradeFee, tradeAmountVolume, bsqVolumeInUsd); }) diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/duplicateoffer/DuplicateOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/duplicateoffer/DuplicateOfferDataModel.java index b713ed112e..5818f2cc23 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/duplicateoffer/DuplicateOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/duplicateoffer/DuplicateOfferDataModel.java @@ -19,16 +19,16 @@ package bisq.desktop.main.portfolio.duplicateoffer; import bisq.desktop.Navigation; -import bisq.desktop.main.offer.MutableOfferDataModel; +import bisq.desktop.main.offer.bisq_v1.MutableOfferDataModel; import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.btc.wallet.BsqWalletService; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.Restrictions; -import bisq.core.offer.CreateOfferService; import bisq.core.offer.Offer; import bisq.core.offer.OfferUtil; import bisq.core.offer.OpenOfferManager; +import bisq.core.offer.bisq_v1.CreateOfferService; import bisq.core.provider.fee.FeeService; import bisq.core.provider.price.PriceFeedService; import bisq.core.trade.statistics.TradeStatisticsManager; diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/duplicateoffer/DuplicateOfferView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/duplicateoffer/DuplicateOfferView.java index ca1b1a4232..db6eb38ba1 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/duplicateoffer/DuplicateOfferView.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/duplicateoffer/DuplicateOfferView.java @@ -19,11 +19,11 @@ package bisq.desktop.main.portfolio.duplicateoffer; import bisq.desktop.Navigation; import bisq.desktop.common.view.FxmlView; -import bisq.desktop.main.offer.MutableOfferView; +import bisq.desktop.main.offer.bisq_v1.MutableOfferView; import bisq.desktop.main.overlays.windows.OfferDetailsWindow; import bisq.core.locale.CurrencyUtil; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.bisq_v1.OfferPayload; import bisq.core.user.Preferences; import bisq.core.util.FormattingUtils; import bisq.core.util.coin.BsqFormatter; @@ -62,7 +62,9 @@ public class DuplicateOfferView extends MutableOfferView { diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferView.java index 2e1f80ca00..3fe8ae9238 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferView.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferView.java @@ -21,7 +21,7 @@ import bisq.desktop.Navigation; import bisq.desktop.common.view.FxmlView; import bisq.desktop.components.AutoTooltipButton; import bisq.desktop.components.BusyAnimation; -import bisq.desktop.main.offer.MutableOfferView; +import bisq.desktop.main.offer.bisq_v1.MutableOfferView; import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.main.overlays.windows.OfferDetailsWindow; @@ -145,7 +145,8 @@ public class EditOfferView extends MutableOfferView { model.applyOpenOffer(openOffer); initWithData(openOffer.getOffer().getDirection(), - CurrencyUtil.getTradeCurrency(openOffer.getOffer().getCurrencyCode()).get()); + CurrencyUtil.getTradeCurrency(openOffer.getOffer().getCurrencyCode()).get(), + null); model.onStartEditOffer(errorMessage -> { log.error(errorMessage); diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferViewModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferViewModel.java index d4aab63268..8e71956038 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferViewModel.java @@ -18,12 +18,9 @@ package bisq.desktop.main.portfolio.editoffer; import bisq.desktop.Navigation; -import bisq.desktop.main.PriceUtil; -import bisq.desktop.main.offer.MutableOfferViewModel; -import bisq.desktop.util.validation.AltcoinValidator; +import bisq.desktop.main.offer.bisq_v1.MutableOfferViewModel; import bisq.desktop.util.validation.BsqValidator; import bisq.desktop.util.validation.BtcValidator; -import bisq.desktop.util.validation.FiatPriceValidator; import bisq.desktop.util.validation.FiatVolumeValidator; import bisq.desktop.util.validation.SecurityDepositValidator; @@ -33,8 +30,11 @@ import bisq.core.offer.OpenOffer; import bisq.core.provider.price.PriceFeedService; import bisq.core.user.Preferences; import bisq.core.util.FormattingUtils; +import bisq.core.util.PriceUtil; import bisq.core.util.coin.BsqFormatter; import bisq.core.util.coin.CoinFormatter; +import bisq.core.util.validation.AltcoinValidator; +import bisq.core.util.validation.FiatPriceValidator; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ResultHandler; diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/failedtrades/FailedTradesDataModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/failedtrades/FailedTradesDataModel.java index defd761362..ea658e1f93 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/failedtrades/FailedTradesDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/failedtrades/FailedTradesDataModel.java @@ -20,10 +20,10 @@ package bisq.desktop.main.portfolio.failedtrades; import bisq.desktop.common.model.ActivatableDataModel; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload; -import bisq.core.trade.Trade; +import bisq.core.offer.OfferDirection; import bisq.core.trade.TradeManager; -import bisq.core.trade.failed.FailedTradesManager; +import bisq.core.trade.bisq_v1.FailedTradesManager; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.network.p2p.NodeAddress; import bisq.network.p2p.P2PService; @@ -77,7 +77,7 @@ class FailedTradesDataModel extends ActivatableDataModel { return list; } - public OfferPayload.Direction getDirection(Offer offer) { + public OfferDirection getDirection(Offer offer) { return failedTradesManager.wasMyOffer(offer) ? offer.getDirection() : offer.getMirroredDirection(); } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/failedtrades/FailedTradesListItem.java b/desktop/src/main/java/bisq/desktop/main/portfolio/failedtrades/FailedTradesListItem.java index f16365e1d2..52973a2316 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/failedtrades/FailedTradesListItem.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/failedtrades/FailedTradesListItem.java @@ -17,7 +17,7 @@ package bisq.desktop.main.portfolio.failedtrades; -import bisq.core.trade.Trade; +import bisq.core.trade.model.bisq_v1.Trade; import lombok.Getter; diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/failedtrades/FailedTradesView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/failedtrades/FailedTradesView.java index bb51a1af07..bfc10599e4 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/failedtrades/FailedTradesView.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/failedtrades/FailedTradesView.java @@ -30,8 +30,8 @@ import bisq.desktop.util.GUIUtil; import bisq.core.locale.Res; import bisq.core.offer.Offer; -import bisq.core.trade.Contract; -import bisq.core.trade.Trade; +import bisq.core.trade.model.bisq_v1.Contract; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.common.config.Config; import bisq.common.util.Utilities; @@ -144,9 +144,9 @@ public class FailedTradesView extends ActivatableViewAndModel o.getTrade().getId())); dateColumn.setComparator(Comparator.comparing(o -> o.getTrade().getDate())); - priceColumn.setComparator(Comparator.comparing(o -> o.getTrade().getTradePrice())); - volumeColumn.setComparator(Comparator.comparing(o -> o.getTrade().getTradeVolume(), Comparator.nullsFirst(Comparator.naturalOrder()))); - amountColumn.setComparator(Comparator.comparing(o -> o.getTrade().getTradeAmount(), Comparator.nullsFirst(Comparator.naturalOrder()))); + priceColumn.setComparator(Comparator.comparing(o -> o.getTrade().getPrice())); + volumeColumn.setComparator(Comparator.comparing(o -> o.getTrade().getVolume(), Comparator.nullsFirst(Comparator.naturalOrder()))); + amountColumn.setComparator(Comparator.comparing(o -> o.getTrade().getAmount(), Comparator.nullsFirst(Comparator.naturalOrder()))); stateColumn.setComparator(Comparator.comparing(model::getState)); marketColumn.setComparator(Comparator.comparing(model::getMarketLabel)); @@ -273,7 +273,8 @@ public class FailedTradesView extends ActivatableViewAndModel list = FXCollections.observableArrayList(); @@ -48,8 +50,11 @@ class OpenOffersDataModel extends ActivatableDataModel { private final ChangeListener currenciesUpdateFlagPropertyListener; @Inject - public OpenOffersDataModel(OpenOfferManager openOfferManager, PriceFeedService priceFeedService) { + public OpenOffersDataModel(OpenOfferManager openOfferManager, + OpenBsqSwapOfferService openBsqSwapOfferService, + PriceFeedService priceFeedService) { this.openOfferManager = openOfferManager; + this.openBsqSwapOfferService = openBsqSwapOfferService; this.priceFeedService = priceFeedService; tradesListChangeListener = change -> applyList(); @@ -69,11 +74,19 @@ class OpenOffersDataModel extends ActivatableDataModel { priceFeedService.updateCounterProperty().removeListener(currenciesUpdateFlagPropertyListener); } - void onActivateOpenOffer(OpenOffer openOffer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { - openOfferManager.activateOpenOffer(openOffer, resultHandler, errorMessageHandler); + void onActivateOpenOffer(OpenOffer openOffer, + ResultHandler resultHandler, + ErrorMessageHandler errorMessageHandler) { + if (openOffer.getOffer().isBsqSwapOffer()) { + openBsqSwapOfferService.activateOpenOffer(openOffer, resultHandler, errorMessageHandler); + } else { + openOfferManager.activateOpenOffer(openOffer, resultHandler, errorMessageHandler); + } } - void onDeactivateOpenOffer(OpenOffer openOffer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { + void onDeactivateOpenOffer(OpenOffer openOffer, + ResultHandler resultHandler, + ErrorMessageHandler errorMessageHandler) { openOfferManager.deactivateOpenOffer(openOffer, resultHandler, errorMessageHandler); } @@ -86,7 +99,7 @@ class OpenOffersDataModel extends ActivatableDataModel { return list; } - public OfferPayload.Direction getDirection(Offer offer) { + public OfferDirection getDirection(Offer offer) { return openOfferManager.isMyOffer(offer) ? offer.getDirection() : offer.getMirroredDirection(); } @@ -100,6 +113,7 @@ class OpenOffersDataModel extends ActivatableDataModel { } boolean wasTriggered(OpenOffer openOffer) { - return TriggerPriceService.wasTriggered(priceFeedService.getMarketPrice(openOffer.getOffer().getCurrencyCode()), openOffer); + return TriggerPriceService.wasTriggered(priceFeedService.getMarketPrice(openOffer.getOffer().getCurrencyCode()), + openOffer); } } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersView.java index 4c08adef0b..692d0339a1 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersView.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersView.java @@ -30,6 +30,7 @@ import bisq.desktop.main.MainView; import bisq.desktop.main.funds.FundsView; import bisq.desktop.main.funds.withdrawal.WithdrawalView; import bisq.desktop.main.overlays.popups.Popup; +import bisq.desktop.main.overlays.windows.BsqSwapOfferDetailsWindow; import bisq.desktop.main.overlays.windows.OfferDetailsWindow; import bisq.desktop.main.portfolio.PortfolioView; import bisq.desktop.main.portfolio.duplicateoffer.DuplicateOfferView; @@ -37,7 +38,7 @@ import bisq.desktop.util.GUIUtil; import bisq.core.locale.Res; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferPayloadBase; import bisq.core.offer.OpenOffer; import bisq.core.user.DontShowAgainLookup; @@ -68,6 +69,7 @@ import javafx.scene.layout.Pane; import javafx.scene.layout.Priority; import javafx.scene.layout.Region; import javafx.scene.layout.VBox; +import javafx.scene.text.Text; import javafx.geometry.Insets; @@ -86,6 +88,7 @@ import java.util.Comparator; import org.jetbrains.annotations.NotNull; import static bisq.desktop.util.FormBuilder.getRegularIconButton; +import static bisq.desktop.util.FormBuilder.getRegularIconForLabel; @FxmlView public class OpenOffersView extends ActivatableViewAndModel { @@ -115,6 +118,7 @@ public class OpenOffersView extends ActivatableViewAndModel sortedList; private FilteredList filteredList; private ChangeListener filterTextFieldListener; @@ -122,10 +126,14 @@ public class OpenOffersView extends ActivatableViewAndModel widthListener; @Inject - public OpenOffersView(OpenOffersViewModel model, Navigation navigation, OfferDetailsWindow offerDetailsWindow) { + public OpenOffersView(OpenOffersViewModel model, + Navigation navigation, + OfferDetailsWindow offerDetailsWindow, + BsqSwapOfferDetailsWindow bsqSwapOfferDetailsWindow) { super(model); this.navigation = navigation; this.offerDetailsWindow = offerDetailsWindow; + this.bsqSwapOfferDetailsWindow = bsqSwapOfferDetailsWindow; } @Override @@ -186,8 +194,9 @@ public class OpenOffersView extends ActivatableViewAndModel { try { - OfferPayload offerPayload = row.getItem().getOffer().getOfferPayload(); - navigation.navigateToWithData(offerPayload, MainView.class, PortfolioView.class, DuplicateOfferView.class); + OfferPayloadBase offerPayloadBase = row.getItem().getOffer().getOfferPayloadBase(); + navigation.navigateToWithData(offerPayloadBase, MainView.class, PortfolioView.class, + DuplicateOfferView.class); } catch (NullPointerException e) { log.warn("Unable to get offerPayload - {}", e.toString()); } @@ -347,7 +356,8 @@ public class OpenOffersView extends ActivatableViewAndModel offerDetailsWindow.show(item.getOffer())); + field.setOnAction(event -> { + if (item.getOffer().isBsqSwapOffer()) { + bsqSwapOfferDetailsWindow.show(item.getOffer()); + } else { + offerDetailsWindow.show(item.getOffer()); + } + }); + field.setTooltip(new Tooltip(Res.get("tooltip.openPopupForDetails"))); setGraphic(field); } else { @@ -472,7 +488,7 @@ public class OpenOffersView extends ActivatableViewAndModel onEditOpenOffer(item.getOpenOffer())); + setGraphic(button); + } } - button.setOnAction(event -> onEditOpenOffer(item.getOpenOffer())); } else { setGraphic(null); if (button != null) { diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersViewModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersViewModel.java index 9a2e12b7d1..4be497e345 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersViewModel.java @@ -19,7 +19,6 @@ package bisq.desktop.main.portfolio.openoffer; import bisq.desktop.common.model.ActivatableWithDataModel; import bisq.desktop.common.model.ViewModel; -import bisq.desktop.main.PriceUtil; import bisq.desktop.util.DisplayUtils; import bisq.desktop.util.GUIUtil; @@ -29,6 +28,7 @@ import bisq.core.monetary.Price; import bisq.core.offer.Offer; import bisq.core.offer.OpenOffer; import bisq.core.util.FormattingUtils; +import bisq.core.util.PriceUtil; import bisq.core.util.VolumeUtil; import bisq.core.util.coin.BsqFormatter; import bisq.core.util.coin.CoinFormatter; @@ -160,8 +160,21 @@ class OpenOffersViewModel extends ActivatableWithDataModel return DisplayUtils.formatDateTime(item.getOffer().getDate()); } + boolean isNotPublished(OpenOfferListItem item) { + return isDeactivated(item) || isBsqSwapOfferHasMissingFunds(item); + } + boolean isDeactivated(OpenOfferListItem item) { - return item != null && item.getOpenOffer() != null && item.getOpenOffer().isDeactivated(); + return item != null && + item.getOpenOffer() != null && + item.getOpenOffer().isDeactivated(); + } + + boolean isBsqSwapOfferHasMissingFunds(OpenOfferListItem item) { + return item != null && + item.getOpenOffer() != null && + item.getOpenOffer().getOffer().isBsqSwapOffer() && + item.getOpenOffer().isBsqSwapOfferHasMissingFunds(); } boolean isBootstrappedOrShowPopup() { diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java index b43a4ca52d..cf5d4f68a2 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java @@ -34,7 +34,7 @@ import bisq.core.btc.wallet.BtcWalletService; import bisq.core.dao.DaoFacade; import bisq.core.locale.Res; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferDirection; import bisq.core.offer.OfferUtil; import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.support.SupportType; @@ -47,14 +47,14 @@ import bisq.core.support.dispute.mediation.MediationManager; import bisq.core.support.dispute.refund.RefundManager; import bisq.core.support.messages.ChatMessage; import bisq.core.support.traderchat.TraderChatManager; -import bisq.core.trade.BuyerTrade; -import bisq.core.trade.SellerTrade; -import bisq.core.trade.Trade; -import bisq.core.trade.TradeDataValidation; import bisq.core.trade.TradeManager; -import bisq.core.trade.protocol.BuyerProtocol; -import bisq.core.trade.protocol.DisputeProtocol; -import bisq.core.trade.protocol.SellerProtocol; +import bisq.core.trade.bisq_v1.TradeDataValidation; +import bisq.core.trade.model.bisq_v1.BuyerTrade; +import bisq.core.trade.model.bisq_v1.SellerTrade; +import bisq.core.trade.model.bisq_v1.Trade; +import bisq.core.trade.protocol.bisq_v1.BuyerProtocol; +import bisq.core.trade.protocol.bisq_v1.DisputeProtocol; +import bisq.core.trade.protocol.bisq_v1.SellerProtocol; import bisq.core.user.Preferences; import bisq.core.util.FormattingUtils; @@ -194,7 +194,7 @@ public class PendingTradesDataModel extends ActivatableDataModel { public void onPaymentStarted(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { Trade trade = getTrade(); checkNotNull(trade, "trade must not be null"); - checkArgument(trade instanceof BuyerTrade, "Check failed: trade instanceof BuyerTrade"); + checkArgument(trade instanceof BuyerTrade, "Check failed: trade instanceof BuyerTrade. Was: " + trade.getClass().getSimpleName()); ((BuyerProtocol) tradeManager.getTradeProtocol(trade)).onPaymentStarted(resultHandler, errorMessageHandler); } @@ -317,9 +317,9 @@ public class PendingTradesDataModel extends ActivatableDataModel { } } else { if (trade.isCurrencyForTakerFeeBtc()) - return trade.getTxFee().multiply(3); + return trade.getTradeTxFee().multiply(3); else - return trade.getTxFee().multiply(3).subtract(trade.getTakerFee()); // BSQ will be used as part of the miner fee + return trade.getTradeTxFee().multiply(3).subtract(trade.getTakerFee()); // BSQ will be used as part of the miner fee } } else { log.error("Trade is null at getTotalFees"); @@ -380,7 +380,9 @@ public class PendingTradesDataModel extends ActivatableDataModel { private void onListChanged() { list.clear(); - list.addAll(tradeManager.getObservableList().stream().map(PendingTradesListItem::new).collect(Collectors.toList())); + list.addAll(tradeManager.getObservableList().stream() + .map(PendingTradesListItem::new) + .collect(Collectors.toList())); // we sort by date, earliest first list.sort((o1, o2) -> o2.getTrade().getDate().compareTo(o1.getTrade().getDate())); @@ -534,7 +536,7 @@ public class PendingTradesDataModel extends ActivatableDataModel { Dispute dispute = new Dispute(new Date().getTime(), trade.getId(), pubKeyRing.hashCode(), // traderId - (offer.getDirection() == OfferPayload.Direction.BUY) == isMaker, + (offer.getDirection() == OfferDirection.BUY) == isMaker, isMaker, pubKeyRing, trade.getDate().getTime(), @@ -595,7 +597,7 @@ public class PendingTradesDataModel extends ActivatableDataModel { Dispute dispute = new Dispute(new Date().getTime(), trade.getId(), pubKeyRing.hashCode(), // traderId - (offer.getDirection() == OfferPayload.Direction.BUY) == isMaker, + (offer.getDirection() == OfferDirection.BUY) == isMaker, isMaker, pubKeyRing, trade.getDate().getTime(), diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesListItem.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesListItem.java index fbc3035b1b..e63383786a 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesListItem.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesListItem.java @@ -19,7 +19,7 @@ package bisq.desktop.main.portfolio.pendingtrades; import bisq.core.monetary.Price; import bisq.core.monetary.Volume; -import bisq.core.trade.Trade; +import bisq.core.trade.model.bisq_v1.Trade; import org.bitcoinj.core.Coin; @@ -41,15 +41,15 @@ public class PendingTradesListItem { } public ReadOnlyObjectProperty tradeAmountProperty() { - return trade.tradeAmountProperty(); + return trade.amountProperty(); } public ReadOnlyObjectProperty tradeVolumeProperty() { - return trade.tradeVolumeProperty(); + return trade.volumeProperty(); } public Price getPrice() { - return trade.getTradePrice(); + return trade.getPrice(); } } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesView.java index a0ef8130d6..5ee9a71308 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesView.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesView.java @@ -35,13 +35,13 @@ import bisq.desktop.util.FormBuilder; import bisq.core.alert.PrivateNotificationManager; import bisq.core.locale.Res; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.bisq_v1.OfferPayload; import bisq.core.support.dispute.mediation.MediationResultState; import bisq.core.support.messages.ChatMessage; import bisq.core.support.traderchat.TradeChatSession; import bisq.core.support.traderchat.TraderChatManager; -import bisq.core.trade.Contract; -import bisq.core.trade.Trade; +import bisq.core.trade.model.bisq_v1.Contract; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.user.Preferences; import bisq.core.util.FormattingUtils; import bisq.core.util.VolumeUtil; @@ -210,8 +210,8 @@ public class PendingTradesView extends ActivatableViewAndModel o.getTrade().getId())); dateColumn.setComparator(Comparator.comparing(o -> o.getTrade().getDate())); - volumeColumn.setComparator(Comparator.comparing(o -> o.getTrade().getTradeVolume(), Comparator.nullsFirst(Comparator.naturalOrder()))); - amountColumn.setComparator(Comparator.comparing(o -> o.getTrade().getTradeAmount(), Comparator.nullsFirst(Comparator.naturalOrder()))); + volumeColumn.setComparator(Comparator.comparing(o -> o.getTrade().getVolume(), Comparator.nullsFirst(Comparator.naturalOrder()))); + amountColumn.setComparator(Comparator.comparing(o -> o.getTrade().getAmount(), Comparator.nullsFirst(Comparator.naturalOrder()))); priceColumn.setComparator(Comparator.comparing(item -> FormattingUtils.formatPrice(item.getPrice()))); paymentMethodColumn.setComparator(Comparator.comparing( item -> item.getTrade().getOffer() != null ? @@ -235,7 +235,7 @@ public class PendingTradesView extends ActivatableViewAndModel { try { - OfferPayload offerPayload = row.getItem().getTrade().getOffer().getOfferPayload(); + OfferPayload offerPayload = row.getItem().getTrade().getOffer().getOfferPayload().orElseThrow(); if (offerPayload.getPubKeyRing().equals(keyRing.getPubKeyRing())) { navigation.navigateToWithData(offerPayload, MainView.class, PortfolioView.class, DuplicateOfferView.class); } else { @@ -368,7 +368,7 @@ public class PendingTradesView extends ActivatableViewAndModel= 3 && !trade.hasFailed()) { String key = "tradeUnconfirmedTooLong_" + trade.getShortId(); if (DontShowAgainLookup.showAgain(key)) { diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java index 809b9a5c13..4665dd7e57 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java @@ -22,7 +22,7 @@ import bisq.desktop.main.portfolio.pendingtrades.PendingTradesViewModel; import bisq.desktop.main.portfolio.pendingtrades.steps.TradeStepView; import bisq.core.locale.Res; -import bisq.core.trade.TradeDataValidation; +import bisq.core.trade.bisq_v1.TradeDataValidation; public class BuyerStep1View extends TradeStepView { diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java index 4d58c9e052..a4bbeb8377 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java @@ -97,8 +97,8 @@ import bisq.core.payment.payload.PaymentMethod; import bisq.core.payment.payload.SwiftAccountPayload; import bisq.core.payment.payload.USPostalMoneyOrderAccountPayload; import bisq.core.payment.payload.WesternUnionAccountPayload; -import bisq.core.trade.Trade; -import bisq.core.trade.TradeDataValidation; +import bisq.core.trade.bisq_v1.TradeDataValidation; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.user.DontShowAgainLookup; import bisq.core.util.VolumeUtil; @@ -432,7 +432,7 @@ public class BuyerStep2View extends TradeStepView { hBox.getChildren().add(1, fillBsqButton); fillBsqButton.setOnAction(e -> { AssetsAccountPayload assetsAccountPayload = (AssetsAccountPayload) paymentAccountPayload; - Tuple2 data = new Tuple2<>(trade.getTradeVolume(), assetsAccountPayload.getAddress()); + Tuple2 data = new Tuple2<>(trade.getVolume(), assetsAccountPayload.getAddress()); model.getNavigation().navigateToWithData(data, MainView.class, DaoView.class, BsqWalletView.class, BsqSendView.class); }); @@ -625,7 +625,7 @@ public class BuyerStep2View extends TradeStepView { String refTextWarn = Res.get("portfolio.pending.step2_buyer.refTextWarn"); String fees = Res.get("portfolio.pending.step2_buyer.fees"); String id = trade.getShortId(); - String amount = VolumeUtil.formatVolumeWithCode(trade.getTradeVolume()); + String amount = VolumeUtil.formatVolumeWithCode(trade.getVolume()); if (paymentAccountPayload instanceof AssetsAccountPayload) { message += Res.get("portfolio.pending.step2_buyer.altcoin", getCurrencyName(trade), diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep1View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep1View.java index eb7129344e..9696afdf30 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep1View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep1View.java @@ -22,7 +22,7 @@ import bisq.desktop.main.portfolio.pendingtrades.PendingTradesViewModel; import bisq.desktop.main.portfolio.pendingtrades.steps.TradeStepView; import bisq.core.locale.Res; -import bisq.core.trade.TradeDataValidation; +import bisq.core.trade.bisq_v1.TradeDataValidation; public class SellerStep1View extends TradeStepView { diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index 66fb0a16a0..cb6715a573 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -42,8 +42,8 @@ import bisq.core.payment.payload.SepaAccountPayload; import bisq.core.payment.payload.SepaInstantAccountPayload; import bisq.core.payment.payload.USPostalMoneyOrderAccountPayload; import bisq.core.payment.payload.WesternUnionAccountPayload; -import bisq.core.trade.Contract; -import bisq.core.trade.Trade; +import bisq.core.trade.model.bisq_v1.Contract; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.trade.txproof.AssetTxProofResult; import bisq.core.user.DontShowAgainLookup; import bisq.core.util.VolumeUtil; @@ -402,7 +402,7 @@ public class SellerStep3View extends TradeStepView { PaymentAccountPayload paymentAccountPayload = model.dataModel.getSellersPaymentAccountPayload(); String key = "confirmPayment" + trade.getId(); String message = ""; - String tradeVolumeWithCode = VolumeUtil.formatVolumeWithCode(trade.getTradeVolume()); + String tradeVolumeWithCode = VolumeUtil.formatVolumeWithCode(trade.getVolume()); String currencyName = getCurrencyName(trade); String part1 = Res.get("portfolio.pending.step3_seller.part", currencyName); if (paymentAccountPayload instanceof AssetsAccountPayload) { diff --git a/desktop/src/main/java/bisq/desktop/main/presentation/DaoPresentation.java b/desktop/src/main/java/bisq/desktop/main/presentation/DaoPresentation.java index 27e2be2213..d36efe3faa 100644 --- a/desktop/src/main/java/bisq/desktop/main/presentation/DaoPresentation.java +++ b/desktop/src/main/java/bisq/desktop/main/presentation/DaoPresentation.java @@ -31,8 +31,7 @@ import lombok.Getter; @Singleton public class DaoPresentation implements DaoStateListener { - - public static final String DAO_NEWS = "daoNewsVersion1.0.0"; + public static final String DAO_NEWS = "daoNews_BsqSwaps"; private final Preferences preferences; private final Navigation navigation; diff --git a/desktop/src/main/java/bisq/desktop/main/shared/ChatView.java b/desktop/src/main/java/bisq/desktop/main/shared/ChatView.java index 2f00671c37..f369eeb301 100644 --- a/desktop/src/main/java/bisq/desktop/main/shared/ChatView.java +++ b/desktop/src/main/java/bisq/desktop/main/shared/ChatView.java @@ -92,16 +92,14 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import lombok.Getter; import lombok.Setter; +import lombok.extern.slf4j.Slf4j; import javax.annotation.Nullable; +@Slf4j public class ChatView extends AnchorPane { - public static final Logger log = LoggerFactory.getLogger(ChatView.class); // UI private TextArea inputTextArea; diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java index f55ac5a6a5..0447bc9eaf 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java @@ -52,9 +52,9 @@ import bisq.core.support.dispute.mediation.MediationManager; import bisq.core.support.dispute.mediation.mediator.MediatorManager; import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; import bisq.core.support.messages.ChatMessage; -import bisq.core.trade.Contract; -import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; +import bisq.core.trade.model.bisq_v1.Contract; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.user.Preferences; import bisq.core.util.FormattingUtils; import bisq.core.util.coin.CoinFormatter; diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java index e7ed03bf36..dc0f5baedc 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java @@ -34,8 +34,8 @@ import bisq.core.support.dispute.DisputeManager; import bisq.core.support.dispute.agent.MultipleHolderNameDetection; import bisq.core.support.dispute.mediation.mediator.MediatorManager; import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; -import bisq.core.trade.TradeDataValidation; import bisq.core.trade.TradeManager; +import bisq.core.trade.bisq_v1.TradeDataValidation; import bisq.core.user.DontShowAgainLookup; import bisq.core.user.Preferences; import bisq.core.util.coin.CoinFormatter; @@ -64,7 +64,7 @@ import java.util.List; import org.jetbrains.annotations.NotNull; -import static bisq.core.trade.TradeDataValidation.ValidationException; +import static bisq.core.trade.bisq_v1.TradeDataValidation.ValidationException; import static bisq.desktop.util.FormBuilder.getIconForLabel; public abstract class DisputeAgentView extends DisputeView implements MultipleHolderNameDetection.Listener { diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/client/mediation/MediationClientView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/client/mediation/MediationClientView.java index 0594e9895e..f967623e83 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/client/mediation/MediationClientView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/client/mediation/MediationClientView.java @@ -35,8 +35,8 @@ import bisq.core.support.dispute.mediation.MediationManager; import bisq.core.support.dispute.mediation.MediationSession; import bisq.core.support.dispute.mediation.mediator.MediatorManager; import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; -import bisq.core.trade.Contract; import bisq.core.trade.TradeManager; +import bisq.core.trade.model.bisq_v1.Contract; import bisq.core.user.Preferences; import bisq.core.util.FormattingUtils; import bisq.core.util.coin.CoinFormatter; diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/client/refund/RefundClientView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/client/refund/RefundClientView.java index 0efcfb38ae..79446c1261 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/client/refund/RefundClientView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/client/refund/RefundClientView.java @@ -33,8 +33,8 @@ import bisq.core.support.dispute.mediation.mediator.MediatorManager; import bisq.core.support.dispute.refund.RefundManager; import bisq.core.support.dispute.refund.RefundSession; import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; -import bisq.core.trade.Contract; import bisq.core.trade.TradeManager; +import bisq.core.trade.model.bisq_v1.Contract; import bisq.core.user.Preferences; import bisq.core.util.FormattingUtils; import bisq.core.util.coin.CoinFormatter; diff --git a/desktop/src/main/java/bisq/desktop/util/DisplayUtils.java b/desktop/src/main/java/bisq/desktop/util/DisplayUtils.java index b74bffc8f3..e3ebb02d56 100644 --- a/desktop/src/main/java/bisq/desktop/util/DisplayUtils.java +++ b/desktop/src/main/java/bisq/desktop/util/DisplayUtils.java @@ -6,7 +6,7 @@ import bisq.core.locale.Res; import bisq.core.monetary.Price; import bisq.core.monetary.Volume; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferDirection; import bisq.core.util.FormattingUtils; import bisq.core.util.ParsingUtils; import bisq.core.util.VolumeUtil; @@ -89,21 +89,21 @@ public class DisplayUtils { // Offer direction /////////////////////////////////////////////////////////////////////////////////////////// - public static String getDirectionWithCode(OfferPayload.Direction direction, String currencyCode) { + public static String getDirectionWithCode(OfferDirection direction, String currencyCode) { if (CurrencyUtil.isFiatCurrency(currencyCode)) - return (direction == OfferPayload.Direction.BUY) ? Res.get("shared.buyCurrency", Res.getBaseCurrencyCode()) : Res.get("shared.sellCurrency", Res.getBaseCurrencyCode()); + return (direction == OfferDirection.BUY) ? Res.get("shared.buyCurrency", Res.getBaseCurrencyCode()) : Res.get("shared.sellCurrency", Res.getBaseCurrencyCode()); else - return (direction == OfferPayload.Direction.SELL) ? Res.get("shared.buyCurrency", currencyCode) : Res.get("shared.sellCurrency", currencyCode); + return (direction == OfferDirection.SELL) ? Res.get("shared.buyCurrency", currencyCode) : Res.get("shared.sellCurrency", currencyCode); } - public static String getDirectionBothSides(OfferPayload.Direction direction, String currencyCode) { + public static String getDirectionBothSides(OfferDirection direction, String currencyCode) { if (CurrencyUtil.isFiatCurrency(currencyCode)) { currencyCode = Res.getBaseCurrencyCode(); - return direction == OfferPayload.Direction.BUY ? + return direction == OfferDirection.BUY ? Res.get("formatter.makerTaker", currencyCode, Res.get("shared.buyer"), currencyCode, Res.get("shared.seller")) : Res.get("formatter.makerTaker", currencyCode, Res.get("shared.seller"), currencyCode, Res.get("shared.buyer")); } else { - return direction == OfferPayload.Direction.SELL ? + return direction == OfferDirection.SELL ? Res.get("formatter.makerTaker", currencyCode, Res.get("shared.buyer"), currencyCode, Res.get("shared.seller")) : Res.get("formatter.makerTaker", currencyCode, Res.get("shared.seller"), currencyCode, Res.get("shared.buyer")); } @@ -135,28 +135,28 @@ public class DisplayUtils { } } - public static String getDirectionForTakeOffer(OfferPayload.Direction direction, String currencyCode) { + public static String getDirectionForTakeOffer(OfferDirection direction, String currencyCode) { String baseCurrencyCode = Res.getBaseCurrencyCode(); if (CurrencyUtil.isFiatCurrency(currencyCode)) { - return direction == OfferPayload.Direction.BUY ? + return direction == OfferDirection.BUY ? Res.get("formatter.youAre", Res.get("shared.selling"), baseCurrencyCode, Res.get("shared.buying"), currencyCode) : Res.get("formatter.youAre", Res.get("shared.buying"), baseCurrencyCode, Res.get("shared.selling"), currencyCode); } else { - return direction == OfferPayload.Direction.SELL ? + return direction == OfferDirection.SELL ? Res.get("formatter.youAre", Res.get("shared.selling"), currencyCode, Res.get("shared.buying"), baseCurrencyCode) : Res.get("formatter.youAre", Res.get("shared.buying"), currencyCode, Res.get("shared.selling"), baseCurrencyCode); } } - public static String getOfferDirectionForCreateOffer(OfferPayload.Direction direction, String currencyCode) { + public static String getOfferDirectionForCreateOffer(OfferDirection direction, String currencyCode) { String baseCurrencyCode = Res.getBaseCurrencyCode(); if (CurrencyUtil.isFiatCurrency(currencyCode)) { - return direction == OfferPayload.Direction.BUY ? + return direction == OfferDirection.BUY ? Res.get("formatter.youAreCreatingAnOffer.fiat", Res.get("shared.buy"), baseCurrencyCode) : Res.get("formatter.youAreCreatingAnOffer.fiat", Res.get("shared.sell"), baseCurrencyCode); } else { - return direction == OfferPayload.Direction.SELL ? + return direction == OfferDirection.SELL ? Res.get("formatter.youAreCreatingAnOffer.altcoin", Res.get("shared.buy"), currencyCode, Res.get("shared.selling"), baseCurrencyCode) : Res.get("formatter.youAreCreatingAnOffer.altcoin", Res.get("shared.sell"), currencyCode, Res.get("shared.buying"), baseCurrencyCode); } diff --git a/desktop/src/main/java/bisq/desktop/util/FormBuilder.java b/desktop/src/main/java/bisq/desktop/util/FormBuilder.java index 6961273312..fe064aedcd 100644 --- a/desktop/src/main/java/bisq/desktop/util/FormBuilder.java +++ b/desktop/src/main/java/bisq/desktop/util/FormBuilder.java @@ -1293,6 +1293,29 @@ public class FormBuilder { return new Tuple2<>(label, vBox); } + public static Tuple3 addTopLabelTextFieldWithHbox(GridPane gridPane, + int rowIndex, + String titleTextfield, + double top) { + HBox hBox = new HBox(); + hBox.setSpacing(10); + + TextField textField = new BisqTextField(); + + final VBox topLabelVBox = getTopLabelVBox(5); + final Label topLabel = getTopLabel(titleTextfield); + topLabelVBox.getChildren().addAll(topLabel, textField); + + hBox.getChildren().addAll(topLabelVBox); + + GridPane.setRowIndex(hBox, rowIndex); + GridPane.setMargin(hBox, new Insets(top, 0, 0, 0)); + gridPane.getChildren().add(hBox); + + return new Tuple3<>(topLabel, textField, hBox); + } + + /////////////////////////////////////////////////////////////////////////////////////////// // Label + ComboBox /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java index 7afd59cb53..3698a13ab6 100644 --- a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java +++ b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java @@ -583,7 +583,7 @@ public class GUIUtil { HBox box = new HBox(); box.setSpacing(20); Label paymentType = new AutoTooltipLabel( - method.isAsset() ? Res.get("shared.crypto") : Res.get("shared.fiat")); + method.isAltcoin() ? Res.get("shared.crypto") : Res.get("shared.fiat")); paymentType.getStyleClass().add("currency-label-small"); Label paymentMethod = new AutoTooltipLabel(Res.get(id)); @@ -835,7 +835,11 @@ public class GUIUtil { return true; } - public static boolean canCreateOrTakeOfferOrShowPopup(User user, Navigation navigation) { + public static boolean canCreateOrTakeOfferOrShowPopup(User user, Navigation navigation, TradeCurrency currency) { + if (currency.getCode().equals("BSQ")) { + return true; + } + if (!user.hasAcceptedRefundAgents()) { new Popup().warning(Res.get("popup.warning.noArbitratorsAvailable")).show(); return false; diff --git a/desktop/src/main/java/bisq/desktop/util/validation/BsqValidator.java b/desktop/src/main/java/bisq/desktop/util/validation/BsqValidator.java index eb6e5b5922..9ae59d577d 100644 --- a/desktop/src/main/java/bisq/desktop/util/validation/BsqValidator.java +++ b/desktop/src/main/java/bisq/desktop/util/validation/BsqValidator.java @@ -22,6 +22,7 @@ import bisq.core.locale.Res; import bisq.core.util.ParsingUtils; import bisq.core.util.coin.BsqFormatter; import bisq.core.util.coin.CoinUtil; +import bisq.core.util.validation.AltcoinValidator; import org.bitcoinj.core.Coin; diff --git a/desktop/src/main/java/bisq/desktop/util/validation/BtcValidator.java b/desktop/src/main/java/bisq/desktop/util/validation/BtcValidator.java index b204dbd0e6..2b46897acb 100644 --- a/desktop/src/main/java/bisq/desktop/util/validation/BtcValidator.java +++ b/desktop/src/main/java/bisq/desktop/util/validation/BtcValidator.java @@ -21,6 +21,7 @@ import bisq.core.btc.wallet.Restrictions; import bisq.core.locale.Res; import bisq.core.util.FormattingUtils; import bisq.core.util.coin.CoinFormatter; +import bisq.core.util.validation.NumberValidator; import org.bitcoinj.core.Coin; diff --git a/desktop/src/main/java/bisq/desktop/util/validation/FiatVolumeValidator.java b/desktop/src/main/java/bisq/desktop/util/validation/FiatVolumeValidator.java index e027962505..cf71ba08ef 100644 --- a/desktop/src/main/java/bisq/desktop/util/validation/FiatVolumeValidator.java +++ b/desktop/src/main/java/bisq/desktop/util/validation/FiatVolumeValidator.java @@ -17,6 +17,8 @@ package bisq.desktop.util.validation; +import bisq.core.util.validation.MonetaryValidator; + import javax.inject.Inject; public class FiatVolumeValidator extends MonetaryValidator { diff --git a/desktop/src/main/java/bisq/desktop/util/validation/PercentageNumberValidator.java b/desktop/src/main/java/bisq/desktop/util/validation/PercentageNumberValidator.java index 5b2263ecf9..7ce2ffac3e 100644 --- a/desktop/src/main/java/bisq/desktop/util/validation/PercentageNumberValidator.java +++ b/desktop/src/main/java/bisq/desktop/util/validation/PercentageNumberValidator.java @@ -18,6 +18,7 @@ package bisq.desktop.util.validation; import bisq.core.locale.Res; +import bisq.core.util.validation.NumberValidator; import lombok.Setter; diff --git a/desktop/src/main/java/bisq/desktop/util/validation/SecurityDepositValidator.java b/desktop/src/main/java/bisq/desktop/util/validation/SecurityDepositValidator.java index 32b9b35738..9a491d3433 100644 --- a/desktop/src/main/java/bisq/desktop/util/validation/SecurityDepositValidator.java +++ b/desktop/src/main/java/bisq/desktop/util/validation/SecurityDepositValidator.java @@ -22,6 +22,7 @@ import bisq.core.locale.Res; import bisq.core.payment.PaymentAccount; import bisq.core.util.FormattingUtils; import bisq.core.util.ParsingUtils; +import bisq.core.util.validation.NumberValidator; import javax.inject.Inject; diff --git a/desktop/src/test/java/bisq/desktop/main/funds/transactions/TransactionAwareTradableFactoryTest.java b/desktop/src/test/java/bisq/desktop/main/funds/transactions/TransactionAwareTradableFactoryTest.java index 86a62b28d2..38a86b5290 100644 --- a/desktop/src/test/java/bisq/desktop/main/funds/transactions/TransactionAwareTradableFactoryTest.java +++ b/desktop/src/test/java/bisq/desktop/main/funds/transactions/TransactionAwareTradableFactoryTest.java @@ -19,8 +19,8 @@ package bisq.desktop.main.funds.transactions; import bisq.core.offer.OpenOffer; import bisq.core.support.dispute.arbitration.ArbitrationManager; -import bisq.core.trade.Tradable; -import bisq.core.trade.Trade; +import bisq.core.trade.model.Tradable; +import bisq.core.trade.model.bisq_v1.Trade; import org.bitcoinj.core.Transaction; diff --git a/desktop/src/test/java/bisq/desktop/main/funds/transactions/TransactionAwareTradeTest.java b/desktop/src/test/java/bisq/desktop/main/funds/transactions/TransactionAwareTradeTest.java index 4552c9bc1c..c682ed3b34 100644 --- a/desktop/src/test/java/bisq/desktop/main/funds/transactions/TransactionAwareTradeTest.java +++ b/desktop/src/test/java/bisq/desktop/main/funds/transactions/TransactionAwareTradeTest.java @@ -21,7 +21,7 @@ import bisq.core.btc.wallet.BtcWalletService; import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.arbitration.ArbitrationManager; import bisq.core.support.dispute.refund.RefundManager; -import bisq.core.trade.Trade; +import bisq.core.trade.model.bisq_v1.Trade; import org.bitcoinj.core.Sha256Hash; import org.bitcoinj.core.Transaction; diff --git a/desktop/src/test/java/bisq/desktop/main/market/trades/TradesChartsViewModelTest.java b/desktop/src/test/java/bisq/desktop/main/market/trades/TradesChartsViewModelTest.java index 470c86b853..3fc291f273 100644 --- a/desktop/src/test/java/bisq/desktop/main/market/trades/TradesChartsViewModelTest.java +++ b/desktop/src/test/java/bisq/desktop/main/market/trades/TradesChartsViewModelTest.java @@ -22,7 +22,7 @@ import bisq.desktop.main.market.trades.charts.CandleData; import bisq.core.locale.FiatCurrency; import bisq.core.monetary.Price; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.bisq_v1.OfferPayload; import bisq.core.payment.payload.PaymentMethod; import bisq.core.provider.price.PriceFeedService; import bisq.core.trade.statistics.TradeStatistics3; diff --git a/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModelTest.java b/desktop/src/test/java/bisq/desktop/main/offer/bisq_v1/createoffer/CreateOfferDataModelTest.java similarity index 92% rename from desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModelTest.java rename to desktop/src/test/java/bisq/desktop/main/offer/bisq_v1/createoffer/CreateOfferDataModelTest.java index 691da7676b..17b8ea3c06 100644 --- a/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModelTest.java +++ b/desktop/src/test/java/bisq/desktop/main/offer/bisq_v1/createoffer/CreateOfferDataModelTest.java @@ -1,4 +1,4 @@ -package bisq.desktop.main.offer.createoffer; +package bisq.desktop.main.offer.bisq_v1.createoffer; import bisq.core.btc.model.AddressEntry; import bisq.core.btc.wallet.BtcWalletService; @@ -6,8 +6,9 @@ import bisq.core.locale.CryptoCurrency; import bisq.core.locale.FiatCurrency; import bisq.core.locale.GlobalSettings; import bisq.core.locale.Res; -import bisq.core.offer.CreateOfferService; +import bisq.core.offer.OfferDirection; import bisq.core.offer.OfferUtil; +import bisq.core.offer.bisq_v1.CreateOfferService; import bisq.core.payment.ClearXchangeAccount; import bisq.core.payment.PaymentAccount; import bisq.core.payment.RevolutAccount; @@ -22,12 +23,10 @@ import org.bitcoinj.core.Coin; import javafx.collections.FXCollections; import java.util.HashSet; -import java.util.UUID; import org.junit.Before; import org.junit.Test; -import static bisq.core.offer.OfferPayload.Direction; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; @@ -35,7 +34,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class CreateOfferDataModelTest { - private CreateOfferDataModel model; private User user; private Preferences preferences; @@ -60,7 +58,6 @@ public class CreateOfferDataModelTest { when(btcWalletService.getOrCreateAddressEntry(anyString(), any())).thenReturn(addressEntry); when(preferences.isUsePercentageBasedPrice()).thenReturn(true); when(preferences.getBuyerSecurityDepositAsPercent(null)).thenReturn(0.01); - when(createOfferService.getRandomOfferId()).thenReturn(UUID.randomUUID().toString()); when(tradeStats.getObservableTradeStatisticsSet()).thenReturn(FXCollections.observableSet()); model = new CreateOfferDataModel(createOfferService, @@ -97,7 +94,7 @@ public class CreateOfferDataModelTest { when(preferences.getSelectedPaymentAccountForCreateOffer()).thenReturn(revolutAccount); when(offerUtil.getMakerFee(any())).thenReturn(Coin.ZERO); - model.initWithData(Direction.BUY, new FiatCurrency("USD")); + model.initWithData(OfferDirection.BUY, new FiatCurrency("USD")); assertEquals("USD", model.getTradeCurrencyCode().get()); } @@ -119,7 +116,7 @@ public class CreateOfferDataModelTest { when(preferences.getSelectedPaymentAccountForCreateOffer()).thenReturn(revolutAccount); when(offerUtil.getMakerFee(any())).thenReturn(Coin.ZERO); - model.initWithData(Direction.BUY, new FiatCurrency("USD")); + model.initWithData(OfferDirection.BUY, new FiatCurrency("USD")); assertEquals("USD", model.getTradeCurrencyCode().get()); } } diff --git a/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferViewModelTest.java b/desktop/src/test/java/bisq/desktop/main/offer/bisq_v1/createoffer/CreateOfferViewModelTest.java similarity index 94% rename from desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferViewModelTest.java rename to desktop/src/test/java/bisq/desktop/main/offer/bisq_v1/createoffer/CreateOfferViewModelTest.java index f82c5636e0..3de14a61b8 100644 --- a/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferViewModelTest.java +++ b/desktop/src/test/java/bisq/desktop/main/offer/bisq_v1/createoffer/CreateOfferViewModelTest.java @@ -15,11 +15,9 @@ * along with Bisq. If not, see . */ -package bisq.desktop.main.offer.createoffer; +package bisq.desktop.main.offer.bisq_v1.createoffer; -import bisq.desktop.util.validation.AltcoinValidator; import bisq.desktop.util.validation.BtcValidator; -import bisq.desktop.util.validation.FiatPriceValidator; import bisq.desktop.util.validation.SecurityDepositValidator; import bisq.core.account.witness.AccountAgeWitnessService; @@ -30,8 +28,9 @@ import bisq.core.locale.Country; import bisq.core.locale.CryptoCurrency; import bisq.core.locale.GlobalSettings; import bisq.core.locale.Res; -import bisq.core.offer.CreateOfferService; +import bisq.core.offer.OfferDirection; import bisq.core.offer.OfferUtil; +import bisq.core.offer.bisq_v1.CreateOfferService; import bisq.core.payment.PaymentAccount; import bisq.core.payment.payload.PaymentMethod; import bisq.core.provider.fee.FeeService; @@ -43,6 +42,8 @@ import bisq.core.user.User; import bisq.core.util.coin.BsqFormatter; import bisq.core.util.coin.CoinFormatter; import bisq.core.util.coin.ImmutableCoinFormatter; +import bisq.core.util.validation.AltcoinValidator; +import bisq.core.util.validation.FiatPriceValidator; import bisq.core.util.validation.InputValidator; import bisq.common.config.Config; @@ -55,12 +56,9 @@ import javafx.collections.FXCollections; import java.time.Instant; -import java.util.UUID; - import org.junit.Before; import org.junit.Test; -import static bisq.core.offer.OfferPayload.Direction; import static bisq.desktop.maker.PreferenceMakers.empty; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -117,8 +115,7 @@ public class CreateOfferViewModelTest { when(accountAgeWitnessService.getMyTradeLimit(any(), any(), any())).thenReturn(100000000L); when(preferences.getUserCountry()).thenReturn(new Country("ES", "Spain", null)); when(bsqFormatter.formatCoin(any())).thenReturn("0"); - when(bsqWalletService.getAvailableConfirmedBalance()).thenReturn(Coin.ZERO); - when(createOfferService.getRandomOfferId()).thenReturn(UUID.randomUUID().toString()); + when(bsqWalletService.getAvailableBalance()).thenReturn(Coin.ZERO); when(tradeStats.getObservableTradeStatisticsSet()).thenReturn(FXCollections.observableSet()); CreateOfferDataModel dataModel = new CreateOfferDataModel(createOfferService, @@ -135,7 +132,7 @@ public class CreateOfferViewModelTest { coinFormatter, tradeStats, null); - dataModel.initWithData(Direction.BUY, new CryptoCurrency("BTC", "bitcoin")); + dataModel.initWithData(OfferDirection.BUY, new CryptoCurrency("BTC", "bitcoin")); dataModel.activate(); model = new CreateOfferViewModel(dataModel, diff --git a/desktop/src/test/java/bisq/desktop/main/offer/offerbook/OfferBookListItemMaker.java b/desktop/src/test/java/bisq/desktop/main/offer/offerbook/OfferBookListItemMaker.java index 55b243aa82..58599f20f4 100644 --- a/desktop/src/test/java/bisq/desktop/main/offer/offerbook/OfferBookListItemMaker.java +++ b/desktop/src/test/java/bisq/desktop/main/offer/offerbook/OfferBookListItemMaker.java @@ -19,7 +19,7 @@ package bisq.desktop.main.offer.offerbook; import bisq.desktop.maker.OfferMaker; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferDirection; import com.natpryce.makeiteasy.Instantiator; import com.natpryce.makeiteasy.MakeItEasy; @@ -37,7 +37,7 @@ public class OfferBookListItemMaker { public static final Property price = new Property<>(); public static final Property amount = new Property<>(); public static final Property minAmount = new Property<>(); - public static final Property direction = new Property<>(); + public static final Property direction = new Property<>(); public static final Property useMarketBasedPrice = new Property<>(); public static final Property marketPriceMargin = new Property<>(); public static final Property baseCurrencyCode = new Property<>(); @@ -48,7 +48,7 @@ public class OfferBookListItemMaker { MakeItEasy.with(OfferMaker.price, lookup.valueOf(price, 100000L)), with(OfferMaker.amount, lookup.valueOf(amount, 100000L)), with(OfferMaker.minAmount, lookup.valueOf(amount, 100000L)), - with(OfferMaker.direction, lookup.valueOf(direction, OfferPayload.Direction.BUY)), + with(OfferMaker.direction, lookup.valueOf(direction, OfferDirection.BUY)), with(OfferMaker.useMarketBasedPrice, lookup.valueOf(useMarketBasedPrice, false)), with(OfferMaker.marketPriceMargin, lookup.valueOf(marketPriceMargin, 0.0)), with(OfferMaker.baseCurrencyCode, lookup.valueOf(baseCurrencyCode, "BTC")), @@ -63,7 +63,7 @@ public class OfferBookListItemMaker { with(OfferMaker.amount, lookup.valueOf(amount, 200000L))))); public static final Maker btcBuyItem = a(OfferBookListItem); - public static final Maker btcSellItem = a(OfferBookListItem, with(direction, OfferPayload.Direction.SELL)); + public static final Maker btcSellItem = a(OfferBookListItem, with(direction, OfferDirection.SELL)); public static final Maker btcItemWithRange = a(OfferBookListItemWithRange); } diff --git a/desktop/src/test/java/bisq/desktop/main/offer/offerbook/OfferBookViewModelTest.java b/desktop/src/test/java/bisq/desktop/main/offer/offerbook/OfferBookViewModelTest.java index 33c5551248..d8fd3ca11a 100644 --- a/desktop/src/test/java/bisq/desktop/main/offer/offerbook/OfferBookViewModelTest.java +++ b/desktop/src/test/java/bisq/desktop/main/offer/offerbook/OfferBookViewModelTest.java @@ -17,16 +17,14 @@ package bisq.desktop.main.offer.offerbook; -import bisq.desktop.main.PriceUtil; - import bisq.core.locale.Country; import bisq.core.locale.CryptoCurrency; import bisq.core.locale.FiatCurrency; import bisq.core.locale.GlobalSettings; import bisq.core.locale.Res; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload; import bisq.core.offer.OpenOfferManager; +import bisq.core.offer.bisq_v1.OfferPayload; import bisq.core.payment.AliPayAccount; import bisq.core.payment.CountryBasedPaymentAccount; import bisq.core.payment.CryptoCurrencyAccount; @@ -44,6 +42,7 @@ import bisq.core.payment.payload.SpecificBanksAccountPayload; import bisq.core.provider.price.MarketPrice; import bisq.core.provider.price.PriceFeedService; import bisq.core.trade.statistics.TradeStatisticsManager; +import bisq.core.util.PriceUtil; import bisq.core.util.coin.BsqFormatter; import bisq.core.util.coin.CoinFormatter; import bisq.core.util.coin.ImmutableCoinFormatter; diff --git a/desktop/src/test/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModelTest.java b/desktop/src/test/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModelTest.java index d0b4b01cac..83bddc3492 100644 --- a/desktop/src/test/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModelTest.java +++ b/desktop/src/test/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModelTest.java @@ -10,10 +10,10 @@ import bisq.core.locale.Country; import bisq.core.locale.CryptoCurrency; import bisq.core.locale.GlobalSettings; import bisq.core.locale.Res; -import bisq.core.offer.CreateOfferService; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferDirection; import bisq.core.offer.OfferUtil; import bisq.core.offer.OpenOffer; +import bisq.core.offer.bisq_v1.CreateOfferService; import bisq.core.payment.CryptoCurrencyAccount; import bisq.core.payment.PaymentAccount; import bisq.core.provider.fee.FeeService; @@ -33,8 +33,6 @@ import javafx.collections.FXCollections; import java.time.Instant; -import java.util.UUID; - import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -94,8 +92,7 @@ public class EditOfferDataModelTest { when(accountAgeWitnessService.getMyTradeLimit(any(), any(), any())).thenReturn(100000000L); when(preferences.getUserCountry()).thenReturn(new Country("US", "United States", null)); when(bsqFormatter.formatCoin(any())).thenReturn("0"); - when(bsqWalletService.getAvailableConfirmedBalance()).thenReturn(Coin.ZERO); - when(createOfferService.getRandomOfferId()).thenReturn(UUID.randomUUID().toString()); + when(bsqWalletService.getAvailableBalance()).thenReturn(Coin.ZERO); model = new EditOfferDataModel(createOfferService, null, @@ -129,6 +126,6 @@ public class EditOfferDataModelTest { @Test public void testInitializeEditOfferWithRemovedAsset() { exception.expect(IllegalArgumentException.class); - model.initWithData(OfferPayload.Direction.BUY, null); + model.initWithData(OfferDirection.BUY, null); } } diff --git a/desktop/src/test/java/bisq/desktop/maker/OfferMaker.java b/desktop/src/test/java/bisq/desktop/maker/OfferMaker.java index 7e956c7bea..3be4461afa 100644 --- a/desktop/src/test/java/bisq/desktop/maker/OfferMaker.java +++ b/desktop/src/test/java/bisq/desktop/maker/OfferMaker.java @@ -18,7 +18,8 @@ package bisq.desktop.maker; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferDirection; +import bisq.core.offer.bisq_v1.OfferPayload; import bisq.network.p2p.NodeAddress; @@ -56,7 +57,7 @@ public class OfferMaker { public static final Property amount = newProperty(); public static final Property baseCurrencyCode = newProperty(); public static final Property counterCurrencyCode = newProperty(); - public static final Property direction = newProperty(); + public static final Property direction = newProperty(); public static final Property useMarketBasedPrice = newProperty(); public static final Property marketPriceMargin = newProperty(); public static final Property nodeAddress = newProperty(); @@ -78,7 +79,7 @@ public class OfferMaker { lookup.valueOf(date, currentTimeMillis()), lookup.valueOf(nodeAddress, getLocalHostNodeWithPort(10000)), lookup.valueOf(pubKeyRing, genPubKeyRing()), - lookup.valueOf(direction, OfferPayload.Direction.BUY), + lookup.valueOf(direction, OfferDirection.BUY), lookup.valueOf(price, 100000L), lookup.valueOf(marketPriceMargin, 0.0), lookup.valueOf(useMarketBasedPrice, false), diff --git a/desktop/src/test/java/bisq/desktop/util/DisplayUtilsTest.java b/desktop/src/test/java/bisq/desktop/util/DisplayUtilsTest.java index 3a233ce4ee..1ed85c4e68 100644 --- a/desktop/src/test/java/bisq/desktop/util/DisplayUtilsTest.java +++ b/desktop/src/test/java/bisq/desktop/util/DisplayUtilsTest.java @@ -3,7 +3,7 @@ package bisq.desktop.util; import bisq.core.locale.Res; import bisq.core.monetary.Volume; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.bisq_v1.OfferPayload; import bisq.core.util.VolumeUtil; import bisq.core.util.coin.CoinFormatter; import bisq.core.util.coin.ImmutableCoinFormatter; diff --git a/gradle/witness/gradle-witness.gradle b/gradle/witness/gradle-witness.gradle index f6f4df9659..ca5ea906cd 100644 --- a/gradle/witness/gradle-witness.gradle +++ b/gradle/witness/gradle-witness.gradle @@ -30,7 +30,7 @@ dependencyVerification { 'com.github.bisq-network.tor-binary:tor-binary-linux64:7f58d31dd684b2e361e2980ba23922cadd5d9d8f8dbab9b3a2c6737741b21f7e', 'com.github.bisq-network.tor-binary:tor-binary-macos:a23802ff66d4ac01366ebe712879e2f51df960572dc34db269588da87453a70d', 'com.github.bisq-network.tor-binary:tor-binary-windows:8e0dee7429228aa0c9f7a36f40f303a016ed8dfb40fea77382f7076c13fc27f1', - 'com.github.bisq-network:bitcoinj:59e4d2370fcbfe38f9f3f01f0830f4b5c0448cd27c867ea4eb23457a79d83c0b', + 'com.github.bisq-network:bitcoinj:eccd3b5250d40ac3147d0e087e856ebaa8665720351b802d30ac53cf17b559c5', 'com.github.bisq-network:jsonrpc4j:842b4a660440ef53cd436da2e21c3e1fed939b620a3fc7542307deb3e77fdeb6', 'com.github.ravn:jsocks:3c71600af027b2b6d4244e4ad14d98ff2352a379410daebefff5d8cd48d742a4', 'com.google.android:annotations:ba734e1e84c09d615af6a09d33034b4f0442f8772dec120efb376d86a565ae15', diff --git a/inventory/src/main/java/bisq/inventory/InventoryMonitor.java b/inventory/src/main/java/bisq/inventory/InventoryMonitor.java index f99d336d64..93e94d1bc9 100644 --- a/inventory/src/main/java/bisq/inventory/InventoryMonitor.java +++ b/inventory/src/main/java/bisq/inventory/InventoryMonitor.java @@ -26,6 +26,7 @@ import bisq.core.network.p2p.inventory.model.InventoryItem; import bisq.core.network.p2p.inventory.model.RequestInfo; import bisq.core.network.p2p.seed.DefaultSeedNodeRepository; import bisq.core.proto.network.CoreNetworkProtoResolver; +import bisq.core.util.JsonUtil; import bisq.network.p2p.NetworkNodeProvider; import bisq.network.p2p.NodeAddress; @@ -36,7 +37,6 @@ import bisq.common.UserThread; import bisq.common.config.BaseCurrencyNetwork; import bisq.common.file.JsonFileManager; import bisq.common.util.Tuple2; -import bisq.common.util.Utilities; import java.time.Clock; @@ -241,7 +241,7 @@ public class InventoryMonitor implements SetupListener { inventoryWebServer.onNewRequestInfo(requestInfoListByNode, requestCounter); - String json = Utilities.objectToJson(requestInfo); + String json = JsonUtil.objectToJson(requestInfo); jsonFileManagerByNodeAddress.get(nodeAddress).writeToDisc(json, String.valueOf(requestInfo.getRequestStartTime())); } diff --git a/monitor/src/main/java/bisq/monitor/metric/P2PMarketStats.java b/monitor/src/main/java/bisq/monitor/metric/P2PMarketStats.java index 8115c1015d..368e9cb03c 100644 --- a/monitor/src/main/java/bisq/monitor/metric/P2PMarketStats.java +++ b/monitor/src/main/java/bisq/monitor/metric/P2PMarketStats.java @@ -19,7 +19,8 @@ package bisq.monitor.metric; import bisq.monitor.Reporter; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferUtil; +import bisq.core.offer.bisq_v1.OfferPayload; import bisq.network.p2p.NodeAddress; import bisq.network.p2p.network.Connection; @@ -144,10 +145,8 @@ public class P2PMarketStats extends P2PSeedNodeSnapshotBase { public void log(Object message) { if (message instanceof OfferPayload) { - OfferPayload currentMessage = (OfferPayload) message; - - String version = "v" + currentMessage.getId().substring(currentMessage.getId().lastIndexOf("-") + 1); - + OfferPayload offerPayload = (OfferPayload) message; + String version = "v" + OfferUtil.getVersionFromId(offerPayload.getId()); buckets.putIfAbsent(version, new Aggregator()); buckets.get(version).increment(); } diff --git a/monitor/src/main/java/bisq/monitor/metric/P2PNetworkLoad.java b/monitor/src/main/java/bisq/monitor/metric/P2PNetworkLoad.java index 48c6089776..ff4bb78b6f 100644 --- a/monitor/src/main/java/bisq/monitor/metric/P2PNetworkLoad.java +++ b/monitor/src/main/java/bisq/monitor/metric/P2PNetworkLoad.java @@ -197,11 +197,6 @@ public class P2PNetworkLoad extends Metric implements MessageListener, SetupList private static class Counter { private int value = 1; - /** - * atomic get and reset - * - * @return the current value - */ synchronized int getAndReset() { try { return value; diff --git a/p2p/src/main/java/bisq/network/p2p/network/Connection.java b/p2p/src/main/java/bisq/network/p2p/network/Connection.java index bedef3ed9e..f4cb3a4629 100644 --- a/p2p/src/main/java/bisq/network/p2p/network/Connection.java +++ b/p2p/src/main/java/bisq/network/p2p/network/Connection.java @@ -27,14 +27,13 @@ import bisq.network.p2p.peers.keepalive.messages.KeepAliveMessage; import bisq.network.p2p.storage.P2PDataStorage; import bisq.network.p2p.storage.messages.AddDataMessage; import bisq.network.p2p.storage.messages.AddPersistableNetworkPayloadMessage; +import bisq.network.p2p.storage.messages.RemoveDataMessage; import bisq.network.p2p.storage.payload.CapabilityRequiringPayload; import bisq.network.p2p.storage.payload.PersistableNetworkPayload; -import bisq.network.p2p.storage.payload.ProtectedStoragePayload; import bisq.common.Proto; import bisq.common.UserThread; import bisq.common.app.Capabilities; -import bisq.common.app.Capability; import bisq.common.app.HasCapabilities; import bisq.common.app.Version; import bisq.common.config.Config; @@ -70,17 +69,15 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Queue; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; import java.lang.ref.WeakReference; @@ -123,14 +120,12 @@ public class Connection implements HasCapabilities, Runnable, MessageListener { /////////////////////////////////////////////////////////////////////////////////////////// private final Socket socket; - // private final MessageListener messageListener; private final ConnectionListener connectionListener; @Nullable private final NetworkFilter networkFilter; @Getter private final String uid; private final ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(runnable -> new Thread(runnable, "Connection.java executor-service")); - // holder of state shared between InputHandler and Connection @Getter private final Statistic statistic; @Getter @@ -221,10 +216,6 @@ public class Connection implements HasCapabilities, Runnable, MessageListener { return capabilities; } - private final Object lock = new Object(); - private final Queue queueOfBundles = new ConcurrentLinkedQueue<>(); - private final ScheduledExecutorService bundleSender = Executors.newSingleThreadScheduledExecutor(); - // Called from various threads public void sendMessage(NetworkEnvelope networkEnvelope) { long ts = System.currentTimeMillis(); @@ -242,7 +233,7 @@ public class Connection implements HasCapabilities, Runnable, MessageListener { return; } - if (!noCapabilityRequiredOrCapabilityIsSupported(networkEnvelope)) { + if (!testCapability(networkEnvelope)) { log.debug("Capability for networkEnvelope is required but not supported"); return; } @@ -257,58 +248,6 @@ public class Connection implements HasCapabilities, Runnable, MessageListener { getSendMsgThrottleTrigger(), getSendMsgThrottleSleep(), lastSendTimeStamp, now, elapsed, networkEnvelope.getClass().getSimpleName()); - // check if BundleOfEnvelopes is supported - if (getCapabilities().containsAll(new Capabilities(Capability.BUNDLE_OF_ENVELOPES))) { - synchronized (lock) { - // check if current envelope fits size - // - no? create new envelope - - int size = !queueOfBundles.isEmpty() ? queueOfBundles.element().toProtoNetworkEnvelope().getSerializedSize() + networkEnvelopeSize : 0; - if (queueOfBundles.isEmpty() || size > MAX_PERMITTED_MESSAGE_SIZE * 0.9) { - // - no? create a bucket - queueOfBundles.add(new BundleOfEnvelopes()); - - // - and schedule it for sending - lastSendTimeStamp += getSendMsgThrottleSleep(); - - bundleSender.schedule(() -> { - if (!stopped) { - synchronized (lock) { - BundleOfEnvelopes bundle = queueOfBundles.poll(); - if (bundle != null && !stopped) { - NetworkEnvelope envelope; - int msgSize; - if (bundle.getEnvelopes().size() == 1) { - envelope = bundle.getEnvelopes().get(0); - msgSize = envelope.toProtoNetworkEnvelope().getSerializedSize(); - } else { - envelope = bundle; - msgSize = networkEnvelopeSize; - } - try { - protoOutputStream.writeEnvelope(envelope); - UserThread.execute(() -> messageListeners.forEach(e -> e.onMessageSent(envelope, this))); - UserThread.execute(() -> connectionStatistics.addSendMsgMetrics(System.currentTimeMillis() - ts, msgSize)); - } catch (Throwable t) { - log.error("Sending envelope of class {} to address {} " + - "failed due {}", - envelope.getClass().getSimpleName(), - this.getPeersNodeAddressOptional(), - t.toString()); - log.error("envelope: {}", envelope); - } - } - } - } - }, lastSendTimeStamp - now, TimeUnit.MILLISECONDS); - } - - // - yes? add to bucket - queueOfBundles.element().add(networkEnvelope); - } - return; - } - Thread.sleep(getSendMsgThrottleSleep()); } @@ -324,41 +263,53 @@ public class Connection implements HasCapabilities, Runnable, MessageListener { } } - // TODO: If msg is BundleOfEnvelopes we should check each individual message for capability and filter out those - // which fail. - public boolean noCapabilityRequiredOrCapabilityIsSupported(Proto msg) { - boolean result; - if (msg instanceof AddDataMessage) { - final ProtectedStoragePayload protectedStoragePayload = (((AddDataMessage) msg).getProtectedStorageEntry()).getProtectedStoragePayload(); - result = !(protectedStoragePayload instanceof CapabilityRequiringPayload); - if (!result) - result = capabilities.containsAll(((CapabilityRequiringPayload) protectedStoragePayload).getRequiredCapabilities()); - } else if (msg instanceof AddPersistableNetworkPayloadMessage) { - final PersistableNetworkPayload persistableNetworkPayload = ((AddPersistableNetworkPayloadMessage) msg).getPersistableNetworkPayload(); - result = !(persistableNetworkPayload instanceof CapabilityRequiringPayload); - if (!result) - result = capabilities.containsAll(((CapabilityRequiringPayload) persistableNetworkPayload).getRequiredCapabilities()); - } else if (msg instanceof CapabilityRequiringPayload) { - result = capabilities.containsAll(((CapabilityRequiringPayload) msg).getRequiredCapabilities()); - } else { - result = true; + public boolean testCapability(NetworkEnvelope networkEnvelope) { + if (networkEnvelope instanceof BundleOfEnvelopes) { + // We remove elements in the list which fail the capability test + BundleOfEnvelopes bundleOfEnvelopes = (BundleOfEnvelopes) networkEnvelope; + updateBundleOfEnvelopes(bundleOfEnvelopes); + // If the bundle is empty we dont send the networkEnvelope + return !bundleOfEnvelopes.getEnvelopes().isEmpty(); } + return extractCapabilityRequiringPayload(networkEnvelope) + .map(this::testCapability) + .orElse(true); + } + + private boolean testCapability(CapabilityRequiringPayload capabilityRequiringPayload) { + boolean result = capabilities.containsAll(capabilityRequiringPayload.getRequiredCapabilities()); if (!result) { - if (capabilities.size() > 1) { - Proto data = msg; - if (msg instanceof AddDataMessage) { - data = ((AddDataMessage) msg).getProtectedStorageEntry().getProtectedStoragePayload(); - } - // Monitoring nodes have only one capability set, we don't want to log those - log.debug("We did not send the message because the peer does not support our required capabilities. " + - "messageClass={}, peer={}, peers supportedCapabilities={}", - data.getClass().getSimpleName(), peersNodeAddressOptional, capabilities); - } + log.debug("We did not send {} because capabilities are not supported.", + capabilityRequiringPayload.getClass().getSimpleName()); } return result; } + private void updateBundleOfEnvelopes(BundleOfEnvelopes bundleOfEnvelopes) { + List toRemove = bundleOfEnvelopes.getEnvelopes().stream() + .filter(networkEnvelope -> !testCapability(networkEnvelope)) + .collect(Collectors.toList()); + bundleOfEnvelopes.getEnvelopes().removeAll(toRemove); + } + + private Optional extractCapabilityRequiringPayload(Proto proto) { + Proto candidate = proto; + // Lets check if our networkEnvelope is a wrapped data structure + if (proto instanceof AddDataMessage) { + candidate = (((AddDataMessage) proto).getProtectedStorageEntry()).getProtectedStoragePayload(); + } else if (proto instanceof RemoveDataMessage) { + candidate = (((RemoveDataMessage) proto).getProtectedStorageEntry()).getProtectedStoragePayload(); + } else if (proto instanceof AddPersistableNetworkPayloadMessage) { + candidate = (((AddPersistableNetworkPayloadMessage) proto).getPersistableNetworkPayload()); + } + + if (candidate instanceof CapabilityRequiringPayload) { + return Optional.of((CapabilityRequiringPayload) candidate); + } + return Optional.empty(); + } + public void addMessageListener(MessageListener messageListener) { boolean isNewEntry = messageListeners.add(messageListener); if (!isNewEntry) @@ -531,7 +482,6 @@ public class Connection implements HasCapabilities, Runnable, MessageListener { stopped = true; - //noinspection UnstableApiUsage Uninterruptibles.sleepUninterruptibly(200, TimeUnit.MILLISECONDS); } catch (Throwable t) { log.error(t.getMessage()); @@ -576,10 +526,8 @@ public class Connection implements HasCapabilities, Runnable, MessageListener { //noinspection UnstableApiUsage MoreExecutors.shutdownAndAwaitTermination(singleThreadExecutor, 500, TimeUnit.MILLISECONDS); - //noinspection UnstableApiUsage - MoreExecutors.shutdownAndAwaitTermination(bundleSender, 500, TimeUnit.MILLISECONDS); - log.debug("Connection shutdown complete {}", this.toString()); + log.debug("Connection shutdown complete {}", this); // Use UserThread.execute as its not clear if that is called from a non-UserThread if (shutDownCompleteHandler != null) UserThread.execute(shutDownCompleteHandler); @@ -655,7 +603,7 @@ public class Connection implements HasCapabilities, Runnable, MessageListener { "numRuleViolations={}\n\t" + "corruptRequest={}\n\t" + "corruptRequests={}\n\t" + - "connection={}", numRuleViolations, ruleViolation, ruleViolations.toString(), this); + "connection={}", numRuleViolations, ruleViolation, ruleViolations, this); this.ruleViolation = ruleViolation; if (ruleViolation == RuleViolation.PEER_BANNED) { log.warn("We close connection due RuleViolation.PEER_BANNED. peersNodeAddress={}", getPeersNodeAddressOptional()); @@ -690,13 +638,13 @@ public class Connection implements HasCapabilities, Runnable, MessageListener { log.info("SocketException (expected if connection lost). closeConnectionReason={}; connection={}", closeConnectionReason, this); } else if (e instanceof SocketTimeoutException || e instanceof TimeoutException) { closeConnectionReason = CloseConnectionReason.SOCKET_TIMEOUT; - log.info("Shut down caused by exception {} on connection={}", e.toString(), this); + log.info("Shut down caused by exception {} on connection={}", e, this); } else if (e instanceof EOFException) { closeConnectionReason = CloseConnectionReason.TERMINATED; - log.warn("Shut down caused by exception {} on connection={}", e.toString(), this); + log.warn("Shut down caused by exception {} on connection={}", e, this); } else if (e instanceof OptionalDataException || e instanceof StreamCorruptedException) { closeConnectionReason = CloseConnectionReason.CORRUPTED_DATA; - log.warn("Shut down caused by exception {} on connection={}", e.toString(), this); + log.warn("Shut down caused by exception {} on connection={}", e, this); } else { // TODO sometimes we get StreamCorruptedException, OptionalDataException, IllegalStateException closeConnectionReason = CloseConnectionReason.UNKNOWN_EXCEPTION; diff --git a/p2p/src/main/java/bisq/network/p2p/peers/BroadcastHandler.java b/p2p/src/main/java/bisq/network/p2p/peers/BroadcastHandler.java index d0dc97c3c2..b7b89d2579 100644 --- a/p2p/src/main/java/bisq/network/p2p/peers/BroadcastHandler.java +++ b/p2p/src/main/java/bisq/network/p2p/peers/BroadcastHandler.java @@ -231,7 +231,7 @@ public class BroadcastHandler implements PeerManager.Listener { return broadcastRequests.stream() .filter(broadcastRequest -> !connection.getPeersNodeAddressOptional().isPresent() || !connection.getPeersNodeAddressOptional().get().equals(broadcastRequest.getSender())) - .filter(broadcastRequest -> connection.noCapabilityRequiredOrCapabilityIsSupported(broadcastRequest.getMessage())) + .filter(broadcastRequest -> connection.testCapability(broadcastRequest.getMessage())) .collect(Collectors.toList()); } diff --git a/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java b/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java index ada7d8eddc..615cac138e 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java @@ -106,10 +106,12 @@ import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; +import java.util.function.Predicate; import java.util.stream.Collectors; import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.Setter; import lombok.ToString; import lombok.extern.slf4j.Slf4j; @@ -155,6 +157,9 @@ public class P2PDataStorage implements MessageListener, ConnectionListener, Pers // Don't convert to local variable as it might get GC'ed. private MonadicBinding readFromResourcesCompleteBinding; + @Setter + private Predicate filterPredicate; //Set from FilterManager + /////////////////////////////////////////////////////////////////////////////////////////// // Constructor @@ -784,6 +789,13 @@ public class P2PDataStorage implements MessageListener, ConnectionListener, Pers return false; } + // Test against filterPredicate set from FilterManager + if (filterPredicate != null && + !filterPredicate.test(protectedStorageEntry.getProtectedStoragePayload())) { + log.debug("filterPredicate test failed. hashOfPayload={}", hashOfPayload); + return false; + } + // This is an updated entry. Record it and signal listeners. map.put(hashOfPayload, protectedStorageEntry); hashMapChangedListeners.forEach(e -> e.onAdded(Collections.singletonList(protectedStorageEntry))); diff --git a/p2p/src/main/java/bisq/network/p2p/storage/payload/ProofOfWorkPayload.java b/p2p/src/main/java/bisq/network/p2p/storage/payload/ProofOfWorkPayload.java new file mode 100644 index 0000000000..0ed8439118 --- /dev/null +++ b/p2p/src/main/java/bisq/network/p2p/storage/payload/ProofOfWorkPayload.java @@ -0,0 +1,24 @@ +/* + * 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.network.p2p.storage.payload; + +import bisq.common.crypto.ProofOfWork; + +public interface ProofOfWorkPayload { + ProofOfWork getProofOfWork(); +} diff --git a/proto/src/main/proto/grpc.proto b/proto/src/main/proto/grpc.proto index 84e6ee18ad..b84ea6ae4a 100644 --- a/proto/src/main/proto/grpc.proto +++ b/proto/src/main/proto/grpc.proto @@ -62,14 +62,24 @@ message GetMethodHelpReply { /////////////////////////////////////////////////////////////////////////////////////////// service Offers { + rpc GetBsqSwapOffer (GetOfferRequest) returns (GetBsqSwapOfferReply) { + } rpc GetOffer (GetOfferRequest) returns (GetOfferReply) { } + rpc GetMyBsqSwapOffer (GetMyOfferRequest) returns (GetMyBsqSwapOfferReply) { + } rpc GetMyOffer (GetMyOfferRequest) returns (GetMyOfferReply) { } + rpc GetBsqSwapOffers (GetOffersRequest) returns (GetBsqSwapOffersReply) { + } rpc GetOffers (GetOffersRequest) returns (GetOffersReply) { } + rpc GetMyBsqSwapOffers (GetMyOffersRequest) returns (GetMyBsqSwapOffersReply) { + } rpc GetMyOffers (GetMyOffersRequest) returns (GetMyOffersReply) { } + rpc CreateBsqSwapOffer (CreateBsqSwapOfferRequest) returns (CreateBsqSwapOfferReply) { + } rpc CreateOffer (CreateOfferRequest) returns (CreateOfferReply) { } rpc EditOffer (EditOfferRequest) returns (EditOfferReply) { @@ -78,6 +88,10 @@ service Offers { } } +message GetBsqSwapOfferReply { + BsqSwapOfferInfo bsqSwapOffer = 1; +} + message GetOfferRequest { string id = 1; } @@ -86,6 +100,10 @@ message GetOfferReply { OfferInfo offer = 1; } +message GetMyBsqSwapOfferReply { + BsqSwapOfferInfo bsqSwapOffer = 1; +} + message GetMyOfferRequest { string id = 1; } @@ -103,6 +121,10 @@ message GetOffersReply { repeated OfferInfo offers = 1; } +message GetBsqSwapOffersReply { + repeated BsqSwapOfferInfo bsqSwapOffers = 1; +} + message GetMyOffersRequest { string direction = 1; string currencyCode = 2; @@ -112,6 +134,22 @@ message GetMyOffersReply { repeated OfferInfo offers = 1; } +message GetMyBsqSwapOffersReply { + repeated BsqSwapOfferInfo bsqSwapOffers = 1; +} + +message CreateBsqSwapOfferRequest { + string direction = 1; + uint64 amount = 2; + uint64 minAmount = 3; + string price = 4; + string paymentAccountId = 5; +} + +message CreateBsqSwapOfferReply { + BsqSwapOfferInfo bsqSwapOffer = 1; +} + message CreateOfferRequest { string currencyCode = 1; string direction = 2; @@ -166,6 +204,25 @@ message CancelOfferRequest { message CancelOfferReply { } +message BsqSwapOfferInfo { + string id = 1; + string direction = 2; + uint64 amount = 3; + uint64 minAmount = 4; + uint64 price = 5; + string makerPaymentAccountId = 6; + string paymentMethodId = 7; + string paymentMethodShortName = 8; + string baseCurrencyCode = 9; + string counterCurrencyCode = 10; + uint64 getMakerFee = 11; + uint64 date = 12; + string ownerNodeAddress = 13; + string pubKeyRing = 14; + string versionNr = 15; + int32 protocolVersion = 16; +} + message OfferInfo { string id = 1; string direction = 2; @@ -254,6 +311,7 @@ message CreateCryptoCurrencyPaymentAccountRequest { string currencyCode = 2; string address = 3; bool tradeInstant = 4; + bool isBsqSwap = 5; } message CreateCryptoCurrencyPaymentAccountReply { @@ -320,8 +378,12 @@ message StopReply { /////////////////////////////////////////////////////////////////////////////////////////// service Trades { + rpc GetBsqSwapTrade (GetTradeRequest) returns (GetBsqSwapTradeReply) { + } rpc GetTrade (GetTradeRequest) returns (GetTradeReply) { } + rpc TakeBsqSwapOffer (TakeBsqSwapOfferRequest) returns (TakeBsqSwapOfferReply) { + } rpc TakeOffer (TakeOfferRequest) returns (TakeOfferReply) { } rpc ConfirmPaymentStarted (ConfirmPaymentStartedRequest) returns (ConfirmPaymentStartedReply) { @@ -334,6 +396,17 @@ service Trades { } } +message TakeBsqSwapOfferRequest { + string offerId = 1; + string paymentAccountId = 2; + string takerFeeCurrencyCode = 3; +} + +message TakeBsqSwapOfferReply { + BsqSwapTradeInfo bsqSwapTrade = 1; + AvailabilityResultWithDescription failureReason = 2; +} + message TakeOfferRequest { string offerId = 1; string paymentAccountId = 2; @@ -359,6 +432,10 @@ message ConfirmPaymentReceivedRequest { message ConfirmPaymentReceivedReply { } +message GetBsqSwapTradeReply { + BsqSwapTradeInfo bsqSwapTrade = 1; +} + message GetTradeRequest { string tradeId = 1; } @@ -383,6 +460,36 @@ message WithdrawFundsRequest { message WithdrawFundsReply { } +message BsqSwapTradeInfo { + BsqSwapOfferInfo bsqSwapOfferInfo = 1; + string tradeId = 2; + string tempTradingPeerNodeAddress = 3; + string peerNodeAddress = 4; + string txId = 5; + uint64 bsqTradeAmount = 6; + uint64 bsqMaxTradeAmount = 7; + uint64 bsqMinTradeAmount = 8; + uint64 btcTradeAmount = 9; + uint64 btcMaxTradeAmount = 10; + uint64 btcMinTradeAmount = 11; + uint64 tradePrice = 12; + bool isCurrencyForMakerFeeBtc = 13; + bool isCurrencyForTakerFeeBtc = 14; + uint64 bsqMakerTradeFee = 15; + uint64 btcMakerTradeFee = 16; + uint64 bsqTakerTradeFee = 17; + uint64 btcTakerTradeFee = 18; + uint64 txFeePerVbyte = 19; + uint64 txFee = 20; + string makerBsqAddress = 21; + string makerBtcAddress = 22; + string takerBsqAddress = 23; + string takerBtcAddress = 24; + uint64 takeOfferDate = 25; + string state = 26; + string errorMessage = 27; +} + message TradeInfo { OfferInfo offer = 1; string tradeId = 2; diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 7b48346db4..b8e573ab0d 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -83,6 +83,12 @@ message NetworkEnvelope { GetInventoryResponse get_inventory_response = 53; ShareBuyerPaymentAccountMessage share_buyer_payment_account_message = 54; // Added at 1.7.0 + + SellersBsqSwapRequest sellers_bsq_swap_request = 55; + BuyersBsqSwapRequest buyers_bsq_swap_request= 56; + BsqSwapTxInputsMessage bsq_swap_tx_inputs_message= 57; + BsqSwapFinalizeTxRequest bsq_swap_finalize_tx_request = 58; + BsqSwapFinalizedTxMessage bsq_swap_finalized_tx_message = 59; } } @@ -373,6 +379,63 @@ message TraderSignedWitnessMessage { SignedWitness signed_witness = 4 [deprecated = true]; } +// BsqSwap +message SellersBsqSwapRequest { + string uid = 1; + string trade_id = 2; + NodeAddress sender_node_address = 3; + PubKeyRing taker_pub_key_ring = 4; + int64 trade_amount = 5; + int64 tx_fee_per_vbyte = 6; + int64 maker_fee = 7; + int64 taker_fee = 8; + int64 trade_date = 9; +} + +message BuyersBsqSwapRequest { + string uid = 1; + string trade_id = 2; + NodeAddress sender_node_address = 3; + PubKeyRing taker_pub_key_ring = 4; + int64 trade_amount = 5; + int64 tx_fee_per_vbyte = 6; + int64 maker_fee = 7; + int64 taker_fee = 8; + int64 trade_date = 9; + repeated RawTransactionInput bsq_inputs = 10; + int64 bsq_change = 11; + string buyers_btc_payout_address = 12; + string buyers_bsq_change_address = 13; +} + +message BsqSwapTxInputsMessage { + string uid = 1; + string trade_id = 2; + NodeAddress sender_node_address = 3; + repeated RawTransactionInput bsq_inputs = 4; + int64 bsq_change = 5; + string buyers_btc_payout_address = 6; + string buyers_bsq_change_address = 7; +} + +message BsqSwapFinalizeTxRequest { + string uid = 1; + string trade_id = 2; + NodeAddress sender_node_address = 3; + bytes tx = 4; + repeated RawTransactionInput btc_inputs = 5; + int64 btc_change = 6; + string bsq_payout_address = 7; + string btc_change_address = 8; +} + +message BsqSwapFinalizedTxMessage { + string uid = 1; + string trade_id = 2; + NodeAddress sender_node_address = 3; + bytes tx = 4; +} + // dispute enum SupportType { @@ -546,6 +609,7 @@ message StoragePayload { OfferPayload offer_payload = 7; TempProposalPayload temp_proposal_payload = 8; RefundAgent refund_agent = 9; + BsqSwapOfferPayload bsq_swap_offer_payload = 10; } } @@ -704,13 +768,15 @@ message Filter { repeated string node_addresses_banned_from_network = 26; bool disable_api = 27; bool disable_mempool_validation = 28; + bool disable_pow_message = 29; + int32 pow_difficulty = 30; } // Deprecated message TradeStatistics2 { string base_currency = 1 [deprecated = true]; string counter_currency = 2 [deprecated = true]; - OfferPayload.Direction direction = 3 [deprecated = true]; + OfferDirection direction = 3 [deprecated = true]; int64 trade_price = 4 [deprecated = true]; int64 trade_amount = 5 [deprecated = true]; int64 trade_date = 6 [deprecated = true]; @@ -746,17 +812,11 @@ message MailboxStoragePayload { } message OfferPayload { - enum Direction { - PB_ERROR = 0; - BUY = 1; - SELL = 2; - } - string id = 1; int64 date = 2; NodeAddress owner_node_address = 3; PubKeyRing pub_key_ring = 4; - Direction direction = 5; + OfferDirection direction = 5; int64 price = 6; double market_price_margin = 7; bool use_market_based_price = 8; @@ -792,6 +852,35 @@ message OfferPayload { int32 protocol_version = 38; } +enum OfferDirection { + OFFER_DIRECTION_ERROR = 0; + BUY = 1; + SELL = 2; +} + +message BsqSwapOfferPayload { + string id = 1; + int64 date = 2; + NodeAddress owner_node_address = 3; + PubKeyRing pub_key_ring = 4; + OfferDirection direction = 5; + int64 price = 6; + int64 amount = 7; + int64 min_amount = 8; + ProofOfWork proof_of_work = 9; + map extra_data = 10; + string version_nr = 11; + int32 protocol_version = 12; +} + +message ProofOfWork { + bytes payload = 1; + int64 counter = 2; + bytes challenge = 3; + bytes difficulty = 4; + int64 duration = 5; +} + message AccountAgeWitness { bytes hash = 1; int64 date = 2; @@ -939,6 +1028,7 @@ message RawTransactionInput { int64 index = 1; bytes parent_transaction = 2; int64 value = 3; + int32 script_type_id = 4; } enum AvailabilityResult { @@ -1003,6 +1093,7 @@ message PaymentAccountPayload { CelPayAccountPayload cel_pay_account_payload = 37; MoneseAccountPayload monese_account_payload = 38; VerseAccountPayload verse_account_payload = 39; + BsqSwapAccountPayload bsq_swap_account_payload = 40; } map exclude_from_json_data = 15; } @@ -1146,6 +1237,9 @@ message InstantCryptoCurrencyAccountPayload { string address = 1; } +message BsqSwapAccountPayload { +} + message FasterPaymentsAccountPayload { string sort_code = 1; string account_nr = 2; @@ -1478,7 +1572,10 @@ message Offer { MAKER_OFFLINE = 6; } - OfferPayload offer_payload = 1; + oneof message { + OfferPayload offer_payload = 1; + BsqSwapOfferPayload bsq_swap_offer_payload = 2; + } } message OpenOffer { @@ -1506,6 +1603,10 @@ message Tradable { BuyerAsTakerTrade buyer_as_taker_trade = 3; SellerAsMakerTrade seller_as_maker_trade = 4; SellerAsTakerTrade seller_as_taker_trade = 5; + BsqSwapBuyerAsMakerTrade bsq_swap_buyer_as_maker_trade = 6; + BsqSwapBuyerAsTakerTrade bsq_swap_buyer_as_taker_trade = 7; + BsqSwapSellerAsMakerTrade bsq_swap_seller_as_maker_trade = 8; + BsqSwapSellerAsTakerTrade bsq_swap_seller_as_taker_trade = 9; } } @@ -1634,6 +1735,44 @@ message SellerAsTakerTrade { Trade trade = 1; } +message BsqSwapTrade { + enum State { + PB_ERROR_STATE = 0; + PREPARATION = 1; + COMPLETED = 2; + FAILED = 3; + } + + string uid = 1; + Offer offer = 2; + int64 amount = 3; + int64 take_offer_date = 4; + NodeAddress peer_node_address = 5; + int64 mining_fee_per_byte = 6; + int64 maker_fee = 7; + int64 taker_fee = 8; + BsqSwapProtocolModel bsq_swap_protocol_model = 9; + string tx_id = 10; + string error_message = 11; + State state = 12; +} + +message BsqSwapBuyerAsMakerTrade { + BsqSwapTrade bsq_swap_trade = 1; +} + +message BsqSwapBuyerAsTakerTrade { + BsqSwapTrade bsq_swap_trade = 1; +} + +message BsqSwapSellerAsMakerTrade { + BsqSwapTrade bsq_swap_trade = 1; +} + +message BsqSwapSellerAsTakerTrade { + BsqSwapTrade bsq_swap_trade = 1; +} + message ProcessModel { TradingPeer trading_peer = 1; string offer_id = 2; @@ -1676,6 +1815,29 @@ message TradingPeer { bytes hash_of_payment_account_payload = 16; } +message BsqSwapProtocolModel { + BsqSwapTradePeer trade_peer = 1; + PubKeyRing pub_key_ring = 2; + string btc_address = 3; + string bsq_address = 4; + repeated RawTransactionInput inputs = 5; + int64 change = 6; + int64 payout = 7; + bytes tx = 8; + int64 tx_fee = 9; +} + +message BsqSwapTradePeer { + PubKeyRing pub_key_ring = 1; + string btc_address = 2; + string bsq_address = 3; + repeated RawTransactionInput inputs = 4; + int64 change = 5; + int64 payout = 6; + bytes tx = 7; +} + + /////////////////////////////////////////////////////////////////////////////////////////// // Dispute ///////////////////////////////////////////////////////////////////////////////////////////