diff --git a/apitest/scripts/trade-simulation-utils.sh b/apitest/scripts/trade-simulation-utils.sh index c43beaf2ec8..6b146222619 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/test/java/bisq/apitest/ApiTestCase.java b/apitest/src/test/java/bisq/apitest/ApiTestCase.java index fb4938d4790..315561cbb6f 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.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 @@ protected static void genBtcBlocksThenWait(int numBlocks, long wait) { } 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/offer/AbstractOfferTest.java b/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java index 2d2e5fc6d73..0f01407412a 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 static void setUp() { 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 00000000000..c46da2ea5d9 --- /dev/null +++ b/apitest/src/test/java/bisq/apitest/method/offer/BsqSwapOfferTest.java @@ -0,0 +1,172 @@ +/* + * 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.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.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 testAliceCreateBsqSwapBuyOffer1() { + createBsqSwapOffer(); + } + + @Test + @Order(3) + public void testAliceCreateBsqSwapBuyOffer2() { + createBsqSwapOffer(); + } + + @Test + @Order(4) + public void testAliceCreateBsqSwapBuyOffer3() { + createBsqSwapOffer(); + } + + @Test + @Order(5) + public void testAliceCreateBsqSwapBuyOffer4() { + createBsqSwapOffer(); + } + + private void createBsqSwapOffer() { + var bsqSwapOffer = aliceClient.createBsqSwapOffer(BUY.name(), + 100_000_000L, + 100_000_000L, + "0.00005", + alicesBsqAcct.getId()); + log.info("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(100_000_000L, bsqSwapOffer.getAmount()); + assertEquals(100_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.info("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.info("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); + } + } + } + + @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.info("Alice's After Trade Balance:\n{}", formatBalancesTbls(alicesBalances)); + var bobsBalances = bobClient.getBalances(); + log.info("Bob's After Trade Balance:\n{}", formatBalancesTbls(bobsBalances)); + } +} 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 8db313583cd..d0ec6d86f1b 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 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 652d7f50dcf..b76a8b7aedc 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.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 715e05a92e7..096c457e77d 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.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 391bb4c5a37..efa983caba0 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.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 a947044d07c..018b7277e3c 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.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 33626ee6c3c..cc8a02f33d9 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 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 00000000000..f9463c61115 --- /dev/null +++ b/apitest/src/test/java/bisq/apitest/method/trade/BsqSwapTradeTest.java @@ -0,0 +1,163 @@ +/* + * 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.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +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; + + @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(), + 100_000_000L, + 100_000_000L, + "0.00005", + alicesBsqAcct.getId()); + log.info("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(100_000_000L, bsqSwapOffer.getAmount()); + assertEquals(100_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.info("Trade at t1: {}", bsqSwapTradeInfo); + assertEquals(BsqSwapTrade.State.PREPARATION.name(), bsqSwapTradeInfo.getState()); + genBtcBlocksThenWait(1, 3000); + + bsqSwapTradeInfo = getBsqSwapTrade(bsqSwapTradeInfo.getTradeId()); + log.info("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 bsqSwapOfferInfos = new ArrayList<>(); + int numFetchAttempts = 0; + while (bsqSwapOfferInfos.size() == 0) { + bsqSwapOfferInfos.addAll(bobClient.getBsqSwapOffers(BUY.name(), "BSQ")); + numFetchAttempts++; + if (bsqSwapOfferInfos.size() == 0) { + log.warn("No available bsq swap offer found after {} fetch attempts.", numFetchAttempts); + if (numFetchAttempts > 9) { + fail(format("Bob gave up on fetching available bsq swap offer after %d attempts.", numFetchAttempts)); + } + sleep(1000); + } else { + assertEquals(1, bsqSwapOfferInfos.size()); + log.info("Bob found new available bsq swap offer on attempt # {}.", numFetchAttempts); + break; + } + } + // Test api's getBsqSwapOffer(id). + var bsqSwapOffer = bobClient.getBsqSwapOffer(bsqSwapOfferInfos.get(0).getId()); + assertEquals(bsqSwapOfferInfos.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) { + 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/BsqSwapTradeTestLoop.java b/apitest/src/test/java/bisq/apitest/method/trade/BsqSwapTradeTestLoop.java new file mode 100644 index 00000000000..d5cf9b7dd76 --- /dev/null +++ b/apitest/src/test/java/bisq/apitest/method/trade/BsqSwapTradeTestLoop.java @@ -0,0 +1,65 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.apitest.method.trade; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + + + +import bisq.apitest.method.offer.AbstractOfferTest; + +// @Disabled +@Slf4j +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class BsqSwapTradeTestLoop extends AbstractOfferTest { + + @BeforeAll + public static void setUp() { + AbstractOfferTest.setUp(); + createBsqSwapBsqPaymentAccounts(); + } + + @Test + @Order(1) + public void testGetBalancesBeforeTrade() { + BsqSwapTradeTest test = new BsqSwapTradeTest(); + runTradeLoop(test); + } + + private void runTradeLoop(BsqSwapTradeTest test) { + // TODO Fix wallet inconsistency bugs after 2nd trades. + for (int tradeCount = 1; tradeCount <= 2; tradeCount++) { + log.warn("================================ Trade # {} ================================", tradeCount); + test.testGetBalancesBeforeTrade(); + + test.testAliceCreateBsqSwapBuyOffer(); + genBtcBlocksThenWait(1, 8000); + + test.testBobTakesBsqSwapOffer(); + genBtcBlocksThenWait(1, 8000); + + test.testGetBalancesAfterTrade(); + } + } +} diff --git a/apitest/src/test/java/bisq/apitest/method/trade/ExpectedProtocolStatus.java b/apitest/src/test/java/bisq/apitest/method/trade/ExpectedProtocolStatus.java index 63655585947..f054349ff9b 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 fc365931d5f..9acdb4b32ad 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.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.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 8f03520b525..7a7f0dd9c8e 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 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 1035875010e..a625d86fae4 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 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 786601e6fa1..d33e0e360e7 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.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 c4abd90934b..078d2998599 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 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 e978624c2f0..f96d422f6a5 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 void testBalancesAfterSendingBsqAndGeneratingBtcBlock(final TestInfo test 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 @@ private BsqBalanceInfo waitForNonZeroBsqUnverifiedBalance(GrpcClient grpcClient) 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 85b9f04e84f..e5c002fba5f 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 static bisq.core.api.model.BsqBalanceInfo bsqBalanceModel(long availableC 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/LongRunningOfferDeactivationTest.java b/apitest/src/test/java/bisq/apitest/scenario/LongRunningOfferDeactivationTest.java index e7f09247a86..46195144dcc 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.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/bot/script/BotScriptGenerator.java b/apitest/src/test/java/bisq/apitest/scenario/bot/script/BotScriptGenerator.java index c81730c4c40..9d62799e9e3 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 @@ private String examplesNotUsingTestHarness() { } 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 e8f4d94c2a0..6af9a3f29d3 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' @@ -414,7 +414,7 @@ configure(project(':desktop')) { modules = ['javafx.controls', 'javafx.fxml'] } - version = '1.7.4-SNAPSHOT' + version = '1.7.5' jar.manifest.attributes( "Implementation-Title": project.name, diff --git a/cli/src/main/java/bisq/cli/DirectionFormat.java b/cli/src/main/java/bisq/cli/DirectionFormat.java index ac0e5b6c556..b3cb1773f47 100644 --- a/cli/src/main/java/bisq/cli/DirectionFormat.java +++ b/cli/src/main/java/bisq/cli/DirectionFormat.java @@ -24,8 +24,8 @@ 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 9b2195bf20d..4372577a1a5 100644 --- a/cli/src/main/java/bisq/cli/GrpcClient.java +++ b/cli/src/main/java/bisq/cli/GrpcClient.java @@ -20,12 +20,17 @@ 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 TxInfo getTransaction(String txId) { 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 void cancelOffer(String offerId) { 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 List getBsqOffersSortedByDate() { 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 List getMyBsqOffersSortedByDate() { 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 e0b08ed7713..499efba7bdc 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 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 @@ protected AbstractMethodOptionParser(String[] args) { 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 a37a9f109bb..000d327d787 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 @@ 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 String getAddress() { 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 70dda3e6fc3..e31bf744cde 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 215c4f3e80d..2df00bc2ee1 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.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 void cancelOffer(String offerId) { 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 OfferInfo getOffer(String offerId) { 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 OfferInfo getMyOffer(String offerId) { 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 List getOffersSortedByDate(String direction, String currencyCo 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 List getBsqOffersSortedByDate() { 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 List getMyBsqOffersSortedByDate() { 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 6d57bb03547..33a68c344af 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 TradeInfo takeOffer(String offerId, String paymentAccountId, String taker 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 1c9aebd8898..9227e1f6faf 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/app/Version.java b/common/src/main/java/bisq/common/app/Version.java index a6b126caf54..2a8630b1a70 100644 --- a/common/src/main/java/bisq/common/app/Version.java +++ b/common/src/main/java/bisq/common/app/Version.java @@ -30,7 +30,7 @@ public class Version { // VERSION = 0.5.0 introduces proto buffer for the P2P network and local DB and is a not backward compatible update // Therefore all sub versions start again with 1 // We use semantic versioning with major, minor and patch - public static final String VERSION = "1.7.4"; + public static final String VERSION = "1.7.5"; /** * Holds a list of the tagged resource files for optimizing the getData requests. diff --git a/common/src/main/java/bisq/common/config/ConfigFileEditor.java b/common/src/main/java/bisq/common/config/ConfigFileEditor.java index f067430165d..38054cb39e1 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 void clearOption(String name) { 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 00000000000..80e6f929594 --- /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 00000000000..8f88e67a289 --- /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 00000000000..c94e4e5c1ff --- /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 eb34b10bc14..03b29aa35e1 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 @@ 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 @@ @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 static String getPathOfCodeSource() throws URISyntaxException { 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 00000000000..115483d25fb --- /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 00000000000..a4bab784b99 --- /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 85d35e19af1..835b0dd08d8 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.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.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 Optional findWitness(Offer offer) { } 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 @@ private long getTradeLimit(Coin maxTradeLimit, 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 long getMyAccountAge(PaymentAccountPayload paymentAccountPayload) { 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; @@ -641,13 +641,12 @@ private boolean verifyPeersTradeLimit(Offer offer, 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()); @@ -732,8 +731,8 @@ public void arbitratorSignAccountAgeWitness(AccountAgeWitness accountAgeWitness, 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(); + 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"); 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 628f0a81862..bc3b03ac7e1 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 @@ 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; diff --git a/core/src/main/java/bisq/core/api/CoreApi.java b/core/src/main/java/bisq/core/api/CoreApi.java index 2fb5c39c5a0..53a43806a82 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.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 String getMethodHelp(String methodName) { // Offers /////////////////////////////////////////////////////////////////////////////////////////// + public Offer getBsqSwapOffer(String id) { + return coreOffersService.getBsqSwapOffer(id); + } + public Offer getOffer(String id) { return coreOffersService.getOffer(id); } @@ -125,6 +131,14 @@ public OpenOffer getMyOffer(String id) { return coreOffersService.getMyOffer(id); } + public Offer getMyBsqSwapOffer(String id) { + return coreOffersService.getMyBsqSwapOffer(id); + } + + public List getBsqSwapOffers(String direction, String currencyCode) { + return coreOffersService.getBsqSwapOffers(direction, currencyCode); + } + public List getOffers(String direction, String currencyCode) { return coreOffersService.getOffers(direction, currencyCode); } @@ -133,18 +147,38 @@ public List getMyOffers(String direction, String currencyCode) { 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, String currencyCode) { + return coreOffersService.getMyBsqSwapOffers(direction, currencyCode); + } + + 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 String getPaymentAccountForm(String paymentMethodId) { 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 void getMarketPrice(String currencyCode, Consumer resultHandler) // 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 void withdrawFunds(String tradeId, String address, String memo) { 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 4649f4f0b31..99fc1871a27 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 @@ 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.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 @@ public CoreOffersService(CoreContext coreContext, CoreWalletsService coreWalletsService, CreateOfferService createOfferService, OfferBookService offerBookService, - OfferFilter offerFilter, + OfferFilterService offerFilterService, OpenOfferManager openOfferManager, + OpenBsqSwapOfferService openBsqSwapOfferService, OfferUtil offerUtil, PriceFeedService priceFeedService, User user) { @@ -107,18 +110,29 @@ public CoreOffersService(CoreContext coreContext, 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 @@ OpenOffer getMyOffer(String id) { 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, String currencyCode) { + var offers = offerBookService.getOffers().stream() + .filter(o -> !o.isMyOffer(keyRing)) + .filter(o -> offerMatchesDirectionAndCurrency(o, direction, currencyCode)) + .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 @@ List getMyOffers(String direction, String currencyCode) { .collect(Collectors.toList()); } + List getMyBsqSwapOffers(String direction, String currencyCode) { + var offers = offerBookService.getOffers().stream() + .filter(o -> o.isMyOffer(keyRing)) + .filter(o -> offerMatchesDirectionAndCurrency(o, direction, currencyCode)) + .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 @@ boolean isMyOffer(String id) { .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 @@ void createAndPlaceOffer(String currencyCode, 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 @@ void editOffer(String offerId, 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 @@ void cancelOffer(String id) { 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 @@ private OfferPayload getMergedOfferPayload(OpenOffer openOffer, // 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 @@ private OfferPayload getMergedOfferPayload(OpenOffer openOffer, 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 @@ private OfferPayload getMergedOfferPayload(OpenOffer openOffer, 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 @@ private boolean offerMatchesDirectionAndCurrency(Offer offer, 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 416dde6531b..dcce34a46b3 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 @@ 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 @@ Set getPaymentAccounts() { 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 @@ File getPaymentAccountForm(String paymentMethodId) { 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 @@ PaymentAccount createCryptoCurrencyPaymentAccount(String accountName, // 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 @@ PaymentAccount createCryptoCurrencyPaymentAccount(String accountName, 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 2f5683108a9..a941230159a 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.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.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.ClosedTradableManager; +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 @@ public CoreTradesService(CoreContext coreContext, OfferUtil offerUtil, ClosedTradableManager closedTradableManager, TakeOfferModel takeOfferModel, + BsqSwapTakeOfferModel bsqSwapTakeOfferModel, TradeManager tradeManager, TradeUtil tradeUtil, User user) { @@ -80,11 +85,33 @@ public CoreTradesService(CoreContext coreContext, 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 @@ void takeOffer(Offer offer, 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 @@ void withdrawFunds(String tradeId, String toAddress, String memo) { }); } + 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 54f841e125c..f2dec09a530 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 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 @@ private BsqBalanceInfo getBsqBalances() { 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 23324e21f33..cde41915c25 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 BsqBalanceInfo(long availableConfirmedBalance, } @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 static BsqBalanceInfo valueOf(long availableConfirmedBalance, // 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 static BsqBalanceInfo fromProto(bisq.proto.grpc.BsqBalanceInfo proto) { @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 00000000000..487e4fbf23f --- /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 00000000000..c24dcca0163 --- /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 15ad2acc108..67cee988a3b 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 @@ private static OfferInfoBuilder getOfferInfoBuilder(Offer offer, boolean isMyOff .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 38bf15256d2..880c9a5e76e 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 @@ @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. @@ -138,8 +138,8 @@ public static TradeInfo toTradeInfo(Trade trade, String role, boolean isMyOffer) .withTradeVolume(trade.getTradeVolume() == null ? 0 : trade.getTradeVolume().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 5efcce3810d..9e14df37f9e 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.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 void gracefulShutDown(ResultHandler resultHandler) { } 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 541fe069da5..02713b726c9 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.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 @@ private void checkForInvalidMakerFeeTxs() { // 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 @@ private void maybeShowSecurityRecommendation() { } 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 b0dbd16b774..e401e24465a 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.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; @@ -49,8 +50,9 @@ import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; import bisq.core.support.traderchat.TraderChatManager; import bisq.core.trade.TradeManager; -import bisq.core.trade.closed.ClosedTradableManager; -import bisq.core.trade.failed.FailedTradesManager; +import bisq.core.trade.bisq_v1.ClosedTradableManager; +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 DomainInitialisation(ClockWatcher clockWatcher, TraderChatManager traderChatManager, TradeManager tradeManager, ClosedTradableManager closedTradableManager, + BsqSwapTradeManager bsqSwapTradeManager, FailedTradesManager failedTradesManager, XmrTxProofService xmrTxProofService, OpenOfferManager openOfferManager, @@ -149,7 +154,8 @@ public DomainInitialisation(ClockWatcher clockWatcher, 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 DomainInitialisation(ClockWatcher clockWatcher, 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 DomainInitialisation(ClockWatcher clockWatcher, this.daoStateSnapshotService = daoStateSnapshotService; this.triggerPriceService = triggerPriceService; this.mempoolService = mempoolService; + this.openBsqSwapOfferService = openBsqSwapOfferService; } public void initDomainServices(Consumer rejectedTxErrorMessageHandler, @@ -210,10 +218,12 @@ public void initDomainServices(Consumer rejectedTxErrorMessageHandler, 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 2643a389dc4..44ff2ece1ce 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.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 void onRequestCustomBridges() { 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/AppSetupWithP2PAndDAO.java b/core/src/main/java/bisq/core/app/misc/AppSetupWithP2PAndDAO.java index 95751832c9d..38ce8b7c383 100644 --- a/core/src/main/java/bisq/core/app/misc/AppSetupWithP2PAndDAO.java +++ b/core/src/main/java/bisq/core/app/misc/AppSetupWithP2PAndDAO.java @@ -28,6 +28,7 @@ import bisq.core.dao.governance.proposal.MyProposalListService; import bisq.core.filter.FilterManager; import bisq.core.trade.statistics.TradeStatisticsManager; +import bisq.core.user.Preferences; import bisq.network.p2p.P2PService; import bisq.network.p2p.peers.PeerManager; @@ -42,6 +43,7 @@ @Slf4j public class AppSetupWithP2PAndDAO extends AppSetupWithP2P { private final DaoSetup daoSetup; + private final Preferences preferences; @Inject public AppSetupWithP2PAndDAO(P2PService p2PService, @@ -58,6 +60,7 @@ public AppSetupWithP2PAndDAO(P2PService p2PService, MyProposalListService myProposalListService, MyReputationListService myReputationListService, MyProofOfBurnListService myProofOfBurnListService, + Preferences preferences, Config config) { super(p2PService, p2PDataStorage, @@ -69,6 +72,7 @@ public AppSetupWithP2PAndDAO(P2PService p2PService, config); this.daoSetup = daoSetup; + this.preferences = preferences; // TODO Should be refactored/removed. In the meantime keep in sync with CorePersistedDataHost if (config.daoActivated) { @@ -86,5 +90,8 @@ protected void onBasicServicesInitialized() { super.onBasicServicesInitialized(); daoSetup.onAllServicesInitialized(log::error, log::warn); + + // For seed nodes we need to set default value to true + preferences.setUseDaoMonitor(true); } } 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 ccca2a6a0c6..ec9bfd65b27 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.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 void gracefulShutDown(ResultHandler resultHandler) { 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 5d3ddd4e437..ff6ece544c4 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.OpenOfferManager; import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.refund.RefundManager; -import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; -import bisq.core.trade.closed.ClosedTradableManager; -import bisq.core.trade.failed.FailedTradesManager; +import bisq.core.trade.bisq_v1.ClosedTradableManager; +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 1cf1d07cd47..049b4a4d5f0 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 7923db370c5..fa6c5a1af6c 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 @@ 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 6677555e811..6eaa1b4b5da 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 @@ 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 String getTxID() { - return txID; + public TxConfidenceListener(String txId) { + this.txId = 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 3a59ab07f9c..0b2d74de36e 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 protobuf.RawTransactionInput toProtoMessage() { .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 String toString() { "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 82851886322..1f4d2652a77 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 List getProvidedBtcNodes() { 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 List getProvidedBtcNodes() { 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 e6d122a6b80..2d893a54bd8 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 javax.inject.Inject; +import lombok.Getter; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; /** @@ -36,9 +38,13 @@ 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 @@ protected boolean isTxOutputSpendable(TransactionOutput output) { 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 5639004c70f..29b9282589a 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 BsqTransferModel getBsqTransferModel(Address address, 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 708c5044678..ad28df8be50 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.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.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 interface WalletTransactionsChangeListener { 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 BsqWalletService(WalletsSetup walletsSetup, UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService, Preferences preferences, FeeService feeService, - DaoKillSwitch daoKillSwitch) { + DaoKillSwitch daoKillSwitch, + BsqFormatter bsqFormatter) { super(walletsSetup, preferences, feeService); @@ -135,6 +144,7 @@ public BsqWalletService(WalletsSetup walletsSetup, this.daoStateService = daoStateService; this.unconfirmedBsqChangeOutputListService = unconfirmedBsqChangeOutputListService; this.daoKillSwitch = daoKillSwitch; + this.bsqFormatter = bsqFormatter; nonBsqCoinSelector.setPreferences(preferences); @@ -284,18 +294,20 @@ private void updateBsqBalance() { .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 Optional isWalletTransaction(String txId) { // 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 Transaction getPreparedSendBsqTx(String receiverAddress, 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 @@ private void addInputsAndChangeOutputForTx(Transaction tx, } + /////////////////////////////////////////////////////////////////////////////////////////// + // 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 Transaction getPreparedLockupTx(Coin lockupAmount) throws AddressFormatEx 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 bef6378b794..2437b2b8f3d 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.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.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 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 Transaction createRefundPayoutTx(Coin buyerAmount, 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 94d6e75cf36..2df405287da 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 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 Transaction sellerSignsAndFinalizesPayoutTx(Transaction depositTx, 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 Transaction finalizeMediatedPayoutTx(Transaction depositTx, 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 Transaction traderSignAndFinalizeDisputedPayoutTx(byte[] depositTxSeriali 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 Transaction traderSignAndFinalizeDisputedPayoutTx(byte[] depositTxSeriali /////////////////////////////////////////////////////////////////////////////////////////// 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 Tuple2 emergencyBuildPayoutTxFrom2of2MultiSig(String depo 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 String emergencyGenerateSignature(String rawTxHex, String redeemScriptHex } 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 Tuple2 emergencyApplySignatureToPayoutTxFrom2of2MultiSig( 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 void emergencyPublishPayoutTxFrom2of2MultiSig(String signedTxHex, TxBroad 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 Transaction getClonedTransaction(Transaction tx) { // 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 @@ private RawTransactionInput getRawInputFromTransactionInput(@NotNull Transaction 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 boolean isP2WH(RawTransactionInput rawTransactionInput) { 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 @@ private Transaction createPayoutTx(Transaction depositTx, 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 0b893e470a8..93708c79c09 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 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 static void checkScriptSig(Transaction transaction, // 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 static void signTransactionInput(Wallet wallet, 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 static void signTransactionInput(Wallet wallet, } + /////////////////////////////////////////////////////////////////////////////////////////// + // 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 @@ private TransactionConfidence getMostRecentConfidence(List 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 +908,7 @@ void notifyBalanceListeners(Transaction tx) { 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/DaoEventCoordinator.java b/core/src/main/java/bisq/core/dao/DaoEventCoordinator.java deleted file mode 100644 index 4c8cc0f4098..00000000000 --- a/core/src/main/java/bisq/core/dao/DaoEventCoordinator.java +++ /dev/null @@ -1,69 +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.dao; - -import bisq.core.dao.monitoring.DaoStateMonitoringService; -import bisq.core.dao.state.DaoStateListener; -import bisq.core.dao.state.DaoStateService; -import bisq.core.dao.state.DaoStateSnapshotService; -import bisq.core.dao.state.model.blockchain.Block; - -import javax.inject.Inject; - -public class DaoEventCoordinator implements DaoSetupService, DaoStateListener { - private final DaoStateService daoStateService; - private final DaoStateSnapshotService daoStateSnapshotService; - private final DaoStateMonitoringService daoStateMonitoringService; - - @Inject - public DaoEventCoordinator(DaoStateService daoStateService, - DaoStateSnapshotService daoStateSnapshotService, - DaoStateMonitoringService daoStateMonitoringService) { - this.daoStateService = daoStateService; - this.daoStateSnapshotService = daoStateSnapshotService; - this.daoStateMonitoringService = daoStateMonitoringService; - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // DaoSetupService - /////////////////////////////////////////////////////////////////////////////////////////// - - @Override - public void addListeners() { - this.daoStateService.addDaoStateListener(this); - } - - @Override - public void start() { - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // DaoStateListener - /////////////////////////////////////////////////////////////////////////////////////////// - - // We listen onDaoStateChanged to ensure the dao state has been processed from listener clients after parsing. - // We need to listen during batch processing as well to write snapshots during that process. - @Override - public void onDaoStateChanged(Block block) { - // We need to execute first the daoStateMonitoringService - daoStateMonitoringService.createHashFromBlock(block); - daoStateSnapshotService.maybeCreateSnapshot(block); - } -} diff --git a/core/src/main/java/bisq/core/dao/DaoFacade.java b/core/src/main/java/bisq/core/dao/DaoFacade.java index 8adfe466a26..2d1ac71601c 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.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 Set getUnspentTxOutputs() { 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 Set getAllDonationAddresses() { return allPastParamValues; } + + public boolean isParseBlockChainComplete() { + return daoStateService.isParseBlockChainComplete(); + } } diff --git a/core/src/main/java/bisq/core/dao/DaoModule.java b/core/src/main/java/bisq/core/dao/DaoModule.java index 87d5f9e7a00..ed2f0efef3f 100644 --- a/core/src/main/java/bisq/core/dao/DaoModule.java +++ b/core/src/main/java/bisq/core/dao/DaoModule.java @@ -102,7 +102,6 @@ public DaoModule(Config config) { protected void configure() { bind(DaoSetup.class).in(Singleton.class); bind(DaoFacade.class).in(Singleton.class); - bind(DaoEventCoordinator.class).in(Singleton.class); bind(DaoKillSwitch.class).in(Singleton.class); // Node, parser diff --git a/core/src/main/java/bisq/core/dao/DaoSetup.java b/core/src/main/java/bisq/core/dao/DaoSetup.java index b8c13582f77..728f94108af 100644 --- a/core/src/main/java/bisq/core/dao/DaoSetup.java +++ b/core/src/main/java/bisq/core/dao/DaoSetup.java @@ -39,6 +39,7 @@ import bisq.core.dao.node.BsqNodeProvider; import bisq.core.dao.node.explorer.ExportJsonFilesService; import bisq.core.dao.state.DaoStateService; +import bisq.core.dao.state.DaoStateSnapshotService; import com.google.inject.Inject; @@ -78,16 +79,11 @@ public DaoSetup(BsqNodeProvider bsqNodeProvider, DaoStateMonitoringService daoStateMonitoringService, ProposalStateMonitoringService proposalStateMonitoringService, BlindVoteStateMonitoringService blindVoteStateMonitoringService, - DaoEventCoordinator daoEventCoordinator) { + DaoStateSnapshotService daoStateSnapshotService) { bsqNode = bsqNodeProvider.getBsqNode(); // We need to take care of order of execution. - - // For order critical event flow we use the daoEventCoordinator to delegate the calls from anonymous listeners - // to concrete clients. - daoSetupServices.add(daoEventCoordinator); - daoSetupServices.add(daoStateService); daoSetupServices.add(cycleService); daoSetupServices.add(ballotListService); @@ -110,6 +106,7 @@ public DaoSetup(BsqNodeProvider bsqNodeProvider, daoSetupServices.add(daoStateMonitoringService); daoSetupServices.add(proposalStateMonitoringService); daoSetupServices.add(blindVoteStateMonitoringService); + daoSetupServices.add(daoStateSnapshotService); daoSetupServices.add(bsqNodeProvider.getBsqNode()); } 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 0a6843a6447..6db225836cf 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 Transaction payFee(StatefulAsset statefulAsset, // 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 d0edacc099f..c0eb65001cc 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 @@ private Transaction getBlindVoteTx(Coin stake, Coin fee, byte[] opReturnData) 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 901793c74de..eb8772b0ec3 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 @@ private Transaction getLockupTx(Coin lockupAmount, int lockTime, LockupReason lo 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 a4c6691cf67..2afa7b06640 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 @@ private Transaction getUnlockTx(String lockupTxId) 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 47125ad1552..febcb8b4495 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 Transaction burn(String preImageAsString, long amount) throws Insufficien // 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 53c2967feb6..6c634a6c133 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 @@ private Transaction createTransaction(R proposal) throws InsufficientMoneyExcept 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 94b4f0b9a67..b02c03ba370 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 @@ private Transaction getVoteRevealTx(TxOutput stakeTxOutput, byte[] opReturnData) 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/monitoring/BlindVoteStateMonitoringService.java b/core/src/main/java/bisq/core/dao/monitoring/BlindVoteStateMonitoringService.java index a3a51629945..f803060a53a 100644 --- a/core/src/main/java/bisq/core/dao/monitoring/BlindVoteStateMonitoringService.java +++ b/core/src/main/java/bisq/core/dao/monitoring/BlindVoteStateMonitoringService.java @@ -274,7 +274,7 @@ private boolean maybeUpdateHashChain(int blockHeight) { byte[] combined = ArrayUtils.addAll(prevHash, serializedBlindVotes); byte[] hash = Hash.getSha256Ripemd160hash(combined); - BlindVoteStateHash myBlindVoteStateHash = new BlindVoteStateHash(blockHeight, hash, prevHash, blindVotes.size()); + BlindVoteStateHash myBlindVoteStateHash = new BlindVoteStateHash(blockHeight, hash, blindVotes.size()); BlindVoteStateBlock blindVoteStateBlock = new BlindVoteStateBlock(myBlindVoteStateHash); blindVoteStateBlockChain.add(blindVoteStateBlock); blindVoteStateHashChain.add(myBlindVoteStateHash); diff --git a/core/src/main/java/bisq/core/dao/monitoring/DaoStateMonitoringService.java b/core/src/main/java/bisq/core/dao/monitoring/DaoStateMonitoringService.java index 35c7ebad0bd..637e34165dd 100644 --- a/core/src/main/java/bisq/core/dao/monitoring/DaoStateMonitoringService.java +++ b/core/src/main/java/bisq/core/dao/monitoring/DaoStateMonitoringService.java @@ -31,6 +31,7 @@ import bisq.core.dao.state.model.blockchain.BaseTxOutput; import bisq.core.dao.state.model.blockchain.Block; import bisq.core.dao.state.model.governance.IssuanceType; +import bisq.core.user.Preferences; import bisq.network.p2p.NodeAddress; import bisq.network.p2p.network.Connection; @@ -40,7 +41,6 @@ import bisq.common.config.Config; import bisq.common.crypto.Hash; import bisq.common.file.FileUtil; -import bisq.common.util.GcUtil; import bisq.common.util.Utilities; import javax.inject.Inject; @@ -60,13 +60,12 @@ import java.util.Random; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import static com.google.common.base.Preconditions.checkArgument; +import javax.annotation.Nullable; /** * Monitors the DaoState by using a hash for the complete daoState and make it accessible to the network @@ -88,7 +87,7 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe DaoStateNetworkService.Listener { public interface Listener { - void onChangeAfterBatchProcessing(); + void onDaoStateHashesChanged(); void onCheckpointFail(); } @@ -110,6 +109,8 @@ public interface Listener { @Getter private boolean isInConflictWithSeedNode; @Getter + private boolean daoStateBlockChainNotConnecting; + @Getter private final ObservableList utxoMismatches = FXCollections.observableArrayList(); private final List checkpoints = Arrays.asList( @@ -120,7 +121,11 @@ public interface Listener { private int numCalls; private long accumulatedDuration; + private final Preferences preferences; private final File storageDir; + @Nullable + private Runnable createSnapshotHandler; + /////////////////////////////////////////////////////////////////////////////////////////// // Constructor @@ -131,11 +136,13 @@ public DaoStateMonitoringService(DaoStateService daoStateService, DaoStateNetworkService daoStateNetworkService, GenesisTxInfo genesisTxInfo, SeedNodeRepository seedNodeRepository, + Preferences preferences, @Named(Config.STORAGE_DIR) File storageDir, @Named(Config.IGNORE_DEV_MSG) boolean ignoreDevMsg) { this.daoStateService = daoStateService; this.daoStateNetworkService = daoStateNetworkService; this.genesisTxInfo = genesisTxInfo; + this.preferences = preferences; this.storageDir = storageDir; this.ignoreDevMsg = ignoreDevMsg; seedNodeAddresses = seedNodeRepository.getSeedNodeAddresses().stream() @@ -163,16 +170,19 @@ public void start() { // DaoStateListener /////////////////////////////////////////////////////////////////////////////////////////// - // We do not use onDaoStateChanged but let the DaoEventCoordinator call createHashFromBlock to ensure the - // correct order of execution. - @Override public void onParseBlockChainComplete() { parseBlockChainComplete = true; + daoStateService.getLastBlock().ifPresent(this::checkUtxos); + daoStateNetworkService.addListeners(); - // We wait for processing messages until we have completed batch processing - int fromHeight = daoStateService.getChainHeight() - 10; + // We take either the height of the previous hashBlock we have or 10 blocks below the chain tip. + int nextBlockHeight = daoStateBlockChain.isEmpty() ? + genesisTxInfo.getGenesisBlockHeight() : + daoStateBlockChain.getLast().getHeight() + 1; + int past10 = daoStateService.getChainHeight() - 10; + int fromHeight = Math.min(nextBlockHeight, past10); daoStateNetworkService.requestHashesFromAllConnectedSeedNodes(fromHeight); if (!ignoreDevMsg) { @@ -188,16 +198,9 @@ public void onParseBlockChainComplete() { @Override public void onDaoStateChanged(Block block) { - long genesisTotalSupply = daoStateService.getGenesisTotalSupply().value; - long compensationIssuance = daoStateService.getTotalIssuedAmount(IssuanceType.COMPENSATION); - long reimbursementIssuance = daoStateService.getTotalIssuedAmount(IssuanceType.REIMBURSEMENT); - long totalAmountOfBurntBsq = daoStateService.getTotalAmountOfBurntBsq(); - // confiscated funds are still in the utxo set - long sumUtxo = daoStateService.getUnspentTxOutputMap().values().stream().mapToLong(BaseTxOutput::getValue).sum(); - long sumBsq = genesisTotalSupply + compensationIssuance + reimbursementIssuance - totalAmountOfBurntBsq; - - if (sumBsq != sumUtxo) { - utxoMismatches.add(new UtxoMismatch(block.getHeight(), sumUtxo, sumBsq)); + // During syncing we do not call checkUtxos as its a bit slow (about 4 ms) + if (parseBlockChainComplete) { + checkUtxos(block); } } @@ -208,8 +211,24 @@ public void onDaoStateChanged(Block block) { @Override public void onNewStateHashMessage(NewDaoStateHashMessage newStateHashMessage, Connection connection) { - if (newStateHashMessage.getStateHash().getHeight() <= daoStateService.getChainHeight()) { - processPeersDaoStateHash(newStateHashMessage.getStateHash(), connection.getPeersNodeAddressOptional(), true); + // Called when receiving NewDaoStateHashMessages from peers after a new block + DaoStateHash peersDaoStateHash = newStateHashMessage.getStateHash(); + if (peersDaoStateHash.getHeight() <= daoStateService.getChainHeight()) { + putInPeersMapAndCheckForConflicts(getPeersAddress(connection.getPeersNodeAddressOptional()), peersDaoStateHash); + listeners.forEach(Listener::onDaoStateHashesChanged); + } + } + + @Override + public void onPeersStateHashes(List stateHashes, Optional peersNodeAddress) { + // Called when receiving GetDaoStateHashesResponse from seed nodes + processPeersDaoStateHashes(stateHashes, peersNodeAddress); + listeners.forEach(Listener::onDaoStateHashesChanged); + if (createSnapshotHandler != null) { + createSnapshotHandler.run(); + // As we get called multiple times from hashes of diff. seed nodes we want to avoid to + // call our handler multiple times. + createSnapshotHandler = null; } } @@ -223,29 +242,17 @@ public void onGetStateHashRequest(Connection connection, GetDaoStateHashesReques daoStateNetworkService.sendGetStateHashesResponse(connection, getStateHashRequest.getNonce(), daoStateHashes); } - @Override - public void onPeersStateHashes(List stateHashes, Optional peersNodeAddress) { - AtomicBoolean hasChanged = new AtomicBoolean(false); - - stateHashes.forEach(daoStateHash -> { - boolean changed = processPeersDaoStateHash(daoStateHash, peersNodeAddress, false); - if (changed) { - hasChanged.set(true); - } - }); - - if (hasChanged.get()) { - listeners.forEach(Listener::onChangeAfterBatchProcessing); - } - } - /////////////////////////////////////////////////////////////////////////////////////////// // API /////////////////////////////////////////////////////////////////////////////////////////// public void createHashFromBlock(Block block) { - updateHashChain(block); + createDaoStateBlock(block); + if (parseBlockChainComplete) { + // We notify listeners only after batch processing to avoid performance issues at UI code + listeners.forEach(Listener::onDaoStateHashesChanged); + } } public void requestHashesFromGenesisBlockHeight(String peersAddress) { @@ -263,7 +270,11 @@ public void applySnapshot(LinkedList persistedDaoStateHashChain) { persistedDaoStateHashChain.size(), persistedDaoStateHashChain.getLast()); } daoStateHashChain.addAll(persistedDaoStateHashChain); - daoStateHashChain.forEach(e -> daoStateBlockChain.add(new DaoStateBlock(e))); + daoStateHashChain.forEach(daoStateHash -> daoStateBlockChain.add(new DaoStateBlock(daoStateHash))); + } + + public void setCreateSnapshotHandler(Runnable handler) { + createSnapshotHandler = handler; } @@ -284,7 +295,7 @@ public void removeListener(Listener listener) { // Private /////////////////////////////////////////////////////////////////////////////////////////// - private void updateHashChain(Block block) { + private void createDaoStateBlock(Block block) { long ts = System.currentTimeMillis(); byte[] prevHash; int height = block.getHeight(); @@ -295,34 +306,43 @@ private void updateHashChain(Block block) { } else { log.warn("DaoStateBlockchain is empty but we received the block which was not the genesis block. " + "We stop execution here."); + daoStateBlockChainNotConnecting = true; + listeners.forEach(Listener::onDaoStateHashesChanged); return; } } else { - checkArgument(height == daoStateBlockChain.getLast().getHeight() + 1, - "New block must be 1 block above previous block. height={}, " + - "daoStateBlockChain.getLast().getHeight()={}", - height, daoStateBlockChain.getLast().getHeight()); - prevHash = daoStateBlockChain.getLast().getHash(); + int heightOfLastBlock = daoStateBlockChain.getLast().getHeight(); + if (height == heightOfLastBlock + 1) { + prevHash = daoStateBlockChain.getLast().getHash(); + } else { + log.warn("New block must be 1 block above previous block. height={}, " + + "daoStateBlockChain.getLast().getHeight()={}", + height, heightOfLastBlock); + daoStateBlockChainNotConnecting = true; + listeners.forEach(Listener::onDaoStateHashesChanged); + return; + } } + byte[] stateAsBytes = daoStateService.getSerializedStateForHashChain(); // We include the prev. hash in our new hash so we can be sure that if one hash is matching all the past would // match as well. byte[] combined = ArrayUtils.addAll(prevHash, stateAsBytes); byte[] hash = Hash.getSha256Ripemd160hash(combined); - DaoStateHash myDaoStateHash = new DaoStateHash(height, hash, prevHash); + DaoStateHash myDaoStateHash = new DaoStateHash(height, hash, true); DaoStateBlock daoStateBlock = new DaoStateBlock(myDaoStateHash); daoStateBlockChain.add(daoStateBlock); daoStateHashChain.add(myDaoStateHash); // We only broadcast after parsing of blockchain is complete if (parseBlockChainComplete) { - // We notify listeners only after batch processing to avoid performance issues at UI code - listeners.forEach(Listener::onChangeAfterBatchProcessing); - // We delay broadcast to give peers enough time to have received the block. // Otherwise they would ignore our data if received block is in future to their local blockchain. int delayInSec = 5 + new Random().nextInt(10); + if (Config.baseCurrencyNetwork().isRegtest()) { + delayInSec = 1; + } UserThread.runAfter(() -> daoStateNetworkService.broadcastMyStateHash(myDaoStateHash), delayInSec); } long duration = System.currentTimeMillis() - ts; @@ -334,57 +354,75 @@ private void updateHashChain(Block block) { numCalls++; } - private boolean processPeersDaoStateHash(DaoStateHash daoStateHash, - Optional peersNodeAddress, - boolean notifyListeners) { - GcUtil.maybeReleaseMemory(); - - AtomicBoolean changed = new AtomicBoolean(false); - AtomicBoolean inConflictWithNonSeedNode = new AtomicBoolean(this.isInConflictWithNonSeedNode); - AtomicBoolean inConflictWithSeedNode = new AtomicBoolean(this.isInConflictWithSeedNode); - StringBuilder sb = new StringBuilder(); - daoStateBlockChain.stream() - .filter(e -> e.getHeight() == daoStateHash.getHeight()).findAny() - .ifPresent(daoStateBlock -> { - String peersNodeAddressAsString = peersNodeAddress.map(NodeAddress::getFullAddress) - .orElseGet(() -> "Unknown peer " + new Random().nextInt(10000)); - daoStateBlock.putInPeersMap(peersNodeAddressAsString, daoStateHash); - if (!daoStateBlock.getMyStateHash().hasEqualHash(daoStateHash)) { - daoStateBlock.putInConflictMap(peersNodeAddressAsString, daoStateHash); - if (seedNodeAddresses.contains(peersNodeAddressAsString)) { - inConflictWithSeedNode.set(true); - } else { - inConflictWithNonSeedNode.set(true); - } - sb.append("We received a block hash from peer ") - .append(peersNodeAddressAsString) - .append(" which conflicts with our block hash.\n") - .append("my daoStateHash=") - .append(daoStateBlock.getMyStateHash()) - .append("\npeers daoStateHash=") - .append(daoStateHash); - } - changed.set(true); - }); - - this.isInConflictWithNonSeedNode = inConflictWithNonSeedNode.get(); - this.isInConflictWithSeedNode = inConflictWithSeedNode.get(); - - String conflictMsg = sb.toString(); - if (!conflictMsg.isEmpty()) { - if (this.isInConflictWithSeedNode) - log.warn("Conflict with seed nodes: {}", conflictMsg); - else if (this.isInConflictWithNonSeedNode) - log.debug("Conflict with non-seed nodes: {}", conflictMsg); + private void processPeersDaoStateHashes(List stateHashes, Optional peersNodeAddress) { + stateHashes.forEach(peersHash -> { + // If we do not add own hashes during initial parsing we fill the missing hashes from the peer and create + // the at the last block our own hash. + if (!preferences.isUseDaoMonitor() && + !findDaoStateBlock(peersHash.getHeight()).isPresent()) { + if (isLastBlock(peersHash)) { + // At the most recent block we create out own hash + daoStateService.getLastBlock().ifPresent(this::createDaoStateBlock); + } else { + // Otherwise we create a block from the peers daoStateHash + DaoStateHash daoStateHash = new DaoStateHash(peersHash.getHeight(), peersHash.getHash(), false); + DaoStateBlock daoStateBlock = new DaoStateBlock(daoStateHash); + daoStateBlockChain.add(daoStateBlock); + daoStateHashChain.add(daoStateHash); + } + } + + // In any case we add the peer to our peersMap and check for conflicts on the relevant daoStateBlock + putInPeersMapAndCheckForConflicts(getPeersAddress(peersNodeAddress), peersHash); + }); + } + + private void putInPeersMapAndCheckForConflicts(String peersAddress, DaoStateHash peersHash) { + findDaoStateBlock(peersHash.getHeight()).ifPresent(daoStateBlock -> { + daoStateBlock.putInPeersMap(peersAddress, peersHash); + checkForHashConflicts(peersHash, peersAddress, daoStateBlock); + }); + } + + private void checkForHashConflicts(DaoStateHash peersDaoStateHash, + String peersNodeAddress, + DaoStateBlock daoStateBlock) { + if (daoStateBlock.getMyStateHash().hasEqualHash(peersDaoStateHash)) { + return; } - if (notifyListeners && changed.get()) { - listeners.forEach(Listener::onChangeAfterBatchProcessing); + daoStateBlock.putInConflictMap(peersNodeAddress, peersDaoStateHash); + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("We received a block hash from peer ") + .append(peersNodeAddress) + .append(" which conflicts with our block hash.\n") + .append("my peersDaoStateHash=") + .append(daoStateBlock.getMyStateHash()) + .append("\npeers peersDaoStateHash=") + .append(peersDaoStateHash); + String conflictMsg = stringBuilder.toString(); + + if (isSeedNode(peersNodeAddress)) { + isInConflictWithSeedNode = true; + log.warn("Conflict with seed nodes: {}", conflictMsg); + } else { + isInConflictWithNonSeedNode = true; + log.debug("Conflict with non-seed nodes: {}", conflictMsg); } + } - GcUtil.maybeReleaseMemory(); + private void checkUtxos(Block block) { + long genesisTotalSupply = daoStateService.getGenesisTotalSupply().value; + long compensationIssuance = daoStateService.getTotalIssuedAmount(IssuanceType.COMPENSATION); + long reimbursementIssuance = daoStateService.getTotalIssuedAmount(IssuanceType.REIMBURSEMENT); + long totalAmountOfBurntBsq = daoStateService.getTotalAmountOfBurntBsq(); + // confiscated funds are still in the utxo set + long sumUtxo = daoStateService.getUnspentTxOutputMap().values().stream().mapToLong(BaseTxOutput::getValue).sum(); + long sumBsq = genesisTotalSupply + compensationIssuance + reimbursementIssuance - totalAmountOfBurntBsq; - return changed.get(); + if (sumBsq != sumUtxo) { + utxoMismatches.add(new UtxoMismatch(block.getHeight(), sumUtxo, sumBsq)); + } } private void verifyCheckpoints() { @@ -431,4 +469,25 @@ private void removeFile(String storeName) { log.error(t.toString()); } } + + private boolean isLastBlock(DaoStateHash peersDaoStateHash) { + return daoStateService.getLastBlock() + .map(block -> block.getHeight() == peersDaoStateHash.getHeight()) + .orElse(false); + } + + private boolean isSeedNode(String peersNodeAddress) { + return seedNodeAddresses.contains(peersNodeAddress); + } + + private String getPeersAddress(Optional peersNodeAddress) { + return peersNodeAddress.map(NodeAddress::getFullAddress) + .orElseGet(() -> "Unknown peer " + new Random().nextInt(10000)); + } + + private Optional findDaoStateBlock(int height) { + return daoStateBlockChain.stream() + .filter(myDaoStateBlock -> myDaoStateBlock.getHeight() == height) + .findFirst(); + } } diff --git a/core/src/main/java/bisq/core/dao/monitoring/ProposalStateMonitoringService.java b/core/src/main/java/bisq/core/dao/monitoring/ProposalStateMonitoringService.java index 81d40496d8b..c28f33a2357 100644 --- a/core/src/main/java/bisq/core/dao/monitoring/ProposalStateMonitoringService.java +++ b/core/src/main/java/bisq/core/dao/monitoring/ProposalStateMonitoringService.java @@ -275,7 +275,7 @@ private boolean maybeUpdateHashChain(int blockHeight) { } byte[] combined = ArrayUtils.addAll(prevHash, serializedProposals); byte[] hash = Hash.getSha256Ripemd160hash(combined); - ProposalStateHash myProposalStateHash = new ProposalStateHash(blockHeight, hash, prevHash, proposals.size()); + ProposalStateHash myProposalStateHash = new ProposalStateHash(blockHeight, hash, proposals.size()); ProposalStateBlock proposalStateBlock = new ProposalStateBlock(myProposalStateHash); proposalStateBlockChain.add(proposalStateBlock); proposalStateHashChain.add(myProposalStateHash); diff --git a/core/src/main/java/bisq/core/dao/monitoring/model/BlindVoteStateHash.java b/core/src/main/java/bisq/core/dao/monitoring/model/BlindVoteStateHash.java index 79a59032c71..ce08528d951 100644 --- a/core/src/main/java/bisq/core/dao/monitoring/model/BlindVoteStateHash.java +++ b/core/src/main/java/bisq/core/dao/monitoring/model/BlindVoteStateHash.java @@ -29,8 +29,8 @@ public final class BlindVoteStateHash extends StateHash { @Getter private final int numBlindVotes; - public BlindVoteStateHash(int cycleStartBlockHeight, byte[] hash, byte[] prevHash, int numBlindVotes) { - super(cycleStartBlockHeight, hash, prevHash); + public BlindVoteStateHash(int cycleStartBlockHeight, byte[] hash, int numBlindVotes) { + super(cycleStartBlockHeight, hash); this.numBlindVotes = numBlindVotes; } @@ -43,14 +43,12 @@ public protobuf.BlindVoteStateHash toProtoMessage() { return protobuf.BlindVoteStateHash.newBuilder() .setHeight(height) .setHash(ByteString.copyFrom(hash)) - .setPrevHash(ByteString.copyFrom(prevHash)) .setNumBlindVotes(numBlindVotes).build(); } public static BlindVoteStateHash fromProto(protobuf.BlindVoteStateHash proto) { return new BlindVoteStateHash(proto.getHeight(), proto.getHash().toByteArray(), - proto.getPrevHash().toByteArray(), proto.getNumBlindVotes()); } diff --git a/core/src/main/java/bisq/core/dao/monitoring/model/DaoStateBlock.java b/core/src/main/java/bisq/core/dao/monitoring/model/DaoStateBlock.java index 8bd91e67fa6..9c82f410361 100644 --- a/core/src/main/java/bisq/core/dao/monitoring/model/DaoStateBlock.java +++ b/core/src/main/java/bisq/core/dao/monitoring/model/DaoStateBlock.java @@ -18,12 +18,14 @@ package bisq.core.dao.monitoring.model; import lombok.EqualsAndHashCode; -import lombok.Getter; -@Getter @EqualsAndHashCode(callSuper = true) public class DaoStateBlock extends StateBlock { public DaoStateBlock(DaoStateHash myDaoStateHash) { super(myDaoStateHash); } + + public boolean isSelfConstructed() { + return myStateHash.isSelfConstructed(); + } } diff --git a/core/src/main/java/bisq/core/dao/monitoring/model/DaoStateHash.java b/core/src/main/java/bisq/core/dao/monitoring/model/DaoStateHash.java index e0e7f5e403a..11f7f320fe3 100644 --- a/core/src/main/java/bisq/core/dao/monitoring/model/DaoStateHash.java +++ b/core/src/main/java/bisq/core/dao/monitoring/model/DaoStateHash.java @@ -21,11 +21,17 @@ import com.google.protobuf.ByteString; import lombok.EqualsAndHashCode; +import lombok.Getter; +@Getter @EqualsAndHashCode(callSuper = true) public final class DaoStateHash extends StateHash { - public DaoStateHash(int height, byte[] hash, byte[] prevHash) { - super(height, hash, prevHash); + // If we have built the hash by ourself opposed to that we got delivered the hash from seed nodes or resources + private final boolean isSelfConstructed; + + public DaoStateHash(int height, byte[] hash, boolean isSelfConstructed) { + super(height, hash); + this.isSelfConstructed = isSelfConstructed; } @@ -38,12 +44,18 @@ public protobuf.DaoStateHash toProtoMessage() { return protobuf.DaoStateHash.newBuilder() .setHeight(height) .setHash(ByteString.copyFrom(hash)) - .setPrevHash(ByteString.copyFrom(prevHash)).build(); + .setIsSelfConstructed(isSelfConstructed) + .build(); } public static DaoStateHash fromProto(protobuf.DaoStateHash proto) { - return new DaoStateHash(proto.getHeight(), - proto.getHash().toByteArray(), - proto.getPrevHash().toByteArray()); + return new DaoStateHash(proto.getHeight(), proto.getHash().toByteArray(), proto.getIsSelfConstructed()); + } + + @Override + public String toString() { + return "DaoStateHash{" + + "\r\n isSelfConstructed=" + isSelfConstructed + + "\r\n} " + super.toString(); } } diff --git a/core/src/main/java/bisq/core/dao/monitoring/model/ProposalStateHash.java b/core/src/main/java/bisq/core/dao/monitoring/model/ProposalStateHash.java index dc2ccdeabfe..995b28cefc6 100644 --- a/core/src/main/java/bisq/core/dao/monitoring/model/ProposalStateHash.java +++ b/core/src/main/java/bisq/core/dao/monitoring/model/ProposalStateHash.java @@ -29,8 +29,8 @@ public final class ProposalStateHash extends StateHash { @Getter private final int numProposals; - public ProposalStateHash(int cycleStartBlockHeight, byte[] hash, byte[] prevHash, int numProposals) { - super(cycleStartBlockHeight, hash, prevHash); + public ProposalStateHash(int cycleStartBlockHeight, byte[] hash, int numProposals) { + super(cycleStartBlockHeight, hash); this.numProposals = numProposals; } @@ -43,14 +43,12 @@ public protobuf.ProposalStateHash toProtoMessage() { return protobuf.ProposalStateHash.newBuilder() .setHeight(height) .setHash(ByteString.copyFrom(hash)) - .setPrevHash(ByteString.copyFrom(prevHash)) .setNumProposals(numProposals).build(); } public static ProposalStateHash fromProto(protobuf.ProposalStateHash proto) { return new ProposalStateHash(proto.getHeight(), proto.getHash().toByteArray(), - proto.getPrevHash().toByteArray(), proto.getNumProposals()); } diff --git a/core/src/main/java/bisq/core/dao/monitoring/model/StateBlock.java b/core/src/main/java/bisq/core/dao/monitoring/model/StateBlock.java index b41ddc2296f..8f5a27b78fa 100644 --- a/core/src/main/java/bisq/core/dao/monitoring/model/StateBlock.java +++ b/core/src/main/java/bisq/core/dao/monitoring/model/StateBlock.java @@ -56,10 +56,6 @@ public byte[] getHash() { return myStateHash.getHash(); } - public byte[] getPrevHash() { - return myStateHash.getPrevHash(); - } - @Override public String toString() { return "StateBlock{" + diff --git a/core/src/main/java/bisq/core/dao/monitoring/model/StateHash.java b/core/src/main/java/bisq/core/dao/monitoring/model/StateHash.java index 12cb299a11c..63771d83ee6 100644 --- a/core/src/main/java/bisq/core/dao/monitoring/model/StateHash.java +++ b/core/src/main/java/bisq/core/dao/monitoring/model/StateHash.java @@ -40,13 +40,10 @@ public abstract class StateHash implements PersistablePayload, NetworkPayload { protected final int height; protected final byte[] hash; - // For first block the prevHash is an empty byte array - protected final byte[] prevHash; - StateHash(int height, byte[] hash, byte[] prevHash) { + StateHash(int height, byte[] hash) { this.height = height; this.hash = hash; - this.prevHash = prevHash; } /////////////////////////////////////////////////////////////////////////////////////////// @@ -67,7 +64,6 @@ public String toString() { return "StateHash{" + "\n height=" + height + ",\n hash=" + Utilities.bytesAsHexString(hash) + - ",\n prevHash=" + Utilities.bytesAsHexString(prevHash) + "\n}"; } } diff --git a/core/src/main/java/bisq/core/dao/monitoring/network/StateNetworkService.java b/core/src/main/java/bisq/core/dao/monitoring/network/StateNetworkService.java index 8300c76b235..254307a7d7e 100644 --- a/core/src/main/java/bisq/core/dao/monitoring/network/StateNetworkService.java +++ b/core/src/main/java/bisq/core/dao/monitoring/network/StateNetworkService.java @@ -167,6 +167,10 @@ public void reset() { requestStateHashHandlerMap.clear(); } + public boolean isSeedNode(NodeAddress nodeAddress) { + return peerManager.isSeedNode(nodeAddress); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Listeners 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 0c40fc36833..1175a264468 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.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 void maybeExportToJson() { 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 bc75fb3602f..e3869e06bec 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.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 Optional getUnspentTxOutput(TxOutputKey key) { 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 boolean isTxOutputSpendable(TxOutputKey key) { // 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/dao/state/DaoStateSnapshotService.java b/core/src/main/java/bisq/core/dao/state/DaoStateSnapshotService.java index 3b179551d3b..e1df3b588ea 100644 --- a/core/src/main/java/bisq/core/dao/state/DaoStateSnapshotService.java +++ b/core/src/main/java/bisq/core/dao/state/DaoStateSnapshotService.java @@ -17,11 +17,13 @@ package bisq.core.dao.state; +import bisq.core.dao.DaoSetupService; import bisq.core.dao.monitoring.DaoStateMonitoringService; import bisq.core.dao.monitoring.model.DaoStateHash; import bisq.core.dao.state.model.DaoState; import bisq.core.dao.state.model.blockchain.Block; import bisq.core.dao.state.storage.DaoStateStorageService; +import bisq.core.user.Preferences; import bisq.common.config.Config; import bisq.common.util.GcUtil; @@ -49,13 +51,14 @@ * SNAPSHOT_GRID old not less than 2 times the SNAPSHOT_GRID old. */ @Slf4j -public class DaoStateSnapshotService { +public class DaoStateSnapshotService implements DaoSetupService, DaoStateListener { private static final int SNAPSHOT_GRID = 20; private final DaoStateService daoStateService; private final GenesisTxInfo genesisTxInfo; private final DaoStateStorageService daoStateStorageService; private final DaoStateMonitoringService daoStateMonitoringService; + private final Preferences preferences; private final File storageDir; private DaoState daoStateSnapshotCandidate; @@ -65,6 +68,7 @@ public class DaoStateSnapshotService { @Nullable private Runnable daoRequiresRestartHandler; private boolean requestPersistenceCalled; + private boolean isParseBlockChainComplete; /////////////////////////////////////////////////////////////////////////////////////////// @@ -76,21 +80,84 @@ public DaoStateSnapshotService(DaoStateService daoStateService, GenesisTxInfo genesisTxInfo, DaoStateStorageService daoStateStorageService, DaoStateMonitoringService daoStateMonitoringService, + Preferences preferences, @Named(Config.STORAGE_DIR) File storageDir) { this.daoStateService = daoStateService; this.genesisTxInfo = genesisTxInfo; this.daoStateStorageService = daoStateStorageService; this.daoStateMonitoringService = daoStateMonitoringService; + this.preferences = preferences; this.storageDir = storageDir; } + /////////////////////////////////////////////////////////////////////////////////////////// + // DaoSetupService + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void addListeners() { + daoStateService.addDaoStateListener(this); + } + + @Override + public void start() { + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // DaoStateListener + /////////////////////////////////////////////////////////////////////////////////////////// + + // We listen onDaoStateChanged to ensure the dao state has been processed from listener clients after parsing. + // We need to listen during batch processing as well to write snapshots during that process. + @Override + public void onDaoStateChanged(Block block) { + // If we have isUseDaoMonitor activated we apply the hash and snapshots at each new block during initial parsing. + // Otherwise we do it only after the initial blockchain parsing is completed to not delay the parsing. + // In that case we get the missing hashes from the seed nodes. At any new block we do the hash calculation + // ourself and therefore get back confidence that our DAO state is in sync with the network. + if (preferences.isUseDaoMonitor() || isParseBlockChainComplete) { + // We need to execute first the daoStateMonitoringService.createHashFromBlock to get the hash created + daoStateMonitoringService.createHashFromBlock(block); + maybeCreateSnapshot(block); + } + } + + @Override + public void onParseBlockChainComplete() { + isParseBlockChainComplete = true; + + // In case we have dao monitoring deactivated we create the snapshot after we are completed with parsing + // and we got called back from daoStateMonitoringService once the hashes are created from peers data. + if (!preferences.isUseDaoMonitor()) { + // We register a callback handler once the daoStateMonitoringService has received the missing hashes from + // the seed node and applied the latest hash. After that we are ready to make a snapshot and persist it. + daoStateMonitoringService.setCreateSnapshotHandler(() -> { + // As we did not have created any snapshots during initial parsing we create it now. We cannot use the past + // snapshot height as we have not cloned a candidate (that would cause quite some delay during parsing). + // The next snapshots will be created again according to the snapshot height grid (each 20 blocks). + // This also comes with the improvement that the user does not need to load the past blocks back to the last + // snapshot height. Thought it comes also with the small risk that in case of re-orgs the user need to do + // a resync in case the dao state would have been affected by that reorg. + long ts = System.currentTimeMillis(); + daoStateSnapshotCandidate = daoStateService.getClone(); + daoStateHashChainSnapshotCandidate = new LinkedList<>(daoStateMonitoringService.getDaoStateHashChain()); + daoStateStorageService.requestPersistence(daoStateSnapshotCandidate, + daoStateHashChainSnapshotCandidate, + () -> { + log.info("Persisted daoState after parsing completed at height {}. Took {} ms", + daoStateService.getChainHeight(), System.currentTimeMillis() - ts); + }); + }); + } + } + + /////////////////////////////////////////////////////////////////////////////////////////// // API /////////////////////////////////////////////////////////////////////////////////////////// - // We do not use DaoStateListener.onDaoStateChanged but let the DaoEventCoordinator call maybeCreateSnapshot to ensure the - // correct order of execution. // We need to process during batch processing as well to write snapshots during that process. public void maybeCreateSnapshot(Block block) { int chainHeight = block.getHeight(); diff --git a/core/src/main/java/bisq/core/dao/state/storage/DaoStateStorageService.java b/core/src/main/java/bisq/core/dao/state/storage/DaoStateStorageService.java index 2abf8bafe55..6f1b12d949a 100644 --- a/core/src/main/java/bisq/core/dao/state/storage/DaoStateStorageService.java +++ b/core/src/main/java/bisq/core/dao/state/storage/DaoStateStorageService.java @@ -92,9 +92,10 @@ public void requestPersistence(DaoState daoState, new Thread(() -> { Thread.currentThread().setName("Serialize and write DaoState"); persistenceManager.persistNow(() -> { - // After we have written to disk we remove the the daoState in the store to avoid that it stays in + // After we have written to disk we remove the daoState in the store to avoid that it stays in // memory there until the next persist call. store.setDaoState(null); + store.setDaoStateHashChain(null); completeHandler.run(); }); diff --git a/core/src/main/java/bisq/core/filter/Filter.java b/core/src/main/java/bisq/core/filter/Filter.java index be43c3f7465..0a684cd9e92 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.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 @@ static Filter cloneWithSig(Filter filter, String signatureAsBase64) { 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 @@ static Filter cloneWithoutSig(Filter filter) { filter.getBannedAutoConfExplorers(), filter.getNodeAddressesBannedFromNetwork(), filter.isDisableMempoolValidation(), - filter.isDisableApi()); + filter.isDisableApi(), + filter.isDisablePowMessage(), + filter.getPowDifficulty()); } public Filter(List bannedOfferIds, @@ -189,7 +201,9 @@ public Filter(List bannedOfferIds, List bannedAutoConfExplorers, Set nodeAddressesBannedFromNetwork, boolean disableMempoolValidation, - boolean disableApi) { + boolean disableApi, + boolean disablePowMessage, + int powDifficulty) { this(bannedOfferIds, nodeAddressesBannedFromTrading, bannedPaymentAccounts, @@ -217,7 +231,9 @@ public Filter(List bannedOfferIds, bannedAutoConfExplorers, nodeAddressesBannedFromNetwork, disableMempoolValidation, - disableApi); + disableApi, + disablePowMessage, + powDifficulty); } @@ -253,7 +269,9 @@ public Filter(List bannedOfferIds, 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 Filter(List bannedOfferIds, 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 protobuf.StoragePayload toProtoMessage() { .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 static Filter fromProto(protobuf.Filter proto) { ProtoUtil.protocolStringListToList(proto.getBannedAutoConfExplorersList()), ProtoUtil.protocolStringListToSet(proto.getNodeAddressesBannedFromNetworkList()), proto.getDisableMempoolValidation(), - proto.getDisableApi() + proto.getDisableApi(), + proto.getDisablePowMessage(), + proto.getPowDifficulty() ); } @@ -409,6 +433,8 @@ public String toString() { ",\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 472ba4bfcfb..dfae9cf6858 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 @@ 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.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.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.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 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 boolean isWitnessSignerPubKeyBanned(String witnessSignerPubKeyAsHex) { .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 @@ private void onFilterAddedFromNetwork(Filter newFilter) { 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 cec006aff0d..5bd882f1769 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 static String get(String key) { .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 7bfe9556930..d0565d4db2f 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.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 bd170c08792..6f25f2782a2 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.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 @@ private void applyFilterOnAllOffers() { // % 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 @@ private void onOfferAdded(Offer offer) { 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 05305dadf0d..7c3797a058b 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.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 enum State { /////////////////////////////////////////////////////////////////////////////////////////// @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 enum State { // Constructor /////////////////////////////////////////////////////////////////////////////////////////// - public Offer(OfferPayload offerPayload) { - this.offerPayload = offerPayload; + public Offer(OfferPayloadBase offerPayloadBase) { + this.offerPayloadBase = offerPayloadBase; } @@ -130,11 +132,21 @@ public Offer(OfferPayload offerPayload) { @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 void cancelAvailabilityRequest() { @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 void checkTradePriceTolerance(long takersTradePrice) throws TradePriceOut @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 ObjectProperty stateProperty() { } public void setOfferFeePaymentTxId(String offerFeePaymentTxID) { - offerPayload.setOfferFeePaymentTxId(offerFeePaymentTxID); + getOfferPayload().ifPresent(p -> p.setOfferFeePaymentTxId(offerFeePaymentTxID)); } public void setErrorMessage(String errorMessage) { @@ -279,52 +300,52 @@ public void setErrorMessage(String errorMessage) { // 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 Volume getMinVolume() { } 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 String getErrorMessage() { // 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 String getCurrencyCode() { 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(); + return getOfferPayload().map(OfferPayload::isUseAutoClose).orElse(false); } - public boolean isPrivateOffer() { - return offerPayload.isPrivateOffer(); - } - - public long getUpperClosePrice() { - return offerPayload.getUpperClosePrice(); + public boolean isUseReOpenAfterAutoClose() { + return getOfferPayload().map(OfferPayload::isUseReOpenAfterAutoClose).orElse(false); } - public long getLowerClosePrice() { - return offerPayload.getLowerClosePrice(); - } - - public boolean isUseReOpenAfterAutoClose() { - return offerPayload.isUseReOpenAfterAutoClose(); + public boolean isBsqSwapOffer() { + return getOfferPayloadBase() instanceof BsqSwapOfferPayload; } public boolean isXmrAutoConf() { @@ -533,6 +548,24 @@ public boolean isXmr() { 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 boolean equals(Object o) { 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 boolean equals(Object o) { @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 String toString() { 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 71a28280ef7..60099f0bc6d 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 @@ 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.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 OfferBookService(P2PService p2PService, @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 void onAdded(Collection protectedStorageEntries) { @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 void addOffer(Offer offer, ResultHandler resultHandler, ErrorMessageHandl return; } - boolean result = p2PService.addProtectedStorageEntry(offer.getOfferPayload()); + boolean result = p2PService.addProtectedStorageEntry(offer.getOfferPayloadBase()); if (result) { resultHandler.handleResult(); } else { @@ -149,7 +149,7 @@ public void addOffer(Offer offer, ResultHandler resultHandler, ErrorMessageHandl } } - public void refreshTTL(OfferPayload offerPayload, + public void refreshTTL(OfferPayloadBase offerPayloadBase, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { if (filterManager.requireUpdateToNewVersionForTrading()) { @@ -157,7 +157,7 @@ public void refreshTTL(OfferPayload offerPayload, return; } - boolean result = p2PService.refreshTTL(offerPayload); + boolean result = p2PService.refreshTTL(offerPayloadBase); if (result) { resultHandler.handleResult(); } else { @@ -171,16 +171,16 @@ public void activateOffer(Offer offer, 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 void removeOffer(OfferPayload offerPayload, 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 @@ private void doDumpStatistics() { }) .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 00000000000..ade5334a887 --- /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 bfd37f2af12..7a18db1f965 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.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 @@ @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 enum Result { 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 Result canTakeOffer(Offer offer, boolean isTakerApiUser) { 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 afac82ae4e6..1ac2b658b17 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 @@ 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 @@ private void setDisplayStrings() { 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 00000000000..4ce2c8304fc --- /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 eec00333d36..04e824c715d 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 static boolean requiresNodeAddressUpdate() { 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 fb76a69c89e..c676c57bbdc 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.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.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 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.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 OfferUtil(AccountAgeWitnessService accountAgeWitnessService, 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 @@ else if (feeCurrencyCode.equalsIgnoreCase("BTC") && !preferences.isPayFeeInBtc() * @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 Coin getUsableBsqBalance() { // 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 boolean isCurrencyForMakerFeeBtc(@Nullable Coin amount) { * @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 boolean isCurrencyForTakerFeeBtc(Coin amount) { } 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 boolean isBsqForTakerFeeAvailable(@Nullable Coin amount) { } 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 Optional getFeeInUserFiatCurrency(Coin makerFee, 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 Map getExtraDataMap(PaymentAccount paymentAccount, 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 void validateOfferData(double buyerSecurityDeposit, 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 void validateOfferData(double buyerSecurityDeposit, // 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 static Optional getInvalidMakerFeeTxErrorMessage(Offer offer, Btc } } 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 4c2d06c3085..3e47b7e1caa 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 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 enum State { 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 enum State { @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 static Tradable fromProto(protobuf.OpenOffer proto) { proto.getTriggerPrice()); } - /////////////////////////////////////////////////////////////////////////////////////////// - // Getters + // Tradable /////////////////////////////////////////////////////////////////////////////////////////// @Override @@ -151,6 +167,11 @@ public String getShortId() { return offer.getShortId(); } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Misc + /////////////////////////////////////////////////////////////////////////////////////////// + public void setState(State state) { this.state = state; @@ -166,6 +187,12 @@ public boolean isDeactivated() { 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 @@ private void stopTimeout() { } } - @Override public String toString() { return "OpenOffer{" + @@ -195,6 +221,7 @@ public String toString() { ",\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 7c8ddb2c653..b7ea5b9cbbc 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.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.bisq_v1.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.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 void onUpdatedDataReceived() { 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 void shutDown(@Nullable Runnable completeHandler) { 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 @@ private void removeOpenOffers(List openOffers, @Nullable Runnable com 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 void placeOffer(Offer offer, 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 void placeOffer(Offer offer, 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 void removeOffer(Offer offer, ResultHandler resultHandler, ErrorMessageHa 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 void removeOffer(Offer offer, ResultHandler resultHandler, ErrorMessageHa 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 void deactivateOpenOffer(OpenOffer openOffer, errorMessageHandler); } + public void removeOpenOffer(OpenOffer openOffer) { + removeOpenOffer(openOffer, () -> { + }, error -> { + }); + } + public void removeOpenOffer(OpenOffer openOffer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { @@ -464,7 +490,7 @@ public void removeOpenOffer(OpenOffer openOffer, 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 void editOpenOfferPublish(Offer editedOffer, 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 void editOpenOfferCancel(OpenOffer openOffer, } } - 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 @@ private void handleOfferAvailabilityRequest(OfferAvailabilityRequest request, No 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 @@ private void maybeUpdatePersistedOffers() { 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 @@ private void maybeUpdatePersistedOffers() { 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 @@ private void maybeUpdatePersistedOffers() { } // - 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 @@ private void maybeUpdatePersistedOffers() { // 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 @@ private void maybeUpdatePersistedOffers() { 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 @@ private void republishOffers() { if (stopped) { return; } - stopPeriodicRefreshOffersTimer(); List openOffersList = new ArrayList<>(openOffers.getList()); @@ -917,15 +944,8 @@ private void processListForRepublishOffers(List list) { } 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 @@ private void processListForRepublishOffers(List list) { } } - 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 @@ private void startPeriodicRefreshOffersTimer() { 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 @@ private void startPeriodicRefreshOffersTimer() { 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 @@ private void restart() { startPeriodicRepublishOffersTimer(); } - private void requestPersistence() { + public void requestPersistence() { persistenceManager.requestPersistence(); } @@ -1057,4 +1087,23 @@ private void stopRetryRepublishOffersTimer() { 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 e4ad982163e..abf3bd33cf9 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 c1559cec8d3..6b75fda67dc 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 218abca67bb..bec32754887 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 6d9d14eaf61..2b2d412b45e 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.UUID; import lombok.EqualsAndHashCode; -import lombok.Value; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import javax.annotation.Nullable; @@ -35,7 +35,7 @@ // 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 41b5aa0fcc7..5d616fe7e1b 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.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 d72fdc4a276..2b230ef3533 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 f6c04fa1982..3afb6fb2a3a 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 0dbc8e69ea4..6c0c11f40e6 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 @@ 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 9f5f61dd68b..f984dd9ddff 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.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.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.Date; import java.util.List; import java.util.Map; -import java.util.UUID; import lombok.extern.slf4j.Slf4j; @@ -94,14 +95,8 @@ public CreateOfferService(OfferUtil offerUtil, // 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 Offer createAndGetOffer(String offerId, creationTime, makerAddress, pubKeyRing, - OfferPayload.Direction.valueOf(direction.name()), + direction, priceAsLong, marketPriceMarginParam, useMarketBasedPriceValue, @@ -228,7 +223,7 @@ public Offer createAndGetOffer(String offerId, } public Tuple2 getEstimatedFeeAndTxVsize(Coin amount, - OfferPayload.Direction direction, + OfferDirection direction, double buyerSecurityDeposit, double sellerSecurityDeposit) { Coin reservedFundsForOffer = getReservedFundsForOffer(direction, @@ -239,7 +234,7 @@ public Tuple2 getEstimatedFeeAndTxVsize(Coin amount, 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 Coin getReservedFundsForOffer(OfferPayload.Direction direction, 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 35ee04a7941..a92e5950f07 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 ec0fe502faf..b2025267540 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 fa4ae7440be..228c4e43de5 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 @@ // 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 static protobuf.OfferPayload.Direction toProtoMessage(Direction direction // 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 static protobuf.OfferPayload.Direction toProtoMessage(Direction direction 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 static protobuf.OfferPayload.Direction toProtoMessage(Direction direction @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 static protobuf.OfferPayload.Direction toProtoMessage(Direction direction 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 static protobuf.OfferPayload.Direction toProtoMessage(Direction direction @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 OfferPayload(String id, long date, NodeAddress ownerNodeAddress, PubKeyRing pubKeyRing, - Direction direction, + OfferDirection direction, long price, double marketPriceMargin, boolean useMarketBasedPrice, @@ -224,28 +172,31 @@ public OfferPayload(String id, @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 OfferPayload(String id, 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 protobuf.StoragePayload toProtoMessage() { .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 static OfferPayload fromProto(protobuf.OfferPayload proto) { 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 static OfferPayload fromProto(protobuf.OfferPayload proto) { 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 8e345b81424..447d1d7076f 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 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 99defe325b9..c18cf11ec4c 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 static boolean wasTriggered(MarketPrice marketPrice, OpenOffer openOffer) 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 static boolean wasTriggered(MarketPrice marketPrice, OpenOffer openOffer) } 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 @@ private void checkPriceThreshold(MarketPrice marketPrice, OpenOffer openOffer) { 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 @@ private void checkPriceThreshold(MarketPrice marketPrice, OpenOffer openOffer) { }); } 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 00000000000..33b3f963cda --- /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 00000000000..2ba5ca2892f --- /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 00000000000..266cc2a8564 --- /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 00000000000..13ed697afe5 --- /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 00000000000..a2e05bcf750 --- /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.bsqswap.PlaceBsqSwapOfferModel; +import bisq.core.offer.placeoffer.bsqswap.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 0c54d733ed0..e90e4dcc00a 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 cdf394435fc..b3f50a95e55 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 void placeOffer() { 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 16def612ba0..f9314ef31b6 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 31e8d300032..689ad265c10 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 df697134319..eeb21bda5c7 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.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 void onFailure(TxBroadcastException exception) { 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 35d619feb4d..e5494349e77 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 @@ protected void run() { 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/bsqswap/PlaceBsqSwapOfferModel.java b/core/src/main/java/bisq/core/offer/placeoffer/bsqswap/PlaceBsqSwapOfferModel.java new file mode 100644 index 00000000000..add215a7bb0 --- /dev/null +++ b/core/src/main/java/bisq/core/offer/placeoffer/bsqswap/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.bsqswap; + +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/bsqswap/PlaceBsqSwapOfferProtocol.java b/core/src/main/java/bisq/core/offer/placeoffer/bsqswap/PlaceBsqSwapOfferProtocol.java new file mode 100644 index 00000000000..33fceecd196 --- /dev/null +++ b/core/src/main/java/bisq/core/offer/placeoffer/bsqswap/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.bsqswap; + +import bisq.core.offer.placeoffer.bsqswap.tasks.AddBsqSwapOfferToOfferBook; +import bisq.core.offer.placeoffer.bsqswap.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/bsqswap/tasks/AddBsqSwapOfferToOfferBook.java b/core/src/main/java/bisq/core/offer/placeoffer/bsqswap/tasks/AddBsqSwapOfferToOfferBook.java new file mode 100644 index 00000000000..41d6c356519 --- /dev/null +++ b/core/src/main/java/bisq/core/offer/placeoffer/bsqswap/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.bsqswap.tasks; + +import bisq.core.offer.placeoffer.bisq_v1.PlaceOfferModel; +import bisq.core.offer.placeoffer.bsqswap.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/bsqswap/tasks/ValidateBsqSwapOffer.java b/core/src/main/java/bisq/core/offer/placeoffer/bsqswap/tasks/ValidateBsqSwapOffer.java new file mode 100644 index 00000000000..69f39bcc72c --- /dev/null +++ b/core/src/main/java/bisq/core/offer/placeoffer/bsqswap/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.bsqswap.tasks; + +import bisq.core.offer.Offer; +import bisq.core.offer.placeoffer.bsqswap.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 00000000000..bc5899d5343 --- /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 d1764980547..3979eeac096 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 static PaymentAccount getPaymentAccount(PaymentMethod paymentMethod) { 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 00000000000..ce8f892ddd3 --- /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 e6da6be373f..ee9f523bf48 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 f76d38fdecc..89d6cd4ed37 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.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 PaymentAccountPayload fromProto(protobuf.PaymentAccountPayload proto) { 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 6b70441a866..52af1c7e501 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 static void printAllEnumsForPB() { 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 21fff967377..9d6a68584dc 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.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.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 NetworkEnvelope fromProto(protobuf.NetworkEnvelope proto) throws Protobuf 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 NetworkPayload fromProto(protobuf.StoragePayload proto) { 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 f42670d76d4..b0aa0312576 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.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 7894b55f089..d6348e555a6 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 @@ 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; diff --git a/core/src/main/java/bisq/core/setup/CoreNetworkCapabilities.java b/core/src/main/java/bisq/core/setup/CoreNetworkCapabilities.java index 69485612c7d..1f270e9a121 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 @@ static void setSupportedCapabilities(Config config) { 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 0e54e5527ca..264ccc0cdef 100644 --- a/core/src/main/java/bisq/core/setup/CorePersistedDataHost.java +++ b/core/src/main/java/bisq/core/setup/CorePersistedDataHost.java @@ -30,8 +30,9 @@ import bisq.core.support.dispute.mediation.MediationDisputeListService; import bisq.core.support.dispute.refund.RefundDisputeListService; import bisq.core.trade.TradeManager; -import bisq.core.trade.closed.ClosedTradableManager; -import bisq.core.trade.failed.FailedTradesManager; +import bisq.core.trade.bisq_v1.ClosedTradableManager; +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 static List getPersistedDataHosts(Injector injector) { 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 669f7d08540..5a0c3eac8f0 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.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 b38cf7fa4ff..d1a1a20ba10 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 c02641b1bdc..af511766aeb 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.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.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.TradeManager; -import bisq.core.trade.closed.ClosedTradableManager; +import bisq.core.trade.bisq_v1.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 b3b35a68c59..1be96332efa 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.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 2718da778ba..a663c966f8c 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.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.TradeManager; -import bisq.core.trade.closed.ClosedTradableManager; +import bisq.core.trade.bisq_v1.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 0b941272760..52af69e1305 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.PeerOpenedDisputeMessage; import bisq.core.support.messages.ChatMessage; import bisq.core.support.messages.SupportMessage; -import bisq.core.trade.Trade; 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.bisq_v1.ClosedTradableManager; +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 void onAcceptMediationResult(Trade trade, // 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 90352c26475..5500da3156c 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.PeerOpenedDisputeMessage; import bisq.core.support.messages.ChatMessage; import bisq.core.support.messages.SupportMessage; -import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; -import bisq.core.trade.closed.ClosedTradableManager; +import bisq.core.trade.bisq_v1.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 e69a18c3f66..32afe342d58 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 @@ 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 ObservableList getObservableChatMessageList() { @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 1c44185f2a8..d5116e8ca39 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.SupportType; import bisq.core.support.messages.ChatMessage; import bisq.core.support.messages.SupportMessage; -import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; -import bisq.core.trade.closed.ClosedTradableManager; -import bisq.core.trade.failed.FailedTradesManager; +import bisq.core.trade.bisq_v1.ClosedTradableManager; +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 PubKeyRing getPeerPubKeyRing(ChatMessage message) { @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/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index 6707fde8ea7..81d7759d7f8 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -23,23 +23,46 @@ 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.ClosedTradableManager; +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 +140,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 +149,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 +179,7 @@ public TradeManager(User user, BsqWalletService bsqWalletService, OpenOfferManager openOfferManager, ClosedTradableManager closedTradableManager, + BsqSwapTradeManager bsqSwapTradeManager, FailedTradesManager failedTradesManager, P2PService p2PService, PriceFeedService priceFeedService, @@ -162,7 +187,7 @@ public TradeManager(User user, TradeUtil tradeUtil, ArbitratorManager arbitratorManager, MediatorManager mediatorManager, - ProcessModelServiceProvider processModelServiceProvider, + Provider provider, ClockWatcher clockWatcher, PersistenceManager> persistenceManager, ReferralIdService referralIdService, @@ -174,6 +199,7 @@ public TradeManager(User user, this.bsqWalletService = bsqWalletService; this.openOfferManager = openOfferManager; this.closedTradableManager = closedTradableManager; + this.bsqSwapTradeManager = bsqSwapTradeManager; this.failedTradesManager = failedTradesManager; this.p2PService = p2PService; this.priceFeedService = priceFeedService; @@ -181,7 +207,7 @@ public TradeManager(User user, 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 +249,8 @@ public void onDirectMessage(DecryptedMessageWithPubKey message, NodeAddress peer NetworkEnvelope networkEnvelope = message.getNetworkEnvelope(); if (networkEnvelope instanceof InputsForDepositTxRequest) { handleTakeOfferRequest(peer, (InputsForDepositTxRequest) networkEnvelope); + } else if (networkEnvelope instanceof BsqSwapRequest) { + handleBsqSwapRequest(peer, (BsqSwapRequest) networkEnvelope); } } @@ -274,13 +302,9 @@ private void handleTakeOfferRequest(NodeAddress peer, InputsForDepositTxRequest 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 +316,61 @@ private void handleTakeOfferRequest(NodeAddress peer, InputsForDepositTxRequest } + 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 +398,7 @@ public void onUpdatedDataReceived() { }); } - public TradeProtocol getTradeProtocol(Trade trade) { + public TradeProtocol getTradeProtocol(TradeModel trade) { String uid = trade.getUid(); if (tradeProtocolByTradeId.containsKey(uid)) { return tradeProtocolByTradeId.get(uid); @@ -340,26 +419,48 @@ public TradeProtocol getTradeProtocol(Trade trade) { /////////////////////////////////////////////////////////////////////////////////////////// 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 +499,7 @@ public void onTakeOffer(Coin amount, String paymentAccountId, boolean useSavingsWallet, boolean isTakerApiUser, - TradeResultHandler tradeResultHandler, + TradeResultHandler tradeResultHandler, ErrorMessageHandler errorMessageHandler) { checkArgument(!wasOfferAlreadyUsedInTrade(offer.getId())); @@ -441,12 +542,7 @@ public void onTakeOffer(Coin amount, 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 +556,77 @@ public void onTakeOffer(Coin amount, 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 +701,12 @@ public void onTradeCompleted(Trade trade) { /////////////////////////////////////////////////////////////////////////////////////////// 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,35 +808,17 @@ public Set getSetOfFailedOrClosedTradeIdsFromLockedInFunds() throws Trad return tradesIdSet; } - // 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) { - if (!recoverAddresses(trade)) { - log.warn("Failed to recover address during unFail trade"); - return false; - } - initPersistedTrade(trade); + /////////////////////////////////////////////////////////////////////////////////////////// + // BsqSwapTradeManager delegates + /////////////////////////////////////////////////////////////////////////////////////////// - if (!tradableList.contains(trade)) { - tradableList.add(trade); - } - return true; + public void onBsqSwapTradeCompleted(BsqSwapTrade bsqSwapTrade) { + bsqSwapTradeManager.onTradeCompleted(bsqSwapTrade); } - // The trade is added to pending trades if the associated address entries are AVAILABLE and - // the relevant entries are changed, otherwise it's not added and no address entries are changed - private boolean recoverAddresses(Trade trade) { - // Find addresses associated with this trade. - var entries = tradeUtil.getAvailableAddresses(trade); - if (entries == null) - return false; - - btcWalletService.recoverAddressEntry(trade.getId(), entries.first, - AddressEntry.Context.MULTI_SIG); - btcWalletService.recoverAddressEntry(trade.getId(), entries.second, - AddressEntry.Context.TRADE_PAYOUT); - return true; + public Optional findBsqSwapTradeById(String tradeId) { + return bsqSwapTradeManager.findBsqSwapTradeById(tradeId); } @@ -696,21 +839,33 @@ public boolean isMyOffer(Offer offer) { } public boolean wasOfferAlreadyUsedInTrade(String offerId) { - return getTradeById(offerId).isPresent() || - failedTradesManager.getTradeById(offerId).isPresent() || - closedTradableManager.getTradableById(offerId).isPresent(); + 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 OfferPayload.Direction, otherwise the mirrored direction + // If I am the maker, we use the OfferDirection, otherwise the mirrored direction if (isMyOffer(offer)) return offer.isBuyOffer(); else - return offer.getDirection() == OfferPayload.Direction.SELL; + 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 tradableList.stream().filter(e -> e.getId().equals(tradeId)).findFirst(); + return getTradeModelById(tradeId) + .filter(tradeModel -> tradeModel instanceof Trade) + .map(tradeModel -> (Trade) tradeModel); } private void removeTrade(Trade trade) { @@ -725,9 +880,45 @@ private void addTrade(Trade trade) { } } + 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) { + if (!recoverAddresses(trade)) { + log.warn("Failed to recover address during unFail trade"); + return false; + } + + initPersistedTrade(trade); + + if (!tradableList.contains(trade)) { + tradableList.add(trade); + } + return true; + } + + // The trade is added to pending trades if the associated address entries are AVAILABLE and + // the relevant entries are changed, otherwise it's not added and no address entries are changed + private boolean recoverAddresses(Trade trade) { + // Find addresses associated with this trade. + var entries = tradeUtil.getAvailableAddresses(trade); + if (entries == null) + return false; + + btcWalletService.recoverAddressEntry(trade.getId(), entries.first, + AddressEntry.Context.MULTI_SIG); + btcWalletService.recoverAddressEntry(trade.getId(), entries.second, + AddressEntry.Context.TRADE_PAYOUT); + return true; + } } diff --git a/core/src/main/java/bisq/core/trade/TradeModule.java b/core/src/main/java/bisq/core/trade/TradeModule.java index 751bd5a0eb1..a3ee4d5aab3 100644 --- a/core/src/main/java/bisq/core/trade/TradeModule.java +++ b/core/src/main/java/bisq/core/trade/TradeModule.java @@ -21,8 +21,8 @@ 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.ClosedTradableManager; +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 5e3e40810b5..c27d200408f 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 @@ * 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 @@ private boolean isMyMessage(AckMessage ackMessage, Trade trade) { 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/closed/ClosedTradableManager.java b/core/src/main/java/bisq/core/trade/bisq_v1/ClosedTradableManager.java similarity index 89% rename from core/src/main/java/bisq/core/trade/closed/ClosedTradableManager.java rename to core/src/main/java/bisq/core/trade/bisq_v1/ClosedTradableManager.java index 01c36beb4d1..b508d801f66 100644 --- a/core/src/main/java/bisq/core/trade/closed/ClosedTradableManager.java +++ b/core/src/main/java/bisq/core/trade/bisq_v1/ClosedTradableManager.java @@ -15,14 +15,13 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.closed; +package bisq.core.trade.bisq_v1; 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.core.trade.model.Tradable; +import bisq.core.trade.model.TradableList; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.common.crypto.KeyRing; import bisq.common.persistence.PersistenceManager; @@ -47,18 +46,18 @@ public class ClosedTradableManager implements PersistedDataHost { private final TradableList closedTradables = new TradableList<>(); private final KeyRing keyRing; private final PriceFeedService priceFeedService; - private final CleanupMailboxMessages cleanupMailboxMessages; + private final CleanupMailboxMessagesService cleanupMailboxMessagesService; private final DumpDelayedPayoutTx dumpDelayedPayoutTx; @Inject public ClosedTradableManager(KeyRing keyRing, PriceFeedService priceFeedService, PersistenceManager> persistenceManager, - CleanupMailboxMessages cleanupMailboxMessages, + CleanupMailboxMessagesService cleanupMailboxMessagesService, DumpDelayedPayoutTx dumpDelayedPayoutTx) { this.keyRing = keyRing; this.priceFeedService = priceFeedService; - this.cleanupMailboxMessages = cleanupMailboxMessages; + this.cleanupMailboxMessagesService = cleanupMailboxMessagesService; this.dumpDelayedPayoutTx = dumpDelayedPayoutTx; this.persistenceManager = persistenceManager; @@ -79,7 +78,7 @@ public void readPersisted(Runnable completeHandler) { } public void onAllServicesInitialized() { - cleanupMailboxMessages.handleTrades(getClosedTrades()); + cleanupMailboxMessagesService.handleTrades(getClosedTrades()); } public void add(Tradable tradable) { diff --git a/core/src/main/java/bisq/core/trade/closed/ClosedTradeUtil.java b/core/src/main/java/bisq/core/trade/bisq_v1/ClosedTradeUtil.java similarity index 97% rename from core/src/main/java/bisq/core/trade/closed/ClosedTradeUtil.java rename to core/src/main/java/bisq/core/trade/bisq_v1/ClosedTradeUtil.java index 50fab443dfa..7edd5965237 100644 --- a/core/src/main/java/bisq/core/trade/closed/ClosedTradeUtil.java +++ b/core/src/main/java/bisq/core/trade/bisq_v1/ClosedTradeUtil.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.closed; +package bisq.core.trade.bisq_v1; import bisq.core.btc.wallet.BsqWalletService; import bisq.core.locale.CurrencyUtil; @@ -25,8 +25,8 @@ 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.model.Tradable; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.user.Preferences; import bisq.core.util.coin.BsqFormatter; @@ -51,9 +51,9 @@ 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.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.AveragePriceUtil.getAveragePriceTuple; import static bisq.core.util.FormattingUtils.BTC_FORMATTER_KEY; import static bisq.core.util.FormattingUtils.formatPercentagePrice; @@ -326,7 +326,7 @@ public String getStateAsString(Tradable tradable) { } else { log.error("That must not happen. We got a pending state but we are in" + " the closed trades list. state={}", - trade.getState().name()); + trade.getTradeState().name()); return Res.get("shared.na"); } } else if (tradable instanceof OpenOffer) { 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 3d2785c01e2..1589a4afb32 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 void maybeDumpDelayedPayoutTxs(TradableList trada .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 84746a2128c..295342511a6 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 FailedTradesManager(KeyRing keyRing, 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 void readPersisted(Runnable completeHandler) { } 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 013dcdb42ab..60dc78ae843 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; 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 aab6dc4e460..3ae8a13fba3 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 cbc79660678..947257aa890 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 a026f6ab982..3f99e7dea9b 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 String getRole(boolean isBuyerMakerAndSellerTaker, boolean isMaker, Strin : 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 ca3531226bf..3a9d2b42f07 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 00000000000..98c4a8ab0b2 --- /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.getAmount(), 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.getAmount(), 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 00000000000..69181167281 --- /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 00000000000..7b3b8c86acc --- /dev/null +++ b/core/src/main/java/bisq/core/trade/bsq_swap/BsqSwapTradeManager.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.bsq_swap; + +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 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 lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Singleton +public class BsqSwapTradeManager implements PersistedDataHost { + 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, + PersistenceManager> persistenceManager) { + this.keyRing = keyRing; + this.priceFeedService = priceFeedService; + 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(); + } + + private void requestPersistence() { + persistenceManager.requestPersistence(); + } +} 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 2e0a41182dd..b911758da82 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 a6d82ac8406..bb163a6051a 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/Tradable.java b/core/src/main/java/bisq/core/trade/model/Tradable.java similarity index 96% rename from core/src/main/java/bisq/core/trade/Tradable.java rename to core/src/main/java/bisq/core/trade/model/Tradable.java index e9b3f331931..fbb21d085ef 100644 --- a/core/src/main/java/bisq/core/trade/Tradable.java +++ b/core/src/main/java/bisq/core/trade/model/Tradable.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.trade; +package bisq.core.trade.model; import bisq.core.offer.Offer; 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 c3a668708ad..345766bc405 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 static TradableList fromProto(protobuf.TradableList proto, 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 00000000000..04104c8c6a1 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/model/TradeModel.java @@ -0,0 +1,111 @@ +package bisq.core.trade.model; + +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 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(); + + + /////////////////////////////////////////////////////////////////////////////////////////// + // 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 00000000000..fdc1b54f38f --- /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 00000000000..f1e777bae14 --- /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 96% 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 3a0005f8247..8ab3777d3e6 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; 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 96% 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 2ccf0fb3cb9..165f3d67c13 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; 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 96% 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 82f38cf9c16..9b0917f273f 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; 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 d32b1c83722..473d90af0d1 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 boolean isMyRoleMaker(PubKeyRing myPubKeyRing) { } 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 96% 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 ccbf66a0f6c..47f672cb402 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; 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 96% 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 11fb6c281d0..470354f2d10 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; 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 a87c18ddee9..2284b2baaa3 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 92% 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 447f8b52ea2..c96dabce4ef 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.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.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 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 @@ * 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 enum State { // 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 static protobuf.Trade.State toProtoMessage(Trade.State state) { // 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 static Trade.Phase fromProto(protobuf.Trade.Phase phase) { 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 static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt @Getter private final ProcessModel processModel; @Getter - private final Offer offer; - @Getter private final boolean isCurrencyForTakerFeeBtc; @Getter private final long txFeeAsLong; @Getter private final long takerFeeAsLong; - // Added in 1.5.1 - @Getter - private final String uid; - - @Setter - private long takeOfferDate; - // Mutable @Nullable @Getter @@ -322,10 +311,6 @@ public static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt private long tradeAmountAsLong; @Setter private long tradePrice; - @Nullable - @Getter - private NodeAddress tradingPeerNodeAddress; - @Getter private State state = State.PREPARATION; @Getter private DisputeState disputeState = DisputeState.NO_DISPUTE; @@ -374,8 +359,7 @@ public static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt @Getter @Setter private String takerPaymentAccountId; - @Nullable - private String errorMessage; + @Getter @Setter @Nullable @@ -396,7 +380,6 @@ public static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt 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 @@ -477,7 +460,7 @@ protected Trade(Offer offer, BtcWalletService btcWalletService, ProcessModel processModel, String uid) { - this.offer = offer; + super(uid, offer); this.txFee = txFee; this.takerFee = takerFee; this.isCurrencyForTakerFeeBtc = isCurrencyForTakerFeeBtc; @@ -486,11 +469,9 @@ protected Trade(Offer offer, this.refundAgentNodeAddress = refundAgentNodeAddress; this.btcWalletService = btcWalletService; this.processModel = processModel; - this.uid = uid; txFeeAsLong = txFee.value; takerFeeAsLong = takerFee.value; - takeOfferDate = new Date().getTime(); } @@ -521,8 +502,8 @@ protected Trade(Offer offer, processModel, uid); this.tradePrice = tradePrice; - this.tradingPeerNodeAddress = tradingPeerNodeAddress; + setTradingPeerNodeAddress(tradingPeerNodeAddress); setTradeAmount(tradeAmount); } @@ -627,7 +608,7 @@ public static Trade fromProto(Trade trade, protobuf.Trade proto, CoreProtoResolv // 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 +629,7 @@ public void initialize(ProcessModelServiceProvider serviceProvider) { /////////////////////////////////////////////////////////////////////////////////////////// // 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 +702,32 @@ public boolean mediationResultAppliedPenaltyToSeller() { /////////////////////////////////////////////////////////////////////////////////////////// - // 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(); + } /////////////////////////////////////////////////////////////////////////////////////////// // Abstract @@ -755,7 +755,7 @@ public void setState(State state) { // 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 +763,7 @@ public void setState(State state) { this.state = state; stateProperty.set(state); - statePhaseProperty.set(state.getPhase()); + statePhaseProperty.set(state.getTradePhase()); } public void setDisputeState(DisputeState disputeState) { @@ -786,13 +786,6 @@ public void setTradePeriodState(TradePeriodState tradePeriodState) { 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; @@ -805,11 +798,6 @@ public void setPayoutTx(Transaction payoutTx) { 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,14 +808,6 @@ public void setAssetTxProofResult(@Nullable AssetTxProofResult assetTxProofResul // Getter /////////////////////////////////////////////////////////////////////////////////////////// - public Date getTakeOfferDate() { - return new Date(takeOfferDate); - } - - public Phase getPhase() { - return state.getPhase(); - } - @Nullable public Volume getTradeVolume() { try { @@ -857,16 +837,16 @@ public Date getMaxTradePeriodDate() { } 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 +876,15 @@ public boolean hasFailed() { } 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 +918,23 @@ public boolean isFundsLockedIn() { } 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() { @@ -989,25 +969,6 @@ public ReadOnlyObjectProperty tradeVolumeProperty() { return tradeVolumeProperty; } - 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); } @@ -1030,11 +991,6 @@ public boolean hasErrorMessage() { return getErrorMessage() != null && !getErrorMessage().isEmpty(); } - @Nullable - public String getErrorMessage() { - return errorMessageProperty.get(); - } - public boolean isTxChainInvalid() { return offer.getOfferFeePaymentTxId() == null || getTakerFeeTxId() == null || @@ -1056,6 +1012,10 @@ public byte[] getArbitratorBtcPubKey() { return arbitratorBtcPubKey; } + public boolean isBsqSwap() { + return offer != null && offer.isBsqSwapOffer(); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Private @@ -1153,7 +1113,6 @@ public String toString() { ",\n statePhaseProperty=" + statePhaseProperty + ",\n disputeStateProperty=" + disputeStateProperty + ",\n tradePeriodStateProperty=" + tradePeriodStateProperty + - ",\n errorMessageProperty=" + errorMessageProperty + ",\n depositTx=" + depositTx + ",\n delayedPayoutTx=" + delayedPayoutTx + ",\n payoutTx=" + payoutTx + 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 00000000000..7fbf26eb47a --- /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 00000000000..9c374bd34cc --- /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 00000000000..d71aace4321 --- /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 00000000000..4148978ffed --- /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 00000000000..665f7d76b3a --- /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 00000000000..6f62bfc669e --- /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 00000000000..b593d13d2eb --- /dev/null +++ b/core/src/main/java/bisq/core/trade/model/bsq_swap/BsqSwapTrade.java @@ -0,0 +1,248 @@ +/* + * 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()); + } + } + + @Getter + private final long amount; + @Getter + private final long txFeePerVbyte; + @Getter + private final long makerFee; + @Getter + private final long takerFee; + @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 makerFee, + long takerFee, + BsqSwapProtocolModel bsqSwapProtocolModel, + @Nullable String errorMessage, + State state, + @Nullable String txId) { + super(uid, offer, takeOfferDate, tradingPeerNodeAddress, errorMessage); + this.amount = amount.value; + this.txFeePerVbyte = txFeePerVbyte; + this.makerFee = makerFee; + this.takerFee = takerFee; + 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(amount) + .setTakeOfferDate(takeOfferDate) + .setMiningFeePerByte(txFeePerVbyte) + .setMakerFee(makerFee) + .setTakerFee(takerFee) + .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(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // 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 Price getPrice() { + return Price.valueOf(offer.getCurrencyCode(), offer.getFixedPrice()); + } + + //todo Not sure if that delivers the value as expected... -> getBsqTradeAmount + public Volume getVolume() { + if (volume == null) { + try { + volume = getPrice().getVolumeByAmount(Coin.valueOf(amount)); + } catch (Throwable e) { + log.error(e.toString()); + return null; + } + } + return volume; + } + + public long getBsqTradeAmount() { + return BsqSwapCalculation.getBsqTradeAmount(getVolume()).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 2d79f9ed7e8..b1ca476ba6d 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 @@ // taskRunner and the optional runnable. public class FluentProtocol { - - interface Event { + public interface Event { String name(); } @@ -61,7 +61,7 @@ protected FluentProtocol condition(Condition condition) { return this; } - protected FluentProtocol setup(Setup setup) { + public FluentProtocol setup(Setup setup) { this.setup = setup; return this; } @@ -98,14 +98,14 @@ public FluentProtocol executeTasks() { 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 Result info(String info) { } } - 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 Result info(String info) { 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 Condition preCondition(boolean preCondition, Runnable conditionFailedHand 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 Result getResult() { 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 @@ private Result getPhaseResult() { 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 @@ private Result getPhaseResult() { 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 @@ private Result getPhaseResult() { "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 @@ private Result getStateResult() { 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 @@ private Result getStateResult() { 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 @@ private Result getStateResult() { "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 @@ private Result getStateResult() { @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 Setup using(TradeTaskRunner taskRunner) { 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 00000000000..7c6f6b23aba --- /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 37600eccaf3..273285efe19 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.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 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 ProcessModelServiceProvider(OpenOfferManager openOfferManager, 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 e90cbb02657..2854365099c 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/protocol/TradePeer.java b/core/src/main/java/bisq/core/trade/protocol/TradePeer.java new file mode 100644 index 00000000000..81b134fbb62 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/TradePeer.java @@ -0,0 +1,27 @@ +/* + * 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; + +import bisq.common.crypto.PubKeyRing; +import bisq.common.proto.persistable.PersistablePayload; + +public interface TradePeer extends PersistablePayload { + PubKeyRing getPubKeyRing(); + + 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 2135aecec8a..549439e61b7 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.Collections; import java.util.concurrent.TimeUnit; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import javax.annotation.Nullable; @@ -51,8 +50,9 @@ @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 TradeProtocol(Trade trade) { // 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 @@ private void handleMailboxCollection(Collection coll } 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 @@ protected FluentProtocol given(FluentProtocol.Condition condition) { } 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 final FluentProtocol.Setup tasks(Class>... tasks) { // 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 @@ protected void sendAckMessage(TradeMessage message, boolean result, @Nullable St 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 @@ protected void startTimeout(long timeoutSec) { 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 @@ protected void handleTaskRunnerFault(FluentProtocol.Event event, String errorMes 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 @@ private boolean isPubKeyValid(DecryptedMessageWithPubKey message) { /////////////////////////////////////////////////////////////////////////////////////////// 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 @@ void handleTaskRunnerFault(@Nullable TradeMessage message, String source, String 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 fe521e99186..54549adc2f0 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 fef4ff490d6..78171d9038c 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 4bff4a9c6e5..16d47b59b3c 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; - -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; +package bisq.core.trade.protocol.bisq_v1; + +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 2731385f145..a99e4be0809 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 BuyerAsTakerProtocol(BuyerAsTakerTrade 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/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 2a920b84961..858cada6c85 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; - -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; +package bisq.core.trade.protocol.bisq_v1; + +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 @@ protected void handle(DepositTxAndDelayedPayoutTxMessage message, NodeAddress pe 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 23807c96f56..ee549e1cde5 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; - -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; - +package bisq.core.trade.protocol.bisq_v1; + +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 @@ @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 @@ enum DisputeEvent implements FluentProtocol.Event { 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 void onAcceptMediationResult(ResultHandler resultHandler, ErrorMessageHan 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 349e677a702..7c1b3299d77 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 524ab346917..53a7722f300 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; - - -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; +package bisq.core.trade.protocol.bisq_v1; + + +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 7e807d137a1..4cacf30098d 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 ef5f83b0d34..68a587e6e46 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; - -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; +package bisq.core.trade.protocol.bisq_v1; + +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 249d0ac73bd..b02b7cba91d 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 30811f56f72..9da43cae45d 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 81afad3caf5..7674a50fe8a 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 ad3767a3ab9..f9c81d9c2a3 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 8297ee92d55..0ed88a22c46 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 631f1a9ca2d..465eb05e1a9 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 63197dbaba6..d6cf92ab3d4 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 static InputsForDepositTxRequest fromProto(protobuf.InputsForDepositTxReq 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 72305028f7d..fca8d0569df 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 444b6af804f..83e03b19619 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 bc7cc84571e..eecd3e82e64 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 86ed851ba8e..2b89fee4cce 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 d93a32737bd..4cf815df56b 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 c6abcd67ee1..5efc83c79aa 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 6401d499478..3814b7ee985 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 705c0b12ed3..f5237d085c5 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 c2bbde53b37..51f811dd66a 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 b22d478b2df..c321912485e 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.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.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 @@ /** * 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 static byte[] hashOfPaymentAccountPayload(PaymentAccountPayload paymentAc @Getter transient private Transaction depositTx; - // Persistable Immutable private final TradingPeer tradingPeer; private final String offerId; @@ -181,7 +180,7 @@ public ProcessModel(String offerId, String accountId, PubKeyRing pubKeyRing, Tra 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 static ProcessModel fromProto(protobuf.ProcessModel proto, CoreProtoResol 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 Transaction resolveTakeOfferFeeTx(Trade trade) { 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 void setPaymentStartedMessageState(MessageState paymentStartedMessageStat } } - void setDepositTxSentAckMessage(AckMessage ackMessage) { + public void setDepositTxSentAckMessage(AckMessage ackMessage) { MessageState messageState = ackMessage.isSuccess() ? MessageState.ACKNOWLEDGED : MessageState.FAILED; @@ -321,10 +326,6 @@ public void setDepositTxMessageState(MessageState messageState) { } } - 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 ebd37a24eab..192c0d9707d 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 @@ @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 00000000000..00574843118 --- /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 df3b8df2c63..a75613e9c04 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 aab0bb5a3b9..999b98ca293 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 b4eb80cf3db..7368553a464 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 @@ protected void run() { 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 5d925ae41c2..090c723d449 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 @@ private void applyConfidence(TransactionConfidence confidence) { 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 332b571f747..61a2f4a0534 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 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 68ea2d9eeba..74dcf65309c 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 @@ protected void run() { } 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 b35e1f53a62..93f0f69536a 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 cdc995c612c..c2aa5e6b1dc 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 30824a14d74..a4f27592ebd 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 @@ protected void run() { 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 19cdb957f79..0e0d0c04557 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 @@ protected void run() { 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 7e6149bc26f..0318f615546 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 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 @@ protected void run() { 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 @@ protected void run() { } // 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 5b546201afb..e01e118fcd8 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 BuyerProcessPayoutTxPublishedMessage(TaskRunner taskHandler, Trade 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 b594d646ce2..a5bc12088c8 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 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 @@ protected TradeMailboxMessage getTradeMailboxMessage(String tradeId) { @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 5b9702798bb..1f56c3dd69a 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 @@ protected void run() { 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 9076f809c5e..92edf3c56d6 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 9c6bfa2b744..3c0ea6a6ae5 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 c58ce41ef16..6aa2f3a46b3 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 92% 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 70dd1b8074a..1dba0f91694 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; @@ -60,7 +60,7 @@ protected void run() { 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()); @@ -68,7 +68,7 @@ protected void run() { 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 cab7fc896ba..c2e9e891aae 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 @@ protected void run() { 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 b2a5eaf1b32..0cca5d82e68 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 5f259b55740..dc536ffa9c9 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 @@ protected void run() { 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 93% 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 5a7ad8d919f..e42a2b53713 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; @@ -56,7 +56,7 @@ protected void run() { 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(); 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 85de45cf248..19890288090 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 92% 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 0277ca7b76b..b5198c494a5 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; 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 ab03ece48a2..a28bde602e4 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 @@ protected void run() { 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 93% 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 c195059b193..dfb06bace8b 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; @@ -77,7 +77,7 @@ protected void run() { Coin msOutputAmount = offer.getBuyerSecurityDeposit().add(offer.getSellerSecurityDeposit()).add(trade.getTxFee()) .add(checkNotNull(trade.getTradeAmount())); - 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 84% 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 1801d7b1edc..858694e4616 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 @@ protected void run() { 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,9 +69,10 @@ protected void run() { 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(), + offerPayload, checkNotNull(trade.getTradeAmount()).value, trade.getTradePrice().getValue(), takerFeeTxId, @@ -94,7 +97,7 @@ protected void run() { 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 94% 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 e112627aba7..e59a5008465 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 @@ protected void run() { 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()) 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 6fe8c587698..843f2d5d4da 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 44b338fffe6..0233e177995 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 @@ protected void run() { 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 fde4359becd..089bbec587c 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 @@ protected void run() { // 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 0c8a5fa67b8..6a70e2b61c8 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 7ebfa20ce53..cbdcbc1bca2 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 94% 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 875b78f7af4..4a6e4395af7 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,7 +52,7 @@ protected void run() { 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"); 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 ba0aad72043..b04324eef73 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 ProcessMediatedPayoutSignatureMessage(TaskRunner taskHandler, Trad 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 cb06ed645e8..08a2b65c361 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 51ff7267f93..e29b85267d1 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 f03baace1bb..a29217d4f1f 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 dc0b60b314a..86cc22fb8e0 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 93% 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 6699d832003..7738bf72447 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 @@ protected void run() { try { runInterceptHook(); - TradingPeer tradingPeer = processModel.getTradingPeer(); + TradingPeer tradingPeer = processModel.getTradePeer(); if (processModel.getMediatedPayoutTxSignature() != null) { log.warn("processModel.getTxSignatureFromMediation is already set"); } 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 d0a0f6c20d2..238056c346b 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 91% 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 a466f112643..0d255924383 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; 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 2683e526026..e3150f6c5d2 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 @@ protected void run() { 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 7dc721d935e..43f2b8b3b0a 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 SellerProcessCounterCurrencyTransferStartedMessage(TaskRunner task 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 33d60965a44..0b1360859d7 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 @@ protected void run() { 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 dc20761aaea..1659f1fde6b 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 @@ protected void run() { 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 1afae6dad39..4be5e257b97 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 89b6ac5a14a..d3bff19ef05 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 @@ protected void run() { // 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 58cfe2da58b..bde55550223 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 @@ protected void run() { 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 be6990835dc..ae65e10da65 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 b20b9654c9c..dc08270b6e8 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 94% 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 d6b0babc655..47a553de4cc 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; @@ -52,7 +52,7 @@ protected void run() { checkNotNull(trade.getTradeAmount(), "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(); 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 adabac92ebf..909ccc101cd 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 @@ protected void run() { 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 94% 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 b382fd01edb..fa215a8db29 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; @@ -55,7 +55,7 @@ protected void run() { BtcWalletService walletService = processModel.getBtcWalletService(); String id = processModel.getOffer().getId(); - TradingPeer tradingPeer = processModel.getTradingPeer(); + TradingPeer tradingPeer = processModel.getTradePeer(); Offer offer = checkNotNull(trade.getOffer()); // params 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 3902668f82e..fb42b6a6f74 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 @@ protected void run() { 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 0433347f84f..1e6d8162cc8 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 SellerAsMakerProcessDepositTxMessage(TaskRunner taskHandler, Trade 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 e22d7b3dc5c..b2bd8b3ac89 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 93% 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 7aa4118e6e9..452f4ecc681 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; 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 90% 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 52d93e25c62..638e91a44c7 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; @@ -75,7 +75,7 @@ protected void run() { Coin msOutputAmount = offer.getBuyerSecurityDeposit().add(offer.getSellerSecurityDeposit()).add(trade.getTxFee()) .add(checkNotNull(trade.getTradeAmount())); - TradingPeer tradingPeer = processModel.getTradingPeer(); + TradingPeer tradingPeer = processModel.getTradePeer(); Transaction depositTx = processModel.getTradeWalletService().takerSignsDepositTx( true, @@ -95,7 +95,7 @@ protected void run() { } 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 96% 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 e6468020083..de0f55e074d 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; @@ -88,7 +88,7 @@ protected void run() { processModel.getFundsNeededForTrade(), processModel.isUseSavingsWallet(), trade.getTxFee()); - transaction = processModel.getBsqWalletService().signTx(txWithBsqFee); + 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 ada1866d359..f91ac655c53 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 @@ protected void run() { 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 @@ protected void run() { 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 6324f74e000..45f467980de 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 95% 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 695971db4d3..ba7c683be84 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; @@ -142,7 +142,7 @@ protected void run() { 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 84% 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 7812eec9390..fc1080ad87f 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 @@ protected void run() { 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 @@ protected void run() { 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); @@ -80,8 +83,9 @@ protected void run() { String takersPaymentMethodId = checkNotNull(processModel.getPaymentAccountPayload(trade)).getPaymentMethodId(); Coin tradeAmount = checkNotNull(trade.getTradeAmount()); + OfferPayload offerPayload = offer.getOfferPayload().orElseThrow(); Contract contract = new Contract( - processModel.getOffer().getOfferPayload(), + offerPayload, tradeAmount.value, trade.getTradePrice().getValue(), takerFeeTxId, @@ -106,10 +110,10 @@ protected void run() { 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 7d8d208d4d7..941cb6e73f6 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 00000000000..05bd43a1344 --- /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 00000000000..943806038e4 --- /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/core/src/main/java/bisq/core/trade/protocol/bsq_swap/BsqSwapBuyerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/BsqSwapBuyerProtocol.java new file mode 100644 index 00000000000..d8082250bb8 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/bsq_swap/BsqSwapBuyerProtocol.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.BsqSwapBuyerTrade; + +import lombok.extern.slf4j.Slf4j; + +@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 00000000000..b4013f9804a --- /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 00000000000..3588a32918b --- /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 00000000000..74694a4004f --- /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 00000000000..f055d7f32fe --- /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 00000000000..1d6a43a1349 --- /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 00000000000..8b339f0a1fe --- /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 00000000000..a23641c0774 --- /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 00000000000..fd046bae394 --- /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 00000000000..81363e6c8a3 --- /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 00000000000..6d405d4d9ae --- /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 00000000000..4d584973d3c --- /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 00000000000..64c4526e04d --- /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 00000000000..b760450fe24 --- /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 00000000000..bdf5dd88b2c --- /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 00000000000..d946c1c114c --- /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 00000000000..c4b0fa3a253 --- /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 00000000000..41da92f28d4 --- /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 00000000000..ffbf7bd7c29 --- /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 00000000000..60a0398f5cd --- /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 00000000000..9aa8747382e --- /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 00000000000..fc86eb37f39 --- /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 00000000000..3106591d934 --- /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 00000000000..e7a4ecd9fa4 --- /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 00000000000..f8b1d3d3cf8 --- /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 00000000000..cada3e20f62 --- /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 00000000000..d4d636431f7 --- /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.getTakerFee(); + } + + @Override + protected long getBuyersTradeFee() { + return trade.getMakerFee(); + } +} 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 00000000000..9444cf4b070 --- /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.getMakerFee(); + } + + @Override + protected long getSellersTradeFee() { + return trade.getTakerFee(); + } +} 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 00000000000..19b9410c673 --- /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.getTakerFee(); + } + + @Override + protected long getBuyersTradeFee() { + return trade.getMakerFee(); + } +} 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 00000000000..71a6059fcd5 --- /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 00000000000..cf89336891b --- /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 00000000000..57b982a8853 --- /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 00000000000..fb418844462 --- /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.getMakerFee(); + } + + @Override + protected long getBuyersTradeFee() { + return trade.getTakerFee(); + } +} 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 00000000000..40e11da98c1 --- /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.getTakerFee(); + } + + @Override + protected long getSellersTradeFee() { + return trade.getMakerFee(); + } +} 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 00000000000..b69ab157a5a --- /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.getMakerFee(); + } + + @Override + protected long getBuyersTradeFee() { + return trade.getTakerFee(); + } +} 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 00000000000..e50debb3ccd --- /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.getAmount(), + trade.getTxFeePerVbyte(), + trade.getMakerFee(), + trade.getTakerFee(), + 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 00000000000..3658b2dbb6f --- /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 00000000000..b80a9d67a36 --- /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 00000000000..2425644a8dc --- /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.getAmount(), + 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 00000000000..0790171ceb6 --- /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 00000000000..90bfe9c7fc8 --- /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 00000000000..36f3f5619f7 --- /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 00000000000..22bac6da897 --- /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.getTakerFee(); + } + + @Override + protected long getSellersTradeFee() { + return trade.getMakerFee(); + } +} 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 00000000000..0b73b437457 --- /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.getTakerFee(); + } + + @Override + protected long getSellersTradeFee() { + return trade.getMakerFee(); + } +} 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 00000000000..8c4c50e1828 --- /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 00000000000..6b124a84b02 --- /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 00000000000..7ab8df2baa6 --- /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.getMakerFee(); + } + + @Override + protected long getSellersTradeFee() { + return trade.getTakerFee(); + } +} 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 00000000000..244870e6c31 --- /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.getMakerFee(); + } + + @Override + protected long getSellersTradeFee() { + return trade.getTakerFee(); + } +} 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 00000000000..6c7dce99ece --- /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 00000000000..de4dc02685c --- /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 00000000000..64e7f9df312 --- /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.getAmount(), + trade.getTxFeePerVbyte(), + trade.getMakerFee(), + trade.getTakerFee(), + 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 f3a2d341593..00000000000 --- 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 864b08f9677..4b23449ad7e 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.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.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 @@ */ @Deprecated @Slf4j -@Value +@EqualsAndHashCode +@Getter public final class TradeStatistics2 implements ProcessOncePersistableNetworkPayload, PersistableNetworkPayload, CapabilityRequiringPayload, Comparable { @@ -93,7 +97,8 @@ public static TradeStatistics2 from(Trade trade, 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(), + OfferPayload offerPayload = offer.getOfferPayload().orElseThrow(); + return new TradeStatistics2(offerPayload, trade.getTradePrice(), trade.getTradeAmount(), trade.getDate(), @@ -106,7 +111,7 @@ public static TradeStatistics2 from(Trade trade, @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 TradeStatistics2(OfferPayload offerPayload, // PROTO BUFFER /////////////////////////////////////////////////////////////////////////////////////////// - public TradeStatistics2(OfferPayload.Direction direction, + public TradeStatistics2(OfferDirection direction, String baseCurrency, String counterCurrency, String offerPaymentMethod, @@ -204,12 +209,12 @@ public byte[] createHash() { // 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 protobuf.PersistableNetworkPayload toProtoMessage() { 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 fee029654b6..b0bbef5677a 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.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; @@ -107,12 +109,25 @@ public static TradeStatistics3 from(Trade trade, trade.getTradePrice().getValue(), trade.getTradeAmountAsLong(), 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.getAmount(), + 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 byte[] createHash() { // 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 044018c018b..6049e67e381 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.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.common.config.Config; import bisq.common.file.JsonFileManager; -import bisq.common.util.Utilities; import com.google.inject.Inject; @@ -131,13 +132,13 @@ private void maybeDumpStatistics() { 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 @@ private void maybeDumpStatistics() { .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 @@ private void maybeDumpStatistics() { .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; - } - - 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 (!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); - }); + 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; + } + + // 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; + } + + 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 6d386437bed..79191d3968b 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 @@ 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; 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 2d0efe887d4..04eefabd9e9 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.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; 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 469b1b4fc49..14f126554d2 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.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.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.ClosedTradableManager; +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 @@ private void onP2pNetworkAndWalletReady() { // 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 @@ private void onP2pNetworkAndWalletReady() { 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 @@ private void processTrades(List trades) { // 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 @@ private boolean wasTxKeyReUsed(Trade trade, List activeTrades) { // 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/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index 30e93dc8009..394185c5c98 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -800,6 +800,11 @@ public void setNotifyOnPreRelease(boolean value) { requestPersistence(); } + public void setUseDaoMonitor(boolean value) { + prefPayload.setUseDaoMonitor(value); + requestPersistence(); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Getter @@ -1115,5 +1120,7 @@ private interface ExcludesDelegateMethods { void setDenyApiTaker(boolean value); void setNotifyOnPreRelease(boolean value); + + void setUseDaoMonitor(boolean value); } } diff --git a/core/src/main/java/bisq/core/user/PreferencesPayload.java b/core/src/main/java/bisq/core/user/PreferencesPayload.java index 3eb6ecfa7ba..155ee54abc6 100644 --- a/core/src/main/java/bisq/core/user/PreferencesPayload.java +++ b/core/src/main/java/bisq/core/user/PreferencesPayload.java @@ -134,6 +134,7 @@ public final class PreferencesPayload implements PersistableEnvelope { private boolean showOffersMatchingMyAccounts; private boolean denyApiTaker; private boolean notifyOnPreRelease; + private boolean useDaoMonitor; /////////////////////////////////////////////////////////////////////////////////////////// // Constructor @@ -201,7 +202,8 @@ public Message toProtoMessage() { .setHideNonAccountPaymentMethods(hideNonAccountPaymentMethods) .setShowOffersMatchingMyAccounts(showOffersMatchingMyAccounts) .setDenyApiTaker(denyApiTaker) - .setNotifyOnPreRelease(notifyOnPreRelease); + .setNotifyOnPreRelease(notifyOnPreRelease) + .setUseDaoMonitor(useDaoMonitor); Optional.ofNullable(backupDirectory).ifPresent(builder::setBackupDirectory); Optional.ofNullable(preferredTradeCurrency).ifPresent(e -> builder.setPreferredTradeCurrency((protobuf.TradeCurrency) e.toProtoMessage())); @@ -299,7 +301,8 @@ public static PreferencesPayload fromProto(protobuf.PreferencesPayload proto, Co proto.getHideNonAccountPaymentMethods(), proto.getShowOffersMatchingMyAccounts(), proto.getDenyApiTaker(), - proto.getNotifyOnPreRelease() + proto.getNotifyOnPreRelease(), + proto.getUseDaoMonitor() ); } } diff --git a/core/src/main/java/bisq/core/user/User.java b/core/src/main/java/bisq/core/user/User.java index 04edaf82b5c..22555eef917 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 @@ 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 @@ private void init() { 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 00000000000..5ae440ba29e --- /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/core/src/main/java/bisq/core/util/Validator.java b/core/src/main/java/bisq/core/util/Validator.java index 52da4927745..98cb013c712 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 3f8b16bda0f..616f33907c6 100644 --- a/core/src/main/java/bisq/core/util/coin/CoinUtil.java +++ b/core/src/main/java/bisq/core/util/coin/CoinUtil.java @@ -96,14 +96,22 @@ public static Coin getPercentOfAmountAsCoin(double percent, Coin amount) { * @param amount the amount of BTC to trade * @return the maker fee for the given trade amount, or {@code null} if the amount is {@code null} */ - @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 { - return null; + Coin minMakerFee = FeeService.getMinMakerFee(isCurrencyForMakerFeeBtc); + if (amount == null) { + return minMakerFee; } + Coin feePerBtc = getFeePerBtc(FeeService.getMakerFeePerBtc(isCurrencyForMakerFeeBtc), amount); + return maxCoin(feePerBtc, minMakerFee); + } + + public static Coin getTakerFee(boolean isCurrencyForTakerFeeBtc, @Nullable Coin amount) { + Coin minTakerFee = FeeService.getMinTakerFee(isCurrencyForTakerFeeBtc); + if (amount == null) { + return minTakerFee; + } + Coin feePerBtc = getFeePerBtc(FeeService.getTakerFeePerBtc(isCurrencyForTakerFeeBtc), amount); + return maxCoin(feePerBtc, minTakerFee); } /** diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index d6106e5559e..1a0e18d1675 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/BSQ SWAP TRADES\" + # 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=BSQ swap trades portfolio.tab.failed=Failed portfolio.tab.editOpenOffer=Edit offer portfolio.tab.duplicateOffer=Duplicate offer @@ -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 #################################################################### @@ -1283,6 +1334,7 @@ setting.preferences.dao.resyncFromGenesis.popup=A resync from genesis transactio setting.preferences.dao.resyncFromGenesis.resync=Resync from genesis and shutdown setting.preferences.dao.isDaoFullNode=Run Bisq as DAO full node setting.preferences.dao.activated=DAO activated +setting.preferences.dao.useDaoStateMonitoring=Use DAO state monitoring setting.preferences.dao.activated.popup=The change will be applied after a restart setting.preferences.dao.rpcUser=RPC username setting.preferences.dao.rpcPw=RPC password @@ -2372,6 +2424,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\ @@ -2427,10 +2484,13 @@ dao.monitor.proposals=Proposals state dao.monitor.blindVotes=Blind votes state dao.monitor.table.peers=Peers +dao.monitor.table.isSelfConstructed=My hash dao.monitor.table.conflicts=Conflicts dao.monitor.state=Status dao.monitor.requestAlHashes=Request all hashes dao.monitor.resync=Resync DAO state +dao.monitor.activated=DAO state monitoring activated +dao.monitor.deactivated=DAO state monitoring deactivated dao.monitor.table.header.cycleBlockHeight=Cycle / block height dao.monitor.table.cycleBlockHeight=Cycle {0} / block {1} dao.monitor.table.seedPeers=Seed node: {0} @@ -2460,7 +2520,14 @@ dao.monitor.isInConflictWithSeedNode=Your local data is not in consensus with at Please resync the DAO state. dao.monitor.isInConflictWithNonSeedNode=One of your peers is not in consensus with the network but your node \ is in sync with the seed nodes. +dao.monitor.isDaoStateBlockChainNotConnecting=You dao state chain is not connecting with the new data. \ + Please resync the DAO state. dao.monitor.daoStateInSync=Your local node is in consensus with the network +dao.monitor.reactivatedDaoStateMonitoring=Dao state monitoring is re-activated. You might need to resync the DAO state. +dao.monitor.deactivatedDaoStateMonitoring=Dao state monitoring is deactivated +dao.monitor.activate.popup.info=If DAO state monitoring is activated BSQ block parsing required considerable \ + more time. This can degrade user experience if there are many blocks to sync. For users who are regularily using Bisq\ + \this should not be an issue. Having DAO state monitoring activated provides better security. dao.monitor.blindVote.headline=Blind votes state dao.monitor.blindVote.table.headline=Chain of blind vote state hashes @@ -2691,6 +2758,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 +2831,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 +2907,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,6 +3115,8 @@ 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. #################################################################### @@ -3150,6 +3224,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/BSQ SWAP TRADES\" +navigation.portfolio.bsqSwapTrades.short=\"BSQ SWAP TRADES\" navigation.funds.depositFunds=\"Funds/Receive funds\" navigation.settings.preferences=\"Settings/Preferences\" # suppress inspection "UnusedProperty" @@ -3287,6 +3363,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 +3941,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 +4029,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 +4122,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/main/resources/i18n/displayStrings_cs.properties b/core/src/main/resources/i18n/displayStrings_cs.properties index 91443a812fc..f7795f41121 100644 --- a/core/src/main/resources/i18n/displayStrings_cs.properties +++ b/core/src/main/resources/i18n/displayStrings_cs.properties @@ -166,8 +166,8 @@ shared.tradeAmount=Výše obchodu shared.tradeVolume=Objem obchodu shared.invalidKey=Vložený klíč není správný shared.enterPrivKey=Pro odemknutí vložte privátní klíč -shared.makerFeeTxId=ID transakčního poplatku tvůrce -shared.takerFeeTxId=ID transakčního poplatku příjemce +shared.makerFeeTxId=ID transakce s poplatkem tvůrce +shared.takerFeeTxId=ID transakce s poplatkem příjemce shared.payoutTxId=ID platební transakce shared.contractAsJson=Kontakt v JSON formátu shared.viewContractAsJson=Zobrazit kontrakt v JSON formátu @@ -284,6 +284,7 @@ mainView.walletServiceErrorMsg.rejectedTxException=Transakce byla ze sítě zam mainView.networkWarning.allConnectionsLost=Ztratili jste připojení ke všem {0} síťovým peer nodům.\nMožná jste ztratili připojení k internetu nebo byl váš počítač v pohotovostním režimu. mainView.networkWarning.localhostBitcoinLost=Ztratili jste připojení k Bitcoinovému localhost nodu.\nRestartujte aplikaci Bisq a připojte se k jiným Bitcoinovým nodům nebo restartujte Bitcoinový localhost node. +mainView.networkWarning.clockWatcher=Váš počítač byl uspán {0} sekund. Režim spánku může způsobovat selhání obchodů. Aby Bisq fungoval správně, je potřeba v nastavení počítače deaktivovat automatické přecházení do režimu spánku. mainView.version.update=(Dostupná aktualizace) @@ -624,7 +625,7 @@ portfolio.pending.step2_buyer.refTextWarn=Důležité: když vyplňujete platebn # suppress inspection "TrailingSpacesInProperty" portfolio.pending.step2_buyer.fees=Pokud vaše banka účtuje poplatky za převod, musíte tyto poplatky uhradit vy. # suppress inspection "TrailingSpacesInProperty" -portfolio.pending.step2_buyer.fees.swift=Make sure to use the SHA (shared fee model) to send the SWIFT payment. See more details at [HYPERLINK:https://bisq.wiki/SWIFT#Use_the_correct_fee_option]. +portfolio.pending.step2_buyer.fees.swift=K odeslání platby SWIFT nezapomeňte použít SHA (sdílené poplatky). Další podrobnosti najdete zde: [HYPERLINK:https://bisq.wiki/SWIFT#Use_the_correct_fee_option]. # suppress inspection "TrailingSpacesInProperty" portfolio.pending.step2_buyer.altcoin=Převeďte prosím z vaší externí {0} peněženky\n{1} prodejci BTC.\n\n # suppress inspection "TrailingSpacesInProperty" @@ -818,10 +819,10 @@ portfolio.pending.mediationResult.popup.selfAccepted.lockTimeOver=Přijali jste portfolio.pending.mediationResult.popup.openArbitration=Odmítnout a požádat o arbitráž portfolio.pending.mediationResult.popup.alreadyAccepted=Už jste přijali -portfolio.pending.failedTrade.taker.missingTakerFeeTx=Chybí poplatek příjemce transakce.\n\nBez tohoto tx nelze obchod dokončit. Nebyly uzamčeny žádné prostředky a nebyl zaplacen žádný obchodní poplatek. Tento obchod můžete přesunout do neúspěšných obchodů. -portfolio.pending.failedTrade.maker.missingTakerFeeTx=Chybí poplatek příjemce transakce.\n\nBez tohoto tx nelze obchod dokončit. Nebyly uzamčeny žádné prostředky. Vaše nabídka je stále k dispozici dalším obchodníkům, takže jste neztratili poplatek za vytvoření. Tento obchod můžete přesunout do neúspěšných obchodů. -portfolio.pending.failedTrade.missingDepositTx=Vkladová transakce (transakce 2-of-2 multisig) chybí.\n\nBez tohoto tx nelze obchod dokončit. Nebyly uzamčeny žádné prostředky, ale byl zaplacen váš obchodní poplatek. Zde můžete požádat o vrácení obchodního poplatku: [HYPERLINK:https://github.com/bisq-network/support/issues]\n\nKlidně můžete přesunout tento obchod do neúspěšných obchodů. -portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=Odložená výplatní transakce chybí, ale prostředky byly uzamčeny v vkladové transakci.\n\nNezasílejte prosím fiat nebo altcoin platbu prodejci BTC, protože bez odložené platby tx nelze zahájit arbitráž. Místo toho otevřete mediační úkol pomocí Cmd/Ctrl+o. Mediátor by měl navrhnout, aby oba partneři dostali zpět celou částku svých bezpečnostních vkladů (přičemž prodejce také obdrží plnou částku obchodu). Tímto způsobem nehrozí žádné bezpečnostní riziko a jsou ztraceny pouze obchodní poplatky.\n\nO vrácení ztracených obchodních poplatků můžete požádat zde: [HYPERLINK:https://github.com/bisq-network/support/issues] +portfolio.pending.failedTrade.taker.missingTakerFeeTx=Chybí transakce s poplatkem příjemce.\n\nBez této transakce nelze obchod dokončit. Nebyly uzamčeny žádné prostředky a nebyl zaplacen žádný obchodní poplatek. Tento obchod můžete přesunout do neúspěšných obchodů. +portfolio.pending.failedTrade.maker.missingTakerFeeTx=Chybí transakce s poplatkem příjemce.\n\nBez této transakce nelze obchod dokončit. Nebyly uzamčeny žádné prostředky. Vaše nabídka je stále k dispozici dalším obchodníkům, takže jste neztratili poplatek za vytvoření. Tento obchod můžete přesunout do neúspěšných obchodů. +portfolio.pending.failedTrade.missingDepositTx=Vkladová transakce (transakce 2-of-2 multisig) chybí.\n\nBez této transakce nelze obchod dokončit. Nebyly uzamčeny žádné prostředky, ale byl zaplacen váš obchodní poplatek. Zde můžete požádat o vrácení obchodního poplatku: [HYPERLINK:https://github.com/bisq-network/support/issues]\n\nKlidně můžete přesunout tento obchod do neúspěšných obchodů. +portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the fiat or altcoin payment to the BTC seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=Odložená výplatní transakce chybí, ale prostředky byly v depozitní transakci uzamčeny.\n\nPokud kupujícímu chybí také odložená výplatní transakce, bude poučen, aby platbu NEPOSLAL a místo toho otevřel mediační úkol. Měli byste také otevřít mediační úkol pomocí Cmd/Ctrl+o.\n\nPokud kupující ještě neposlal platbu, měl by zprostředkovatel navrhnout, aby oba partneři dostali zpět celou částku svých bezpečnostních vkladů (přičemž prodejce také obdrží plnou částku obchodu). Jinak by částka obchodu měla jít kupujícímu.\n\nO vrácení ztracených obchodních poplatků můžete požádat zde: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.errorMsgSet=Během provádění obchodního protokolu došlo k chybě.\n\nChyba: {0}\n\nJe možné, že tato chyba není kritická a obchod lze dokončit normálně. Pokud si nejste jisti, otevřete si mediační úkol a získejte radu od mediátorů Bisq.\n\nPokud byla chyba kritická a obchod nelze dokončit, možná jste ztratili obchodní poplatek. O vrácení ztracených obchodních poplatků požádejte zde: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.missingContract=Obchodní kontrakt není stanoven.\n\nObchod nelze dokončit a možná jste ztratili poplatek za obchodování. Pokud ano, můžete požádat o vrácení ztracených obchodních poplatků zde: [HYPERLINK:https://github.com/bisq-network/support/issues] @@ -996,7 +997,9 @@ support.buyerTaker=Kupující BTC/Příjemce support.sellerTaker=Prodávající BTC/Příjemce support.backgroundInfo=Bisq není společnost, takže spory řeší jinak.\n\nObchodníci mohou v rámci aplikace komunikovat prostřednictvím zabezpečeného chatu na obrazovce otevřených obchodů a zkusit řešení sporů sami. Pokud to nestačí, může jim pomoci mediátor. Mediátor vyhodnotí situaci a navrhne vyúčtování obchodních prostředků. Pokud oba obchodníci přijmou tento návrh, je výplata dokončena a obchod je uzavřen. Pokud jeden nebo oba obchodníci nesouhlasí s výplatou navrhovanou mediátorem, mohou požádat o rozhodčí řízení. Rozhodce přehodnotí situaci a v odůvodněných případech vrátí osobně prostředky obchodníkovi zpět a požádá o vrácení této platby od Bisq DAO. -support.initialInfo=Do níže uvedeného textového pole zadejte popis problému. Přidejte co nejvíce informací k urychlení doby řešení sporu.\n\nZde je kontrolní seznam informací, které byste měli poskytnout:\n\t● Pokud kupujete BTC: Provedli jste převod Fiat nebo Altcoinu? Pokud ano, klikli jste v aplikaci na tlačítko „Platba zahájena“?\n\t● Pokud jste prodejcem BTC: Obdrželi jste platbu Fiat nebo Altcoinu? Pokud ano, klikli jste v aplikaci na tlačítko „Platba přijata“?\n\t● Kterou verzi Bisq používáte?\n\t● Jaký operační systém používáte?\n\t● Pokud se vyskytl problém s neúspěšnými transakcemi, zvažte přechod na nový datový adresář.\n\t Někdy dojde k poškození datového adresáře a vede to k podivným chybám.\n\tViz: https://bisq.wiki/Switching_to_a_new_data_directory\n\nSeznamte se prosím se základními pravidly procesu sporu:\n\t● Musíte odpovědět na požadavky {0} do 2 dnů.\n\t● Mediátoři reagují do 2 dnů. Rozhodci odpoví do 5 pracovních dnů.\n\t● Maximální doba sporu je 14 dní.\n\t● Musíte spolupracovat s {1} a poskytnout informace, které požaduje, aby jste vyřešili váš případ.\n\t● Při prvním spuštění aplikace jste přijali pravidla uvedena v dokumentu sporu v uživatelské smlouvě.\n\nDalší informace o procesu sporu naleznete na: {2} +support.initialInfo=Do níže uvedeného textového pole zadejte popis problému. Přidejte co nejvíce informací k urychlení doby řešení sporu.\n\nZde je kontrolní seznam informací, které byste měli poskytnout:\n\t● Pokud kupujete BTC: Provedli jste převod Fiat nebo Altcoinu? Pokud ano, klikli jste v aplikaci na tlačítko „Platba zahájena“?\n\t● Pokud jste prodejcem BTC: Obdrželi jste platbu Fiat nebo Altcoinu? Pokud ano, klikli jste v aplikaci na tlačítko „Platba přijata“?\n\t● Kterou verzi Bisq používáte?\n\t● Jaký operační systém používáte?\n\t● Pokud se vyskytl problém s neúspěšnými transakcemi, zvažte přechod na nový datový adresář.\n\t Někdy dojde k poškození datového adresáře a vede to k podivným chybám.\n\tViz: https://bisq.wiki/Switching_to_a_new_data_directory\n\nSeznamte se prosím se základními pravidly procesu sporu:\n\t● Musíte odpovědět na požadavky {0} do 2 dnů.\n\t● {1}\n\t● Maximální trvání sporu je 14 dní.\n\t● Musíte spolupracovat s {2} a poskytnout informace, které požaduje, aby jste obhájili svou pozici.\n\t● Při prvním spuštění aplikace jste přijali pravidla uvedena v dokumentu sporu v uživatelské smlouvě.\n\nDalší informace o procesu sporu naleznete na: {3} +support.initialMediatorMsg=Mediátoři vám obvykle odpoví do 24 hodin.\n\t Pokud jste do 48 hodin nedostali odpověď, neváhejte se obrátit na svého mediátora na Keybase.\n\t Uživatelská jména mediátorů na Keybase jsou stejná jako jejich uživatelská jména v aplikaci Bisq.\n\t Váš mediátor je: {0} +support.initialArbitratorMsg=Rozhodci vám obvykle odpoví do 5 dnů.\n\t Pokud jste do 7 dnů nedostali odpověď, neváhejte se obrátit na svého rozhodce na Keybase.\n\t Uživatelská jména rozhodců na Keybase jsou stejná jako jejich uživatelská jména v aplikaci Bisq.\n\t Váš rozhodce je: {0} support.systemMsg=Systémová zpráva: {0} support.youOpenedTicket=Otevřeli jste žádost o podporu.\n\n{0}\n\nVerze Bisq: {1} support.youOpenedDispute=Otevřeli jste žádost o spor.\n\n{0}\n\nVerze Bisq: {1} @@ -1064,6 +1067,8 @@ setting.preferences.dao.resyncFromResources.popup=Po restartu aplikace budou dat setting.preferences.dao.resyncFromGenesis.popup=Resynchronizace z genesis transakce může stát značné množství času a prostředků CPU. Opravdu to chcete udělat? Většinou je resynchronizace z nejnovějších zdrojových souborů dostatečná a mnohem rychlejší.\n\nPokud budete pokračovat, po restartu aplikace budou data správy sítě Bisq znovu načtena z počátečních uzlů a stav konsensu BSQ bude znovu vytvořen z genesis transakce. setting.preferences.dao.resyncFromGenesis.resync=Resynchronizovat z genesis transakce a vypnout setting.preferences.dao.isDaoFullNode=Spusťte Bisq jako full node DAO +setting.preferences.dao.activated=DAO aktivováno +setting.preferences.dao.activated.popup=Změna se projeví po restartu setting.preferences.dao.rpcUser=Uživatelské jméno RPC setting.preferences.dao.rpcPw=RPC heslo setting.preferences.dao.blockNotifyPort=Blokovat oznamovací port @@ -1500,7 +1505,7 @@ dao.param.ASSET_LISTING_FEE_PER_DAY=Poplatek za vedení aktiva za den dao.param.ASSET_MIN_VOLUME=Min. objem obchodu s aktivy # suppress inspection "UnusedProperty" -dao.param.LOCK_TIME_TRADE_PAYOUT=Doba uzamčení pro alternativní výplaty obchodu tx +dao.param.LOCK_TIME_TRADE_PAYOUT=Doba uzamčení pro transakci alternativní výplaty # suppress inspection "UnusedProperty" dao.param.ARBITRATOR_FEE=Poplatek rozhodce v BTC @@ -1598,7 +1603,7 @@ dao.bond.bondState.LOCKUP_TX_CONFIRMED=Zamčený úpis # suppress inspection "UnusedProperty" dao.bond.bondState.UNLOCK_TX_PENDING=Odemčení čeká na vyřízení # suppress inspection "UnusedProperty" -dao.bond.bondState.UNLOCK_TX_CONFIRMED=Odemknutí tx potvrzeno +dao.bond.bondState.UNLOCK_TX_CONFIRMED=Odemykací transakce potvrzena # suppress inspection "UnusedProperty" dao.bond.bondState.UNLOCKING=Odblokování úpisů # suppress inspection "UnusedProperty" @@ -1922,15 +1927,10 @@ dao.news.bisqDAO.title=BISQ DAO dao.news.bisqDAO.description=Stejně jako je burza Bisq decentralizovaná a odolná vůči cenzuře, tak její model řízení - a Bisq DAO a BSQ token jsou nástroje, které to umožňují. dao.news.bisqDAO.readMoreLink=Dozvědět se více o Bisq DAO -dao.news.pastContribution.title=PŘISPĚLI JSTE V MINULOSTI? POŽÁDEJTE O BSQ -dao.news.pastContribution.description=Pokud jste přispěli do projektu Bisq, použijte prosím níže uvedenou adresu BSQ a požádejte o účast na distribuci prvních BSQ. -dao.news.pastContribution.yourAddress=Adresa vaší BSQ peněženky -dao.news.pastContribution.requestNow=Požádat hned - -dao.news.daoInfo.title=SPUSŤTE BISQ DAO NA NAŠEM TESTNETU -dao.news.daoInfo.description=Síť Bisq DAO ještě nebyla spuštěn, ale o Bisq DAO se můžete dozvědět jeho spuštěním na našem testnetu. -dao.news.daoInfo.firstSection.title=1. Přepněte do režimu DAO Testnet -dao.news.daoInfo.firstSection.content=Na obrazovce Nastavení přepněte na DAO Testnet. +dao.news.daoInfo.title=POVOLIT BISQ DAO +dao.news.daoInfo.description=Abyste se mohli účastnit Bisq DAO a používat BSQ pro úhradu zlevněných obchodních poplatků, musíte povolit DAO. Když je DAO povoleno, Bisq stáhne všechny chybějící bloky a ověří BSQ transakce. Tento ověřovací proces vyžaduje určitý čas, během kterého můžete pozorovat, že Bisq využívá spoustu paměti a výpočetního výkonu. To je normální. +dao.news.daoInfo.firstSection.title=1. Povolit DAO +dao.news.daoInfo.firstSection.content=Povolte Bisq DAO a restartujte aplikaci. dao.news.DAOOnTestnet.secondSection.title=2. Získejte některé BSQ dao.news.DAOOnTestnet.secondSection.content=Vyžádejte si BSQ na Slacku nebo kupte BSQ na Bisq. dao.news.DAOOnTestnet.thirdSection.title=3. Zúčastněte se hlasovacího cyklu @@ -2050,10 +2050,10 @@ inputControlWindow.balanceLabel=Dostupný zůstatek contractWindow.title=Podrobnosti o sporu contractWindow.dates=Datum nabídky / Datum obchodu -contractWindow.btcAddresses=Bitcoinová adresa kupujícího BTC / prodávajícího BTC -contractWindow.onions=Síťová adresa kupující BTC / prodávající BTC -contractWindow.accountAge=Stáří účtu BTC kupující / BTC prodejce -contractWindow.numDisputes=Počet sporů BTC kupující / BTC prodejce +contractWindow.btcAddresses=Bitcoinová adresa BTC kupce / BTC prodejce +contractWindow.onions=Síťová adresa BTC kupce / BTC prodejce +contractWindow.accountAge=Stáří účtu BTC kupce / BTC prodejce +contractWindow.numDisputes=Počet sporů BTC kupce / BTC prodejce contractWindow.contractHash=Hash kontraktu displayAlertMessageWindow.headline=Důležitá informace! @@ -2224,7 +2224,7 @@ showWalletDataWindow.walletData=Data peněženky showWalletDataWindow.includePrivKeys=Zahrnout soukromé klíče setXMRTxKeyWindow.headline=Prokázat odeslání XMR -setXMRTxKeyWindow.note=Přidání tx informací níže umožní automatické potvrzení pro rychlejší obchody. Zobrazit více: https://bisq.wiki/Trading_Monero +setXMRTxKeyWindow.note=Přidání informací o transakci níže umožní automatické potvrzení pro rychlejší obchody. Zobrazit více: https://bisq.wiki/Trading_Monero setXMRTxKeyWindow.txHash=ID transakce (volitelné) setXMRTxKeyWindow.txKey=Transakční klíč (volitelný) @@ -2365,13 +2365,13 @@ popup.warning.disable.dao=Bisq DAO a BSQ jsou dočasně deaktivovány. Další i popup.warning.noFilter="We did not receive a filter object from the seed nodes." Toto je neočekávaná situace. Prosím upozorněte vývojáře Bisq. popup.warning.burnBTC=Tato transakce není možná, protože poplatky za těžbu {0} by přesáhly částku převodu {1}. Počkejte prosím, dokud nebudou poplatky za těžbu opět nízké nebo dokud nenahromadíte více BTC k převodu. -popup.warning.openOffer.makerFeeTxRejected=Transakční poplatek tvůrce za nabídku s ID {0} byl bitcoinovou sítí odmítnut.\nID transakce = {1}.\nNabídka byla odstraněna, aby se předešlo dalším problémům.\nPřejděte do \"Nastavení/Informace o síti\" a proveďte synchronizaci SPV.\nPro další pomoc prosím kontaktujte podpůrný kanál v Bisq Keybase týmu. +popup.warning.openOffer.makerFeeTxRejected=Transakce s poplatkem tvůrce za nabídku s ID {0} byl bitcoinovou sítí odmítnut.\nID transakce = {1}.\nNabídka byla odstraněna, aby se předešlo dalším problémům.\nPřejděte do \"Nastavení/Informace o síti\" a proveďte synchronizaci SPV.\nPro další pomoc prosím kontaktujte podpůrný kanál v Bisq Keybase týmu. popup.warning.trade.txRejected.tradeFee=obchodní poplatek popup.warning.trade.txRejected.deposit=vklad popup.warning.trade.txRejected=Bitcoinová síť odmítla {0} transakci pro obchod s ID {1}.\nID transakce = {2}\nObchod byl přesunut do neúspěšných obchodů.\nPřejděte do části \"Nastavení/Informace o síti\" a proveďte synchronizaci SPV.\nPro další pomoc prosím kontaktujte podpůrný kanál v Bisq Keybase týmu. -popup.warning.openOfferWithInvalidMakerFeeTx=Transakční poplatek tvůrce za nabídku s ID {0} je neplatný.\nID transakce = {1}.\nPřejděte do \"Nastavení/Informace o síti\" a proveďte synchronizaci SPV.\nPro další pomoc prosím kontaktujte podpůrný kanál v Bisq Keybase týmu. +popup.warning.openOfferWithInvalidMakerFeeTx=Transakce s poplatkem tvůrce za nabídku s ID {0} je neplatný.\nID transakce = {1}.\nPřejděte do \"Nastavení/Informace o síti\" a proveďte synchronizaci SPV.\nPro další pomoc prosím kontaktujte podpůrný kanál v Bisq Keybase týmu. popup.info.securityDepositInfo=Aby oba obchodníci dodržovali obchodní protokol, musí oba obchodníci zaplatit kauci.\n\nTento vklad je uložen ve vaší obchodní peněžence, dokud nebude váš obchod úspěšně dokončen a poté vám bude vrácen.\n\nPoznámka: Pokud vytváříte novou nabídku, musí program Bisq běžet, aby ji převzal jiný obchodník. Chcete-li zachovat své nabídky online, udržujte Bisq spuštěný a ujistěte se, že tento počítač zůstává online (tj. Zkontrolujte, zda se nepřepne do pohotovostního režimu...pohotovostní režim monitoru je v pořádku). @@ -2679,26 +2679,27 @@ payment.secret=Tajná otázka payment.answer=Odpověď payment.wallet=ID peněženky payment.capitual.cap=CAP kód - -# suppress inspection "UnusedProperty" -payment.swift.headline=International SWIFT Wire Transfer -payment.swift.title.bank=Receiving Bank -payment.swift.title.intermediary=Intermediary Bank (click to expand) -payment.swift.country.bank=Receiving Bank Country -payment.swift.country.intermediary=Intermediary Bank Country -payment.swift.swiftCode.bank=Receiving Bank SWIFT Code -payment.swift.swiftCode.intermediary=Intermediary Bank SWIFT Code -payment.swift.name.bank=Receiving Bank name -payment.swift.name.intermediary=Intermediary Bank name -payment.swift.branch.bank=Receiving Bank branch -payment.swift.branch.intermediary=Intermediary Bank branch -payment.swift.address.bank=Receiving Bank address -payment.swift.address.intermediary=Intermediary Bank address -payment.swift.address.beneficiary=Beneficiary address -payment.swift.phone.beneficiary=Beneficiary phone number -payment.swift.account=Account No. (or IBAN) -payment.swift.use.intermediary=Use Intermediary Bank -payment.swift.showPaymentInfo=Show Payment Information... +payment.upi.virtualPaymentAddress=Virtual Payment Address + +# suppress inspection "UnusedProperty" +payment.swift.headline=Mezinárodní převod SWIFT +payment.swift.title.bank=Banka příjemce +payment.swift.title.intermediary=Zprostředkující banka (kliknutím rozbalíte) +payment.swift.country.bank=Země banky příjemce +payment.swift.country.intermediary=Země zprostředkující banky +payment.swift.swiftCode.bank=Kód SWIFT banky příjemce +payment.swift.swiftCode.intermediary=Kód SWIFT zprostředkující banky +payment.swift.name.bank=Jméno banky příjemce +payment.swift.name.intermediary=Jméno zprostředkující banky +payment.swift.branch.bank=Pobočka banky příjemce +payment.swift.branch.intermediary=Pobočka zprostředkující banky +payment.swift.address.bank=Adresa banky příjemce +payment.swift.address.intermediary=Adresa zprostředkující banky +payment.swift.address.beneficiary=Adresa příjemce +payment.swift.phone.beneficiary=Telefonní číslo příjemce +payment.swift.account=Číslo účtu (nebo IBAN) +payment.swift.use.intermediary=Použít zprostředkující banku +payment.swift.showPaymentInfo=Zobrazit informace o platbě... payment.amazon.site=Kupte Amazon eGift zde: payment.ask=Zjistěte pomocí obchodního chatu @@ -2767,15 +2768,69 @@ payment.amazonGiftCard.upgrade=Platba kartou Amazon eGift nyní vyžaduje také payment.account.amazonGiftCard.addCountryInfo={0}\nVáš stávající účet pro platbu kartou Amazon eGift ({1}) nemá nastavenou zemi.\nVyberte prosím zemi, ve které je možné vaše karty Amazon eGift uplatnit.\nTato aktualizace vašeho účtu nebude mít vliv na stáří tohoto účtu. payment.amazonGiftCard.upgrade.headLine=Aktualizace účtu pro platbu kartou Amazon eGift -payment.swift.info=Carefully review the core guidelines for using SWIFT on Bisq:\n\n- fill all fields completely and accurately \n- buyer must send payment in currency specified by the offer maker \n- buyer must use the shared fee model (SHA) \n- buyer and seller may incur fees, so they should check their bank's fee schedules beforehand \n\nSWIFT is more sophisticated than other payment methods, so please take a moment to review full guidance on the wiki [HYPERLINK:https://bisq.wiki/SWIFT]. +payment.swift.info.account=Pečlivě si prostudujte základní pokyny pro používání SWIFT na Bisq:\n\n- pečlivě vyplňte všechna pole\n- kupující musí zaslat platbu v měně určené tvůrcem nabídky\n- kupující musí zvolit sdílené poplatky (SHA)\n- kupujícímu a prodávajícímu mohou být účtovány poplatky, proto by si měli předem ověřit ceník své banky\n\nSWIFT je komplikovanější než jiné platební metody, proto si prosím prostudujte úplné pokyny na wiki [HYPERLINK:https://bisq.wiki/SWIFT]. + +payment.swift.info.buyer=Chcete-li koupit bitcoin pomocí SWIFT, musíte:\n\n- zaslat platbu v měně určené tvůrcem nabídky\n- použít sdílený poplatek (SHA)\n\nPřečtěte si prosím další pokyny na wiki, abyste se vyhnuli sankcím a zajistili bezproblémový průběh obchodů [HYPERLINK:https://bisq.wiki/SWIFT]. + +payment.swift.info.seller=SWIFT senders are required to use the shared fee model (SHA) to send payments.\n\nIf you receive a SWIFT payment that does not use SHA, open a mediation ticket.\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. + +payment.imps.info.account=Please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a maximum of Rs. 200,000 that can be sent per transaction. If you are trading over this amount multiple transactions will be needed. However be aware their is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nSome banks have different limits for their customers. +payment.imps.info.buyer=Please send payment only to the account details provided in Bisq.\n\nThe maximum trade size is Rs. 200,000 per transaction.\n\nIf your trade is over Rs. 200,000 you will have to make multiple transfers. However be aware their is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nPlease note some banks have different limits for their customers. +payment.imps.info.seller=If you intend to receive over Rs. 200,000 per trade you should expect the buyer to have to make multiple transfers. However be aware there is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nPlease note some banks have different limits for their customers. + +payment.neft.info.account=Please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a maximum of Rs. 50,000 that can be sent per transaction. If you are trading over this amount multiple transactions will be needed.\n\nPlease note some banks have different limits for their customers. +payment.neft.info.buyer=Please send payment only to the account details provided in Bisq.\n\nThe maximum trade size is Rs. 50,000 per transaction.\n\nIf your trade is over Rs. 50,000 you will have to make multiple transfers.\n\nPlease note some banks have different limits for their customers. +payment.neft.info.seller=If you intend to receive over Rs. 50,000 per trade you should expect the buyer to have to make multiple transfers.\n\nPlease note some banks have different limits for their customers. + +payment.paytm.info.account=Please make sure to include your email or phone number that matches your email or phone number in your PayTM account. \n\nWhen users set up a PayTM account with No KYC users are limited to: \n\n● Maximum of Rs. 5,000 can be sent per transaction.\n● Maximum of Rs. 10,000 can be held in someone's PayTM wallet.\n\nIf you intend to trade amount of over 5,000 per trade you will need to complete KYC with PayTM. With KYC users are limited to:\n\n● Maximum of Rs. 100,000 can be sent per transaction.\n● Maximum of Rs. 100,000 can be held in someone's PayTM wallet.\n\nUsers should also be aware of account limits. Trades above PayTM account limits will likely have to take place over more than one day, or, be cancelled. +payment.paytm.info.buyer=Please send payment only to the email address or phone number provided.\n\nIf you intend to trade amount of over Rs. 5,000 per trade you will need to complete KYC with PayTM.\n\nWith No KYC Rs. 5,000 can be sent per transaction.\n\nWith KYC users Rs. 100,000 can be sent per transaction. +payment.paytm.info.seller=If you intend to trade amount of over Rs. 5,000 per trade you will need to complete KYC with PayTM. With KYC users are limited to:\n\n● Maximum of Rs. 100,000 can be sent per transaction.\n● Maximum of Rs. 100,000 can be held in your PayTM wallet .\n\nUsers should also be aware of account limits. As a maximum of Rs. 100,000 can be held in your PayTM wallet please make sure you transfer out your rupees regularly. + +payment.rtgs.info.account=RTGS is for payments of large trades of Rs. 200,000 or over.\n\nWhen setting up your RTGS payment account please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). +payment.rtgs.info.buyer=Please send payment only to the account details provided in Bisq.\n\nPlease be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). +payment.rtgs.info.seller=Please be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). + +payment.upi.info.account=Please make sure to include your Virtual Payment Address (VPA) also called your UPI ID. The format for this is like an email ID: with the sign “@” in the middle. For example, your UPI ID could be “receiver’s_name@bank_name” or “phone_number@bank_name.” \n\nFor UPI there is a maximum limit of Rs. 100,000 that can be sent per transaction. \n\nIf you intend to trade amount of over Rs. 100,000 per trade it is likely trades will have to take place over multiple transfers. \n\nPlease note some banks have different limits for their customers. +payment.upi.info.buyer=Please send payment only to the VPA / UPI ID provided in Bisq. \n\nThe maximum trade size is Rs. 100,000 per transaction. \n\nIf your trade is over Rs. 100,000 you will have to make multiple transfers. \n\nPlease note some banks have different limits for their customers. +payment.upi.info.seller=If you intend to receive over Rs. 100,000 per trade you should expect the buyer to have to make multiple transfers. \n\nPlease note some banks have different limits for their customers. + +payment.celpay.info.account=Please make sure to include the email your Celsius account is registered to. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account.\n\nCelPay users are limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Buyers can send any matching currency stablecoin to the BTC Seller. +payment.celpay.info.buyer=Please send payment only to the email address provided by the BTC Seller by sending a payment link.\n\nCelPay is limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Buyers can send any matching currency stablecoin to the BTC Seller. +payment.celpay.info.seller=BTC Sellers should expect to receive payment via a secure payment link. Please make sure the email payment link contains the email address provided by the BTC Buyer.\n\nCelPay users are limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Sellers should expect to receive any matching currency stablecoin from the BTC Buyer. It is possible for the BTC Buyer to send any matching currency stablecoin. +payment.celpay.supportedCurrenciesForReceiver=Supported currencies (please note: all the currencies below are supported stable coins within the Celcius app. Trades are for stable coins, not fiat.) -payment.swift.info.buyer=To buy bitcoin with SWIFT, you must:\n\n- send payment in the currency specified by the offer maker \n- use the shared fee model (SHA) to send payment\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. +payment.nequi.info.account=Please make sure to include your phone number that is associated with your Nequi account.\n\nWhen users set up a Nequi account payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. +payment.nequi.info.buyer=Please send payment only to the phone number provided in the BTC Seller's Bisq account.\n\nWhen users set up a Nequi account, payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. +payment.nequi.info.seller=Please check that the payment received matches the phone number provided in the BTC Buyer's Bisq account.\n\nWhen users set up a Nequi account, payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. -payment.swift.info.seller=SWIFT senders are required to use the shared payment model (SHA) to send payments.\n\nIf you receive a SWIFT payment that does not use SHA, open a mediation ticket.\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. +payment.bizum.info.account=To use Bizum you need a bank account (IBAN) in Spain and to be registered for the service.\n\nBizum can be used for trades between €0.50 and €1,000.\n\nThe maximum amount of transactions you can send/receive using Bizum is €2,000 Euros per day.\n\nBizum users can have a maximum of 150 operations per month.\n\nEach bank however may establish its own limits, within the above limits, for its clients.\n\nTraders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.bizum.info.buyer=Please send payment only to the BTC Seller's mobile phone number as provided in Bisq.\n\nThe maximum trade size is €1,000 per payment. The maximum amount of transactions you can send using Bizum is €2,000 Euros per day.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.bizum.info.seller=Please make sure your payment is received from the BTC Buyer's mobile phone number as provided in Bisq.\n\nThe maximum trade size is €1,000 per payment. The maximum amount of transactions you can receive using Bizum is €2,000 Euros per day.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. + +payment.pix.info.account=Please make sure to include your chosen Pix Key. There are four types of keys: CPF (Natural Persons Register) or CNPJ (National Registry of Legal Entities), e-mail address, telephone number or a random key generated by the system called a universally unique identifier (UUID). A different key must be used for each Pix account you have. Individuals can create up to five keys for each account they own.\n\nWhen trading on Bisq, BTC Buyers should use their Pix Keys as the payment description so that it is easy for the BTC Sellers to identify the payment as coming from themselves. +payment.pix.info.buyer=Please send payment only the Pix Key provided in the BTC Seller's Bisq account.\n\nPlease use your Pix Key as the payment reference so that it is easy for the BTC Seller to identify the payment as coming from yourself. +payment.pix.info.seller=Please check that the payment received description matches the Pix Key provided in the BTC Buyer's Bisq account. +payment.pix.key=Pix Key (CPF, CNPJ, Email, Phone number or UUID) + +payment.monese.info.account=Monese is a bank app for users of GBP, EUR and RON*. Monese allows users to send money to other Monese accounts instantly and for free in any supported currency.\n\n*To open a RON account in Monese, you need to either live in Romania or have Romanian citizenship.\n\nWhen setting up your Monese account in Bisq please make sure to include your name and phone number that matches your Monese account. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account. +payment.monese.info.buyer=Please send payment only to the phone number provided by the BTC Seller in their Bisq account. Please leave the payment description blank. +payment.monese.info.seller=BTC Sellers should expect to receive payment from the phone number / name shown in the BTC Buyer's Bisq account. + +payment.satispay.info.account=To use Satispay you need a bank account (IBAN) in Italy and to be registered for the service.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.satispay.info.buyer=Please send payment only to the BTC Seller's mobile phone number as provided in Bisq.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.satispay.info.seller=Please make sure your payment is received from the BTC Buyer's mobile phone number / name as provided in Bisq.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. + +payment.verse.info.account=Verse is a multiple currency payment method that can send and receive payment in EUR, SEK, HUF, DKK, PLN.\n\nWhen setting up your Verse account in Bisq please make sure to include the username that matches your username in your Verse account. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. +payment.verse.info.buyer=Please send payment only to the username provided by the BTC Seller in their Bisq account. Please leave the payment description blank.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. +payment.verse.info.seller=BTC Sellers should expect to receive payment from the username shown in the BTC Buyer's Bisq account.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. + +payment.strike.info.account=Please make sure to include your Strike username.\n\nIn Bisq, Strike is used for fiat to fiat payments only.\n\nPlease make sure you are aware of the Strike limits:\n\nUsers who have registered with only their email, name, and phone number have the following limits:\n\n● $100 maximum per deposit\n● $1,000 maximum total deposits per week\n● $100 maximum per payment\n\nUsers can increase their limits by providing Strike with more information. These users have the following limits:\n\n● $1,000 maximum per deposit\n● $1,000 maximum total deposits per week\n● $1,000 maximum per payment\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.strike.info.buyer=Please send payment only to the BTC Seller's Strike username as provided in Bisq.\n\nThe maximum trade size is $1,000 per payment.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.strike.info.seller=Please make sure your payment is received from the BTC Buyer's Strike username as provided in Bisq.\n\nThe maximum trade size is $1,000 per payment.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. payment.usPostalMoneyOrder.info=Obchodování pomocí amerických poštovních poukázek (USPMO) na Bisq vyžaduje, abyste rozuměli následujícímu:\n\n- Kupující BTC musí před odesláním napsat jméno prodejce BTC do polí plátce i příjemce a pořídit fotografii USPMO a obálku s dokladem o sledování ve vysokém rozlišení.\n- Kupující BTC musí odeslat USPMO prodejci BTC s potvrzením dodávky.\n\nV případě, že je nutná mediace, nebo pokud dojde k obchodnímu sporu, budete povinni poslat fotografie mediátorovi Bisq nebo zástupci pro vrácení peněz spolu s pořadovým číslem USPMO, číslem pošty a částkou dolaru, aby mohli ověřit podrobnosti na webu US Post Office.\n\nNeposkytnutí požadovaných informací mediátorovi nebo arbitrovi bude mít za následek ztrátu případu sporu.\n\nVe všech sporných případech nese odesílatel USPMO 100% břemeno odpovědnosti za poskytnutí důkazů mediátorovi nebo arbitrovi.\n\nPokud těmto požadavkům nerozumíte, neobchodujte pomocí USPMO na Bisq. -payment.cashByMail.info=Trading using cash-by-mail (CBM) on Bisq requires that you understand the following:\n\n● BTC buyer should package cash in a tamper-evident cash bag.\n● BTC buyer should film or take high-resolution photos of the cash packaging process with the address & tracking number already affixed to packaging.\n● BTC buyer should send the cash package to the BTC seller with Delivery Confirmation and appropriate Insurance.\n● BTC seller should film the opening of the package, making sure that the tracking number provided by the sender is visible in the video.\n● Offer maker must state any special terms or conditions in the 'Additional Information' field of the payment account.\n● Offer taker agrees to the offer maker's terms and conditions by taking the offer.\n\nCBM trades put the onus to act honestly squarely on both peers.\n\n● CBM trades have less verifiable actions than other fiat trades. This makes handling dispute much harder.\n● Try to resolve disputes directly with your peer using trader chat. This is your most promising route to solving any CBM dispute.\n● Mediators can consider your case and make a suggestion, but they are NOT guaranteed to help.\n● If a mediator is engaged, and if either peer rejects the mediator's suggestion, both peers' funds will be sent to a Bisq 'donation' address [HYPERLINK:https://bisq.wiki/Arbitration#Time-Locked_Payout_Transaction], and the trade will effectively be completed.\n● If a trader rejects a mediation suggestion and opens arbitration, it could lead to a loss of both the trading and the deposit funds.\n● Arbitrators will make a decision based on the evidence provided to them. Therefore, please follow and document the above processes to have evidence in case of dispute. For Cash by Mail trades the Arbitrators decision is final.\n● Reimbursement requests any lost funds resulting from Cash By Mail trades to the Bisq DAO will NOT be considered.\n\nTo be sure you fully understand the requirements of cash-by-mail trades, please see: [HYPERLINK:https://bisq.wiki/Cash_by_Mail]\n\nIf you do not understand these requirements, do not trade using CBM on Bisq. +payment.cashByMail.info=Obchodování metodou „hotovost poštou“ (cash-by-mail; CBM) vyžaduje, abyste porozuměli následujícímu:\n\n● Kupující BTC by měl zabalit hotovost do sáčku, který je určen pro zabalení cenností a který má opatření proti neoprávněné manipulaci.\n● Kupující by měl z procesu balení hotovosti pořídit video nebo fotografie ve vysokém rozlišení. Adresa a číslo zásilky musí být viditelné na obalu.\n● Kupující by měl odeslat balíček s hotovostí prodávajícímu doporučeně, a s odpovídajícím pojištěním.\n● Prodávající by měl otevření balíčku natočit tak, aby bylo na videu patrné číslo zásilky poskytnuté odesílatelem.\n● Tvůrce nabídky musí uvést jakékoli zvláštní podmínky v poli „Dodatečné informace“ platebního účtu.\n● Příjemce nabídky souhlasí s podmínkami tvůrce nabídky přijetím jeho nabídky.\n\nObchody CBM vyžadují, aby obě strany jednaly poctivě.\n\n● Jednotlivé kroky jsou hůře ověřitelné než u ostatních platebních metod. Díky tomu je řešení sporů mnohem obtížnější.\n● Pokuste se vyřešit spory přímo se svou protistranou pomocí obchodního chatu. Toto je vaše nejslibnější cesta k vyřešení jakéhokoli sporu.\n● Mediátoři mohou váš případ posoudit a navrhnout řešení, ale NENÍ zaručeno, že vám pomohou.\n● Pokud je do sporu zapojen mediátor a některá ze stran odmítne jeho návrh, prostředky obou partnerů budou zaslány na darovací adresu Bisq DAO [HYPERLINK:https://bisq.wiki/Arbitration#Time-Locked_Payout_Transaction] a obchod bude efektivně ukončen.\n● Pokud jeden z obchodníků zamítne návrh mediátora a zahájí arbitráž, může to vést až ke ztrátě obchodovaného BTC i kauce.\n● Rozhodci učiní své rozhodnutí na základě důkazů, které jim budou poskytnuty. Proto prosím dodržujte výše uvedené postupy, abyste měli důkazy pro případ sporu. U obchodů CBM je rozhodnutí rozhodců konečné.\n● Bisq DAO na žádosti o vyrovnání/odškodnění jakýchkoli ztracených finančních prostředků vyplývajících z obchodů CBM NEBUDE brát zřetel.\n\nChcete-li si být jisti, že plně rozumíte požadavkům obchodů CBM, viz: [HYPERLINK:https://bisq.wiki/Cash_by_Mail]\n\nPokud těmto požadavkům nerozumíte, neobchodujte pomocí CBM na Bisq. payment.cashByMail.contact=Kontaktní informace payment.cashByMail.contact.prompt=Obálka se jménem nebo pseudonymem by měla být adresována @@ -2788,10 +2843,12 @@ payment.shared.extraInfo=Dodatečné informace payment.shared.extraInfo.prompt=Uveďte jakékoli speciální požadavky, podmínky a detaily, které chcete zobrazit u vašich nabídek s tímto platebním účtem. (Uživatelé uvidí tyto informace předtím, než akceptují vaši nabídku.) payment.cashByMail.extraInfo.prompt=Uveďte prosím ve svých nabídkách:\n\nZemi, ve které se nacházíte (např. Francie);\nZemě / regiony protistran, se kterými jste ochotni obchodovat (např. Francie, EU nebo jakákoli evropská země);\nJakékoli zvláštní podmínky;\nJakékoli další podrobnosti. payment.cashByMail.tradingRestrictions=Přečtěte si prosím podmínky tvůrce nabídky.\nPokud tyto požadavky nesplňujete, neakceptujte tento obchod. -payment.f2f.info=Obchody „tváří v tvář“ mají různá pravidla a přicházejí s jinými riziky než online transakce.\n\nHlavní rozdíly jsou:\n● Obchodní partneři si musí vyměňovat informace o místě a čase schůzky pomocí poskytnutých kontaktních údajů.\n● Obchodní partneři musí přinést své notebooky a na místě setkání potvrdit „platba odeslána“ a „platba přijata“.\n● Pokud má tvůrce speciální „podmínky“, musí uvést podmínky v textovém poli „Další informace“ na účtu.\n● Přijetím nabídky zadavatel souhlasí s uvedenými „podmínkami a podmínkami“ tvůrce.\n● V případě sporu nemůže být mediátor nebo rozhodce příliš nápomocný, protože je obvykle obtížné získat důkazy o tom, co se na schůzce stalo. V takových případech mohou být prostředky BTC uzamčeny na dobu neurčitou nebo dokud se obchodní partneři nedohodnou.\n\nAbyste si byli jisti, že plně rozumíte rozdílům v obchodech „tváří v tvář“, přečtěte si pokyny a doporučení na adrese: [HYPERLINK:https://bisq.wiki/Face-to-face_(payment_method)] +payment.f2f.info=Obchody „tváří v tvář“ mají různá pravidla a přicházejí s jinými riziky než online transakce.\n\nHlavní rozdíly jsou:\n● Obchodní partneři si musí vyměňovat informace o místě a čase schůzky pomocí poskytnutých kontaktních údajů.\n● Obchodní partneři musí přinést své notebooky a na místě setkání potvrdit „platba odeslána“ a „platba přijata“.\n● Pokud má tvůrce speciální „podmínky“, musí uvést podmínky v textovém poli „Dodatečné informace“ na účtu.\n● Přijetím nabídky zadavatel souhlasí s uvedenými „podmínkami a podmínkami“ tvůrce.\n● V případě sporu nemůže být mediátor nebo rozhodce příliš nápomocný, protože je obvykle obtížné získat důkazy o tom, co se na schůzce stalo. V takových případech mohou být prostředky BTC uzamčeny na dobu neurčitou nebo dokud se obchodní partneři nedohodnou.\n\nAbyste si byli jisti, že plně rozumíte rozdílům v obchodech „tváří v tvář“, přečtěte si pokyny a doporučení na adrese: [HYPERLINK:https://bisq.wiki/Face-to-face_(payment_method)] payment.f2f.info.openURL=Otevřít webovou stránku payment.f2f.offerbook.tooltip.countryAndCity=Země a město: {0} / {1} payment.f2f.offerbook.tooltip.extra=Další informace: {0} +payment.ifsc=IFS kód +payment.ifsc.validation=IFSC formát: XXXX0999999 payment.japan.bank=Banka payment.japan.branch=Pobočka @@ -2800,7 +2857,7 @@ payment.japan.recipient=Jméno payment.australia.payid=PayID payment.payid=PayID spojené s finanční institucí. Jako e-mailová adresa nebo mobilní telefon. payment.payid.info=PayID jako telefonní číslo, e-mailová adresa nebo australské obchodní číslo (ABN), které můžete bezpečně propojit se svou bankou, družstevní záložnou nebo účtem stavební spořitelny. Musíte mít již vytvořený PayID u své australské finanční instituce. Odesílající i přijímající finanční instituce musí podporovat PayID. Další informace najdete na [HYPERLINK:https://payid.com.au/faqs/] -payment.amazonGiftCard.info=Chcete-li platit dárkovou kartou Amazon eGift, budete muset prodejci BTC poslat kartu Amazon eGift přes svůj účet Amazon.\n\nBisq zobrazí e-mail nebo mobilní číslo prodejce BTC, kam bude potřeba odeslat tuto dárkovou kartu. Na kartě ve zprávě pro příjemce musí být uvedeno ID obchodu. Pro další detaily a rady viz wiki: [HYPERLINK:https://bisq.wiki/Amazon_eGift_card].\n\nZde jsou tři důležité poznámky:\n- Preferujte dárkové karty v hodnotě do 100 USD, protože Amazon může považovat nákupy karet s vyššími částkami jako podezřelé a zablokovat je.\n- Na kartě do zprávy pro příjemce můžete přidat i vlastní originální text (např. "Happy birthday Susan!") spolu s ID obchodu. (V takovém případě o tom informujte protistranu pomocí obchodovacího chatu, aby mohli s jistotou ověřit, že obdržená dárková karta pochází od vás.)\n- Karty Amazon eGift lze uplatnit pouze na té stránce Amazon, na které byly také koupeny (např. karta koupená na amazon.it může být uplatněna zase jen na amazon.it). +payment.amazonGiftCard.info=Chcete-li platit dárkovou kartou Amazon eGift, budete muset prodejci BTC poslat kartu Amazon eGift přes svůj účet Amazon.\n\nPro další detaily a rady viz wiki: [HYPERLINK:https://bisq.wiki/Amazon_eGift_card].\n\nZde jsou tři důležité poznámky:\n- Preferujte dárkové karty v hodnotě do 100 USD, protože Amazon může považovat nákupy karet s vyššími částkami jako podezřelé a zablokovat je.\n- Na kartě do zprávy pro příjemce můžete přidat i vlastní originální text (např. "Happy birthday Susan!"). V takovém případě o tom informujte protistranu pomocí obchodního chatu, aby mohli s jistotou ověřit, že obdržená dárková karta pochází od vás.\n- Karty Amazon eGift lze uplatnit pouze na té stránce Amazon, na které byly také koupeny (např. karta koupená na amazon.it může být uplatněna zase jen na amazon.it). # We use constants from the code so we do not use our normal naming convention # dynamic values are not recognized by IntelliJ @@ -2885,13 +2942,39 @@ PAYSERA=Paysera # suppress inspection "UnusedProperty" PAXUM=Paxum # suppress inspection "UnusedProperty" +NEFT=Indie/NEFT +# suppress inspection "UnusedProperty" +RTGS=Indie/RTGS +# suppress inspection "UnusedProperty" +IMPS=Indie/IMPS +# suppress inspection "UnusedProperty" +UPI=Indie/UPI +# suppress inspection "UnusedProperty" +PAYTM=Indie/PayTM +# suppress inspection "UnusedProperty" +NEQUI=Nequi +# suppress inspection "UnusedProperty" +BIZUM=Bizum +# suppress inspection "UnusedProperty" +PIX=Pix +# suppress inspection "UnusedProperty" AMAZON_GIFT_CARD=Amazon eGift Card # suppress inspection "UnusedProperty" BLOCK_CHAINS_INSTANT=Instantní Altcoiny # suppress inspection "UnusedProperty" CAPITUAL=Capitual # suppress inspection "UnusedProperty" -SWIFT=SWIFT International Wire Transfer +CELPAY=CelPay +# suppress inspection "UnusedProperty" +MONESE=Monese +# suppress inspection "UnusedProperty" +SATISPAY=Satispay +# suppress inspection "UnusedProperty" +VERSE=Verse +# suppress inspection "UnusedProperty" +STRIKE=Strike +# suppress inspection "UnusedProperty" +SWIFT=Mezinárodní bankovní převod SWIFT # Deprecated: Cannot be deleted as it would break old trade history entries # suppress inspection "UnusedProperty" @@ -2945,12 +3028,38 @@ PAYSERA_SHORT=Paysera # suppress inspection "UnusedProperty" PAXUM_SHORT=Paxum # suppress inspection "UnusedProperty" +NEFT_SHORT=NEFT +# suppress inspection "UnusedProperty" +RTGS_SHORT=RTGS +# suppress inspection "UnusedProperty" +IMPS_SHORT=IMPS +# suppress inspection "UnusedProperty" +UPI_SHORT=UPI +# suppress inspection "UnusedProperty" +PAYTM_SHORT=PayTM +# suppress inspection "UnusedProperty" +NEQUI_SHORT=Nequi +# suppress inspection "UnusedProperty" +BIZUM_SHORT=Bizum +# suppress inspection "UnusedProperty" +PIX_SHORT=Pix +# suppress inspection "UnusedProperty" AMAZON_GIFT_CARD_SHORT=Amazon eGift Card # suppress inspection "UnusedProperty" BLOCK_CHAINS_INSTANT_SHORT=Instantní Altcoiny # suppress inspection "UnusedProperty" CAPITUAL_SHORT=Capitual # suppress inspection "UnusedProperty" +CELPAY_SHORT=CelPay +# suppress inspection "UnusedProperty" +MONESE_SHORT=Monese +# suppress inspection "UnusedProperty" +SATISPAY_SHORT=Satispay +# suppress inspection "UnusedProperty" +VERSE_SHORT=Verse +# suppress inspection "UnusedProperty" +STRIKE_SHORT=Strike +# suppress inspection "UnusedProperty" SWIFT_SHORT=SWIFT # Deprecated: Cannot be deleted as it would break old trade history entries diff --git a/core/src/main/resources/i18n/displayStrings_de.properties b/core/src/main/resources/i18n/displayStrings_de.properties index b932a8b41a2..516c48a4374 100644 --- a/core/src/main/resources/i18n/displayStrings_de.properties +++ b/core/src/main/resources/i18n/displayStrings_de.properties @@ -124,7 +124,7 @@ shared.noDateAvailable=Kein Datum verfügbar shared.noDetailsAvailable=Keine Details vorhanden shared.notUsedYet=Noch ungenutzt shared.date=Datum -shared.sendFundsDetailsWithFee=Sending: {0}\nFrom address: {1}\nTo receiving address: {2}\nRequired mining fee is: {3} ({4} satoshis/vbyte)\nTransaction vsize: {5} vKb\n\nThe recipient will receive: {6}\n\nAre you sure you want to withdraw this amount? +shared.sendFundsDetailsWithFee=Gesendet: {0}\nVon Adresse: {1}\nAn Empfangsadresse: {2}.\nBenötigte Mining-Gebühr ist: {3} ({4} satoshis/vbyte)\nTransaktionsgröße vsize: {5} vKb\n\nDer Empfänger erhält: {6}\n\nSind Sie sicher, dass Sie diesen Betrag abheben wollen? # suppress inspection "TrailingSpacesInProperty" shared.sendFundsDetailsDust=Diese Transaktion würde ein Wechselgeld erzeugen das unterhalb des Dust-Grenzwerts liegt (und daher von den Bitcoin-Konsensregeln nicht erlaubt wäre). Stattdessen wird dieser Dust ({0} Satoshi{1}) der Mining-Gebühr hinzugefügt.\n\n\n shared.copyToClipboard=In Zwischenablage kopieren @@ -284,6 +284,7 @@ mainView.walletServiceErrorMsg.rejectedTxException=Eine Transaktion wurde aus de mainView.networkWarning.allConnectionsLost=Sie haben die Verbindung zu allen {0} Netzwerk-Peers verloren.\nMöglicherweise haben Sie Ihre Internetverbindung verloren oder Ihr Computer war im Standbymodus. mainView.networkWarning.localhostBitcoinLost=Sie haben die Verbindung zum localhost Bitcoinknoten verloren.\nBitte starten Sie die Bisq Anwendung neu, um mit anderen Bitcoinknoten zu verbinden oder starten Sie den localhost Bitcoinknoten neu. +mainView.networkWarning.clockWatcher=Ihr Computer war für {0} Sekunden im Ruhezustand. Der Standby-Modus hat in der Vergangenheit schon fehlgeschlagene Trades verursacht. Damit Bisq korrekt funktioniert, muss der Standby-Modus in den Einstellungen Ihres Computers deaktiviert werden. mainView.version.update=(Update verfügbar) @@ -353,13 +354,13 @@ offerbook.timeSinceSigning.info.signer=vom Partner unterzeichnet und kann Partne offerbook.timeSinceSigning.info.banned=Konto wurde geblockt offerbook.timeSinceSigning.daysSinceSigning={0} Tage offerbook.timeSinceSigning.daysSinceSigning.long={0} seit der Unterzeichnung -offerbook.timeSinceSigning.tooltip.accountLimit=Account limit: {0} -offerbook.timeSinceSigning.tooltip.accountLimitLifted=Account limit lifted +offerbook.timeSinceSigning.tooltip.accountLimit=Konto-Limit: {0} +offerbook.timeSinceSigning.tooltip.accountLimitLifted=Konto-Limit aufgehoben offerbook.timeSinceSigning.tooltip.info.unsigned=DIeses Konto wurde noch nicht unterzeichnet -offerbook.timeSinceSigning.tooltip.info.signed=This account has been signed -offerbook.timeSinceSigning.tooltip.info.signedAndLifted=This account has been signed and can sign peer accounts -offerbook.timeSinceSigning.tooltip.checkmark.buyBtc=buy BTC from a signed account -offerbook.timeSinceSigning.tooltip.checkmark.wait=wait a minimum of {0} days +offerbook.timeSinceSigning.tooltip.info.signed=Dieses Konto wurde unterzeichnet +offerbook.timeSinceSigning.tooltip.info.signedAndLifted=Dieses Konto wurde unterzeichnet und kann andere Peer-Konten unterzeichnen +offerbook.timeSinceSigning.tooltip.checkmark.buyBtc=Kaufe BTC von einem unterzeichneten Konto +offerbook.timeSinceSigning.tooltip.checkmark.wait=Warte mindestens {0} Tage offerbook.timeSinceSigning.tooltip.learnMore=Mehr erfahren offerbook.xmrAutoConf=Automatische Bestätigung aktiviert @@ -624,7 +625,7 @@ portfolio.pending.step2_buyer.refTextWarn=Wichtig: Wenn Sie die Zahlung durchfü # suppress inspection "TrailingSpacesInProperty" portfolio.pending.step2_buyer.fees=Sollte Ihre Bank irgendwelche Gebühren für die Überweisung erheben, müssen Sie diese Gebühren bezahlen. # suppress inspection "TrailingSpacesInProperty" -portfolio.pending.step2_buyer.fees.swift=Make sure to use the SHA (shared fee model) to send the SWIFT payment. See more details at [HYPERLINK:https://bisq.wiki/SWIFT#Use_the_correct_fee_option]. +portfolio.pending.step2_buyer.fees.swift=Stellen Sie sicher, dass Sie SHA (shared fee model) verwenden um SWIFT Zahlungen zu senden. Weitere Informationen findest du unter [HYPERLINK:https://bisq.wiki/SWIFT#Use_the_correct_fee_option]. # suppress inspection "TrailingSpacesInProperty" portfolio.pending.step2_buyer.altcoin=Bitte überweisen Sie von Ihrer externen {0}-Wallet\n{1} an den BTC-Verkäufer.\n\n # suppress inspection "TrailingSpacesInProperty" @@ -800,9 +801,9 @@ portfolio.pending.mediationRequested=Mediation beantragt portfolio.pending.refundRequested=Rückerstattung beantragt portfolio.pending.openSupport=Support-Ticket öffnen portfolio.pending.supportTicketOpened=Support-Ticket geöffnet -portfolio.pending.communicateWithArbitrator=Please communicate with the arbitrator on the \"Support\" screen. -portfolio.pending.communicateWithMediator=Please communicate with the mediator on the \"Support\" screen. -portfolio.pending.disputeOpenedMyUser=You have already opened a dispute.\n{0} +portfolio.pending.communicateWithArbitrator=Bitte setzen Sie sich auf der \"Support\"-Seite mit dem Vermittler in Verbindung. +portfolio.pending.communicateWithMediator=Bitte setzen Sie sich auf der \"Support\"-Seite mit dem Mediator in Verbindung. +portfolio.pending.disputeOpenedMyUser=Sie haben bereits einen Konflikt eröffnet.\n{0} portfolio.pending.disputeOpenedByPeer=Ihr Handelspartner hat einen Konflikt geöffnet\n{0} portfolio.pending.noReceiverAddressDefined=Keine Empfangsadresse festgelegt @@ -821,7 +822,7 @@ portfolio.pending.mediationResult.popup.alreadyAccepted=Sie haben bereits akzept portfolio.pending.failedTrade.taker.missingTakerFeeTx=Die Transaktion der Abnehmer-Gebühr fehlt.\n\nOhne diese tx kann der Handel nicht abgeschlossen werden. Keine Gelder wurden gesperrt und keine Handelsgebühr wurde bezahlt. Sie können diesen Handel zu den fehlgeschlagenen Händeln verschieben. portfolio.pending.failedTrade.maker.missingTakerFeeTx=Die Transaktion der Abnehmer-Gebühr fehlt.\n\nOhne diese tx kann der Handel nicht abgeschlossen werden. Keine Gelder wurden gesperrt. Ihr Angebot ist für andere Händler weiterhin verfügbar. Sie haben die Ersteller-Gebühr also nicht verloren. Sie können diesen Handel zu den fehlgeschlagenen Händeln verschieben. portfolio.pending.failedTrade.missingDepositTx=Die Einzahlungstransaktion (die 2-of-2 Multisig-Transaktion) fehlt.\n\nOhne diese tx kann der Handel nicht abgeschlossen werden. Keine Gelder wurden gesperrt aber die Handels-Gebühr wurde bezahlt. Sie können eine Anfrage für eine Rückerstattung der Handels-Gebühr hier einreichen: [HYPERLINK:https://github.com/bisq-network/support/issues]\n\nSie können diesen Handel gerne zu den fehlgeschlagenen Händeln verschieben. -portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=Die verzögerte Auszahlungstransaktion fehlt, aber die Gelder wurden in der Einzahlungstransaktion gesperrt.\n\nBitte schicken Sie KEINE Geld-(Fiat-) oder Altcoin-Zahlungen an den BTC Verkäufer, weil ohne die verzögerte Auszahlungstransaktion später kein Schlichtungsverfahren eröffnet werden kann. Stattdessen öffnen Sie ein Vermittlungs-Ticket mit Cmd/Strg+o. Der Vermittler sollte vorschlagen, dass beide Handelspartner ihre vollständige Sicherheitskaution zurückerstattet bekommen (und der Verkäufer auch seinen Handels-Betrag). Durch diese Vorgehensweise entsteht kein Sicherheitsrisiko und es geht ausschließlich die Handelsgebühr verloren.\n\nSie können eine Rückerstattung der verlorenen gegangenen Handelsgebühren hier erbitten: [HYPERLINK:https://github.com/bisq-network/support/issues] +portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the fiat or altcoin payment to the BTC seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=Die verzögerte Auszahlungstransaktion fehlt, aber die Gelder wurden in der Einzahlungstransaktion gesperrt.\n\nWenn dem Käufer die verzögerte Auszahlungstransaktion auch fehlt, wird er dazu aufgefordert die Bezahlung NICHT zu schicken und stattdessen ein Vermittlungs-Ticket zu eröffnen. Sie sollten auch ein Vermittlungs-Ticket mit Cmd/Strg+o öffnen.\n\nWenn der Käufer die Zahlung noch nicht geschickt hat, sollte der Vermittler vorschlagen, dass beide Handelspartner ihre Sicherheitskaution vollständig zurückerhalten (und der Verkäufer auch den Handels-Betrag). Anderenfalls sollte der Handels-Betrag an den Käufer gehen.\n\nSie können eine Rückerstattung der verlorenen gegangenen Handelsgebühren hier erbitten: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.errorMsgSet=Während der Ausführung des Handel-Protokolls ist ein Fehler aufgetreten.\n\nFehler: {0}\n\nEs kann sein, dass dieser Fehler nicht gravierend ist und der Handel ganz normal abgeschlossen werden kann. Wenn Sie sich unsicher sind, öffnen Sie ein Vermittlungs-Ticket um den Rat eines Bisq Vermittlers zu erhalten.\n\nWenn der Fehler gravierend war, kann der Handel nicht abgeschlossen werden und Sie haben vielleicht die Handelsgebühr verloren. Sie können eine Rückerstattung der verlorenen gegangenen Handelsgebühren hier erbitten: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.missingContract=Der Handelsvertrag ist nicht festgelegt.\n\nDer Handel kann nicht abgeschlossen werden und Sie haben möglicherweise die Handelsgebühr verloren. Sollte das der Fall sein, können Sie eine Rückerstattung der verlorenen gegangenen Handelsgebühren hier beantragen: [HYPERLINK:https://github.com/bisq-network/support/issues] @@ -831,7 +832,7 @@ portfolio.pending.failedTrade.txChainValid.moveToFailed=Das Handels-Protokoll ha portfolio.pending.failedTrade.moveTradeToFailedIcon.tooltip=Handel zu den fehlgeschlagenen Händeln verschieben. portfolio.pending.failedTrade.warningIcon.tooltip=Klicken Sie hier um herauszufinden welche Probleme beim Handel aufgetreten sind. portfolio.failed.revertToPending.popup=Wollen Sie diesen Handel zu den offenen Händeln verschieben? -portfolio.failed.revertToPending.failed=Failed to move this trade to open trades. +portfolio.failed.revertToPending.failed=Dieser Trade konnte nicht in die offenen Trades verschoben werden. portfolio.failed.revertToPending=Handel zu den offenen Händeln verschieben portfolio.closed.completed=Abgeschlossen @@ -952,7 +953,7 @@ support.sigCheck.popup.failed=Unterzeichnung der Signatur ist fehlgeschlagen support.sigCheck.popup.invalidFormat=Nachricht ist nicht im erwarteten Format. Copy & paste Konflikt-Zusammenfassung. support.reOpenByTrader.prompt=Sind Sie sicher, dass Sie den Konflikt wiedereröffnen möchten? -support.reOpenByTrader.failed=Failed to re-open the dispute. +support.reOpenByTrader.failed=Der Konflikt konnte nicht wiedereröffnet werden. support.reOpenButton.label=Wiedereröffnen support.sendNotificationButton.label=Private Benachrichtigung support.reportButton.label=Melden @@ -977,10 +978,10 @@ support.closeTicket=Ticket schließen support.attachments=Anhänge: support.savedInMailbox=Die Nachricht wurde im Postfach des Empfängers gespeichert support.arrived=Die Nachricht ist beim Empfänger angekommen -support.transient=Message is on its way to receiver +support.transient=Die Nachricht ist auf dem Weg zum Empfänger support.acknowledged=Nachrichtenankunft vom Empfänger bestätigt support.error=Empfänger konnte die Nachricht nicht verarbeiten. Fehler: {0} -support.errorTimeout=timed out. Try sending message again. +support.errorTimeout=Zeit abgelaufen. Versuchen Sie die Nachricht erneut zu senden. support.buyerAddress=BTC-Adresse des Käufers support.sellerAddress=BTC-Adresse des Verkäufers support.role=Rolle @@ -996,7 +997,9 @@ support.buyerTaker=BTC-Käufer/Abnehmer support.sellerTaker=BTC-Verkäufer/Abnehmer support.backgroundInfo=Bisq ist kein Unternehmen, daher behandelt es Konflikte unterschiedlich.\n\nTrader können innerhalb der Anwendung über einen sicheren Chat auf dem Bildschirm für offene Trades kommunizieren, um zu versuchen, Konflikte selbst zu lösen. Wenn das nicht ausreicht, kann ein Mediator einschreiten und helfen. Der Mediator wird die Situation bewerten und eine Auszahlung von Trade Funds vorschlagen. Wenn beide Trader diesen Vorschlag annehmen, ist die Auszahlungstransaktion abgeschlossen und der Trade geschlossen. Wenn ein oder beide Trader mit der vom Mediator vorgeschlagenen Auszahlung nicht einverstanden sind, können sie ein Vermittlungsverfahren beantragen, bei dem der Vermittler die Situation neu bewertet und, falls gerechtfertigt, dem Trader persönlich eine Rückerstattung leistet und die Rückerstattung dieser Zahlung vom Bisq DAO verlangt. -support.initialInfo=Bitte geben Sie eine Beschreibung Ihres Problems in das untenstehende Textfeld ein. Fügen Sie so viele Informationen wie möglich hinzu, um die Zeit für die Konfliktlösung zu verkürzen.\n\nHier ist eine Checkliste der Informationen die Sie angeben sollten:\n\t● Wenn Sie der BTC-Käufer sind: Haben Sie die Geld- (Fiat-) oder Altcoin-Überweisung vorgenommen? Wenn ja, haben Sie in der Anwendung auf die Schaltfläche "Zahlung gestartet" geklickt?\n\t● Wenn Sie der BTC-Verkäufer sind: Haben Sie die Geld- (Fiat-) oder Altcoin-Zahlung erhalten? Wenn ja, haben Sie in der Anwendung auf die Schaltfläche "Zahlung erhalten" geklickt?\n\t● Welche Version von Bisq verwenden Sie?\n\t● Welches Betriebssystem verwenden Sie?\n\t● Wenn Sie ein Problem mit fehlgeschlagenen Transaktionen hatten, erwägen Sie bitte zu einem neuen Data-Verzeichnis zu wechseln.\n\t Manchmal wird das Data-Verzeichnis beschädigt was zu seltsamen Fehlern führt. \n\t Siehe: https://bisq.wiki/Switching_to_a_new_data_directory\n\nBitte machen Sie sich mit den Grundregeln der Streitbeilegung vertraut:\n\t● Sie müssen auf die Anfragen der {0} innerhalb von 2 Tagen antworten.\n\t● Vermittler antworten innerhalb von 2 Tagen. Schiedspersonen antworten innerhalb von 5 Werktagen.\n\t● Die maximale Zeitspanne für eine Konfliktlösung beträgt 14 Tage.\n\t● Sie müssen mit dem/der {1} zusammenarbeiten und die Informationen zur Verfügung stellen, die sie anfordern, um Ihren Fall zu bearbeiten.\n\t● Sie haben die in der Benutzervereinbarung aufgeführten Regeln zur Streitbeilegung akzeptiert, als Sie die Anwendung zum ersten Mal gestartet haben.\n\nSie können mehr über die Streitbeilegung erfahren auf: {2} +support.initialInfo=Bitte geben Sie eine Beschreibung Ihres Problems in das untenstehende Textfeld ein. Fügen Sie so viele Informationen wie möglich hinzu, um die Zeit für die Konfliktlösung zu verkürzen.\n\nHier ist eine Checkliste der Informationen die Sie angeben sollten:\n\t● Wenn Sie der BTC-Käufer sind: Haben Sie die Geld- (Fiat-) oder Altcoin-Überweisung vorgenommen? Wenn ja, haben Sie in der Anwendung auf die Schaltfläche "Zahlung gestartet" geklickt?\n\t● Wenn Sie der BTC-Verkäufer sind: Haben Sie die Geld- (Fiat-) oder Altcoin-Zahlung erhalten? Wenn ja, haben Sie in der Anwendung auf die Schaltfläche "Zahlung erhalten" geklickt?\n\t● Welche Version von Bisq verwenden Sie?\n\t● Welches Betriebssystem verwenden Sie?\n\t● Wenn Sie ein Problem mit fehlgeschlagenen Transaktionen hatten, erwägen Sie bitte zu einem neuen Datei-Verzeichnis zu wechseln.\n\t Manchmal wird das Datei-Verzeichnis beschädigt was zu seltsamen Fehlern führt. \n\t Siehe: https://bisq.wiki/Switching_to_a_new_data_directory\n\nBitte machen Sie sich mit den Grundregeln der Konfliktlösung vertraut:\n\t● Sie müssen auf die {0}. Anfrage innerhalb von 2 Tagen antworten.\n\t● {1}\n\t● Die maximale Zeitspanne für eine Konfliktlösung beträgt 14 Tage.\n\t● Sie müssen mit dem/der {2} zusammenarbeiten und die Informationen zur Verfügung stellen, die sie anfordern, um Ihren Fall zu bearbeiten.\n\t● Sie haben die in der Benutzervereinbarung aufgeführten Regeln zur Konfliktlösung akzeptiert, als Sie die Anwendung zum ersten Mal gestartet haben.\n\nSie können hier weitere Informationen zur Konfliktlösung finden: {3} +support.initialMediatorMsg=Mediators will generally reply to you within 24 hours.\n\t If you have not had a reply after 48 hours please feel free to reach out to your mediator on Keybase.\n\t Mediators usernames on Keybase are the same as their usernames within the Bisq app.\n\t Your mediator is: {0} +support.initialArbitratorMsg=Arbitrators will generally reply to you within 5 days.\n\t If you have not had a reply after 7 days please feel free to reach out to your arbitrator on Keybase.\n\t Arbitrators usernames on Keybase are the same as their usernames within the Bisq app.\n\t Your arbitrator is: {0} support.systemMsg=Systemnachricht: {0} support.youOpenedTicket=Sie haben eine Anfrage auf Support geöffnet.\n\n{0}\n\nBisq-Version: {1} support.youOpenedDispute=Sie haben eine Anfrage für einen Konflikt geöffnet.\n\n{0}\n\nBisq-version: {1} @@ -1064,6 +1067,8 @@ setting.preferences.dao.resyncFromResources.popup=Nach einem Neustart der Anwend setting.preferences.dao.resyncFromGenesis.popup=Eine Resync von der Genesis-Transaktion kann erhebliche Zeit und CPU-Ressourcen in Anspruch nehmen. Sind Sie sicher, dass Sie das tun wollen? Meistens ist ein Resync von den neuesten Ressourcendateien ausreichend und viel schneller.\n\nWenn Sie fortfahren, werden nach einem Neustart der Anwendung die Bisq-Netzwerk-Governance-Daten von den Seed-Nodes neu geladen und der BSQ-Konsensstatus wird aus der Genesis-Transaktion neu aufgebaut. setting.preferences.dao.resyncFromGenesis.resync=Resync von Genesis ausführen und beenden setting.preferences.dao.isDaoFullNode=Bisq als DAO Full Node betreiben +setting.preferences.dao.activated=DAO aktiviert +setting.preferences.dao.activated.popup=The change will be applied after a restart setting.preferences.dao.rpcUser=RPC Benutzername setting.preferences.dao.rpcPw=RPC Passwort setting.preferences.dao.blockNotifyPort=Blockbenachrichtigung-Port @@ -1922,15 +1927,10 @@ dao.news.bisqDAO.title=DER BISQ DAO dao.news.bisqDAO.description=Genauso wie der Bisq Handelsplatz dezentral und resistent gegen Zensur ist, ist auch die Führung der DAO - die Bisq DAO und der BSQ Token machen es möglich. dao.news.bisqDAO.readMoreLink=Erfahren Sie mehr über den Bisq DAO -dao.news.pastContribution.title=BEREITS ZU BISQ BEIGETRAGEN? BSQ ANFRAGEN -dao.news.pastContribution.description=Falls sie in der Vergangenheit zu Bisq beigetragen haben, verwenden sie die darunterstehende BSQ Adresse um einen Antrag zur Aufnahme in die BSQ Genesis Transaktion zu erstellen. -dao.news.pastContribution.yourAddress=Ihre BSQ-Wallets-Adresse -dao.news.pastContribution.requestNow=Jetzt anfordern - -dao.news.daoInfo.title=DEN BISQ DAO AUF UNSEREM TESTNETZWERK LAUFEN LASSEN -dao.news.daoInfo.description=Die Bisq DAO wurde auf Mainnet noch nicht veröffentlicht, jedoch können sie die Bisq DAO jetzt schon in unserem Testnet ausprobieren. -dao.news.daoInfo.firstSection.title=1. Nach DAO-Testnetzmodus wechseln -dao.news.daoInfo.firstSection.content=Vom Einstellungsmenü ins DAO-Testnetzwerk wechseln. +dao.news.daoInfo.title=Aktiviere die Bisq DAO +dao.news.daoInfo.description=To participate in the Bisq DAO and to use BSQ for discounted trading fees, you need to enable the DAO. When the DAO is enabled, Bisq downloads all missing blocks and verifies BSQ transactions. This verification process requires time, during which you may see Bisq use a lot of memory and processing power. This is normal. +dao.news.daoInfo.firstSection.title=1. Enable DAO +dao.news.daoInfo.firstSection.content=Enable the Bisq DAO and restart. dao.news.DAOOnTestnet.secondSection.title=2. Einige BSQ erwerben dao.news.DAOOnTestnet.secondSection.content=Fragen sie einfach auf Slack nach BSQ oder kaufen sie direkt BSQ in Bisq. dao.news.DAOOnTestnet.thirdSection.title=3. Beim Wahl-Zyklus teilhaben @@ -2679,6 +2679,7 @@ payment.secret=Geheimfrage payment.answer=Antwort payment.wallet=Wallets-ID payment.capitual.cap=CAP Code +payment.upi.virtualPaymentAddress=Virtual Payment Address # suppress inspection "UnusedProperty" payment.swift.headline=International SWIFT Wire Transfer @@ -2767,11 +2768,65 @@ payment.amazonGiftCard.upgrade=Bei der Zahlungsmethode Amazon Geschenkkarten mus payment.account.amazonGiftCard.addCountryInfo={0}\nDein bestehendes Amazon Geschenkkarten Konto ({1}) wurde keinem Land zugeteilt.\nBitte geben Sie das Amazon Geschenkkarten Land ein um Ihre Kontodaten zu aktualisieren.\nDas wird ihr Kontoalter nicht beeinflussen. payment.amazonGiftCard.upgrade.headLine=Amazon Geschenkkarte Konto updaten -payment.swift.info=Carefully review the core guidelines for using SWIFT on Bisq:\n\n- fill all fields completely and accurately \n- buyer must send payment in currency specified by the offer maker \n- buyer must use the shared fee model (SHA) \n- buyer and seller may incur fees, so they should check their bank's fee schedules beforehand \n\nSWIFT is more sophisticated than other payment methods, so please take a moment to review full guidance on the wiki [HYPERLINK:https://bisq.wiki/SWIFT]. +payment.swift.info.account=Carefully review the core guidelines for using SWIFT on Bisq:\n\n- fill all fields completely and accurately \n- buyer must send payment in currency specified by the offer maker \n- buyer must use the shared fee model (SHA) \n- buyer and seller may incur fees, so they should check their bank's fee schedules beforehand \n\nSWIFT is more sophisticated than other payment methods, so please take a moment to review full guidance on the wiki [HYPERLINK:https://bisq.wiki/SWIFT]. payment.swift.info.buyer=To buy bitcoin with SWIFT, you must:\n\n- send payment in the currency specified by the offer maker \n- use the shared fee model (SHA) to send payment\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. -payment.swift.info.seller=SWIFT senders are required to use the shared payment model (SHA) to send payments.\n\nIf you receive a SWIFT payment that does not use SHA, open a mediation ticket.\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. +payment.swift.info.seller=SWIFT senders are required to use the shared fee model (SHA) to send payments.\n\nIf you receive a SWIFT payment that does not use SHA, open a mediation ticket.\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. + +payment.imps.info.account=Please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a maximum of Rs. 200,000 that can be sent per transaction. If you are trading over this amount multiple transactions will be needed. However be aware their is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nSome banks have different limits for their customers. +payment.imps.info.buyer=Please send payment only to the account details provided in Bisq.\n\nThe maximum trade size is Rs. 200,000 per transaction.\n\nIf your trade is over Rs. 200,000 you will have to make multiple transfers. However be aware their is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nPlease note some banks have different limits for their customers. +payment.imps.info.seller=If you intend to receive over Rs. 200,000 per trade you should expect the buyer to have to make multiple transfers. However be aware there is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nPlease note some banks have different limits for their customers. + +payment.neft.info.account=Please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a maximum of Rs. 50,000 that can be sent per transaction. If you are trading over this amount multiple transactions will be needed.\n\nPlease note some banks have different limits for their customers. +payment.neft.info.buyer=Please send payment only to the account details provided in Bisq.\n\nThe maximum trade size is Rs. 50,000 per transaction.\n\nIf your trade is over Rs. 50,000 you will have to make multiple transfers.\n\nPlease note some banks have different limits for their customers. +payment.neft.info.seller=If you intend to receive over Rs. 50,000 per trade you should expect the buyer to have to make multiple transfers.\n\nPlease note some banks have different limits for their customers. + +payment.paytm.info.account=Please make sure to include your email or phone number that matches your email or phone number in your PayTM account. \n\nWhen users set up a PayTM account with No KYC users are limited to: \n\n● Maximum of Rs. 5,000 can be sent per transaction.\n● Maximum of Rs. 10,000 can be held in someone's PayTM wallet.\n\nIf you intend to trade amount of over 5,000 per trade you will need to complete KYC with PayTM. With KYC users are limited to:\n\n● Maximum of Rs. 100,000 can be sent per transaction.\n● Maximum of Rs. 100,000 can be held in someone's PayTM wallet.\n\nUsers should also be aware of account limits. Trades above PayTM account limits will likely have to take place over more than one day, or, be cancelled. +payment.paytm.info.buyer=Please send payment only to the email address or phone number provided.\n\nIf you intend to trade amount of over Rs. 5,000 per trade you will need to complete KYC with PayTM.\n\nWith No KYC Rs. 5,000 can be sent per transaction.\n\nWith KYC users Rs. 100,000 can be sent per transaction. +payment.paytm.info.seller=If you intend to trade amount of over Rs. 5,000 per trade you will need to complete KYC with PayTM. With KYC users are limited to:\n\n● Maximum of Rs. 100,000 can be sent per transaction.\n● Maximum of Rs. 100,000 can be held in your PayTM wallet .\n\nUsers should also be aware of account limits. As a maximum of Rs. 100,000 can be held in your PayTM wallet please make sure you transfer out your rupees regularly. + +payment.rtgs.info.account=RTGS is for payments of large trades of Rs. 200,000 or over.\n\nWhen setting up your RTGS payment account please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). +payment.rtgs.info.buyer=Please send payment only to the account details provided in Bisq.\n\nPlease be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). +payment.rtgs.info.seller=Please be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). + +payment.upi.info.account=Please make sure to include your Virtual Payment Address (VPA) also called your UPI ID. The format for this is like an email ID: with the sign “@” in the middle. For example, your UPI ID could be “receiver’s_name@bank_name” or “phone_number@bank_name.” \n\nFor UPI there is a maximum limit of Rs. 100,000 that can be sent per transaction. \n\nIf you intend to trade amount of over Rs. 100,000 per trade it is likely trades will have to take place over multiple transfers. \n\nPlease note some banks have different limits for their customers. +payment.upi.info.buyer=Please send payment only to the VPA / UPI ID provided in Bisq. \n\nThe maximum trade size is Rs. 100,000 per transaction. \n\nIf your trade is over Rs. 100,000 you will have to make multiple transfers. \n\nPlease note some banks have different limits for their customers. +payment.upi.info.seller=If you intend to receive over Rs. 100,000 per trade you should expect the buyer to have to make multiple transfers. \n\nPlease note some banks have different limits for their customers. + +payment.celpay.info.account=Please make sure to include the email your Celsius account is registered to. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account.\n\nCelPay users are limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Buyers can send any matching currency stablecoin to the BTC Seller. +payment.celpay.info.buyer=Please send payment only to the email address provided by the BTC Seller by sending a payment link.\n\nCelPay is limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Buyers can send any matching currency stablecoin to the BTC Seller. +payment.celpay.info.seller=BTC Sellers should expect to receive payment via a secure payment link. Please make sure the email payment link contains the email address provided by the BTC Buyer.\n\nCelPay users are limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Sellers should expect to receive any matching currency stablecoin from the BTC Buyer. It is possible for the BTC Buyer to send any matching currency stablecoin. +payment.celpay.supportedCurrenciesForReceiver=Supported currencies (please note: all the currencies below are supported stable coins within the Celcius app. Trades are for stable coins, not fiat.) + +payment.nequi.info.account=Please make sure to include your phone number that is associated with your Nequi account.\n\nWhen users set up a Nequi account payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. +payment.nequi.info.buyer=Please send payment only to the phone number provided in the BTC Seller's Bisq account.\n\nWhen users set up a Nequi account, payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. +payment.nequi.info.seller=Please check that the payment received matches the phone number provided in the BTC Buyer's Bisq account.\n\nWhen users set up a Nequi account, payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. + +payment.bizum.info.account=To use Bizum you need a bank account (IBAN) in Spain and to be registered for the service.\n\nBizum can be used for trades between €0.50 and €1,000.\n\nThe maximum amount of transactions you can send/receive using Bizum is €2,000 Euros per day.\n\nBizum users can have a maximum of 150 operations per month.\n\nEach bank however may establish its own limits, within the above limits, for its clients.\n\nTraders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.bizum.info.buyer=Please send payment only to the BTC Seller's mobile phone number as provided in Bisq.\n\nThe maximum trade size is €1,000 per payment. The maximum amount of transactions you can send using Bizum is €2,000 Euros per day.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.bizum.info.seller=Please make sure your payment is received from the BTC Buyer's mobile phone number as provided in Bisq.\n\nThe maximum trade size is €1,000 per payment. The maximum amount of transactions you can receive using Bizum is €2,000 Euros per day.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. + +payment.pix.info.account=Please make sure to include your chosen Pix Key. There are four types of keys: CPF (Natural Persons Register) or CNPJ (National Registry of Legal Entities), e-mail address, telephone number or a random key generated by the system called a universally unique identifier (UUID). A different key must be used for each Pix account you have. Individuals can create up to five keys for each account they own.\n\nWhen trading on Bisq, BTC Buyers should use their Pix Keys as the payment description so that it is easy for the BTC Sellers to identify the payment as coming from themselves. +payment.pix.info.buyer=Please send payment only the Pix Key provided in the BTC Seller's Bisq account.\n\nPlease use your Pix Key as the payment reference so that it is easy for the BTC Seller to identify the payment as coming from yourself. +payment.pix.info.seller=Please check that the payment received description matches the Pix Key provided in the BTC Buyer's Bisq account. +payment.pix.key=Pix Key (CPF, CNPJ, Email, Phone number or UUID) + +payment.monese.info.account=Monese is a bank app for users of GBP, EUR and RON*. Monese allows users to send money to other Monese accounts instantly and for free in any supported currency.\n\n*To open a RON account in Monese, you need to either live in Romania or have Romanian citizenship.\n\nWhen setting up your Monese account in Bisq please make sure to include your name and phone number that matches your Monese account. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account. +payment.monese.info.buyer=Please send payment only to the phone number provided by the BTC Seller in their Bisq account. Please leave the payment description blank. +payment.monese.info.seller=BTC Sellers should expect to receive payment from the phone number / name shown in the BTC Buyer's Bisq account. + +payment.satispay.info.account=To use Satispay you need a bank account (IBAN) in Italy and to be registered for the service.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.satispay.info.buyer=Please send payment only to the BTC Seller's mobile phone number as provided in Bisq.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.satispay.info.seller=Please make sure your payment is received from the BTC Buyer's mobile phone number / name as provided in Bisq.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. + +payment.verse.info.account=Verse is a multiple currency payment method that can send and receive payment in EUR, SEK, HUF, DKK, PLN.\n\nWhen setting up your Verse account in Bisq please make sure to include the username that matches your username in your Verse account. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. +payment.verse.info.buyer=Please send payment only to the username provided by the BTC Seller in their Bisq account. Please leave the payment description blank.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. +payment.verse.info.seller=BTC Sellers should expect to receive payment from the username shown in the BTC Buyer's Bisq account.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. + +payment.strike.info.account=Please make sure to include your Strike username.\n\nIn Bisq, Strike is used for fiat to fiat payments only.\n\nPlease make sure you are aware of the Strike limits:\n\nUsers who have registered with only their email, name, and phone number have the following limits:\n\n● $100 maximum per deposit\n● $1,000 maximum total deposits per week\n● $100 maximum per payment\n\nUsers can increase their limits by providing Strike with more information. These users have the following limits:\n\n● $1,000 maximum per deposit\n● $1,000 maximum total deposits per week\n● $1,000 maximum per payment\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.strike.info.buyer=Please send payment only to the BTC Seller's Strike username as provided in Bisq.\n\nThe maximum trade size is $1,000 per payment.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.strike.info.seller=Please make sure your payment is received from the BTC Buyer's Strike username as provided in Bisq.\n\nThe maximum trade size is $1,000 per payment.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. payment.usPostalMoneyOrder.info=Der Handel auf Bisq unter Verwendung von US Postal Money Orders (USPMO) setzt voraus, dass Sie Folgendes verstehen:\n\n- Der BTC-Käufer muss den Namen des BTC-Verkäufers sowohl in das Feld des Zahlers als auch in das Feld des Zahlungsempfängers eintragen und vor dem Versand ein hochauflösendes Foto des USPMO und des Umschlags mit dem Tracking-Nachweis machen.\n- BTC-Käufer müssen den USPMO mit Zustellbenachrichtigung an den BTC-Verkäufer schicken.\n\nFür den Fall, dass eine Mediation erforderlich ist oder es zu einem Handelskonflikt kommt, müssen Sie die Fotos zusammen mit der USPMO-Seriennummer, der Nummer des Postamtes und dem Dollarbetrag an den Bisq-Vermittler oder Rückerstattungsbeauftragten schicken, damit dieser die Angaben auf der Website der US-Post überprüfen kann.\n\nWenn Sie dem Vermittler oder der Schiedsperson die erforderlichen Informationen nicht zur Verfügung stellen, führt dies dazu, dass der Konflikt zu Ihrem Nachteil entschieden wird.\n\nIn allen Konfliktfällen trägt der USPMO-Absender 100% der Verantwortung für die Bereitstellung von Beweisen/Nachweisen für den Vermittler oder die Schiedsperson.\n\nWenn Sie diese Anforderungen nicht verstehen, handeln Sie bitte nicht auf Bisq unter Verwendung von USPMO. @@ -2792,6 +2847,8 @@ payment.f2f.info=Persönliche Händel "von Angesicht zu Angesicht" ('Face to Fac payment.f2f.info.openURL=Webseite öffnen payment.f2f.offerbook.tooltip.countryAndCity=Land und Stadt: {0} / {1} payment.f2f.offerbook.tooltip.extra=Zusätzliche Informationen: {0} +payment.ifsc=IFS Code +payment.ifsc.validation=IFSC format: XXXX0999999 payment.japan.bank=Bank payment.japan.branch=Filiale @@ -2800,7 +2857,7 @@ payment.japan.recipient=Name payment.australia.payid=PayID payment.payid=PayIDs wie E-Mail Adressen oder Telefonnummern die mit Finanzinstitutionen verbunden sind. payment.payid.info=Eine PayID wie eine Telefonnummer, E-Mail Adresse oder Australische Business Number (ABN) mit der Sie sicher Ihre Bank, Kreditgenossenschaft oder Bausparkassenkonto verlinken können. Sie müssen bereits eine PayID mit Ihrer Australischen Finanzinstitution erstellt haben. Beide Institutionen, die die sendet und die die empfängt, müssen PayID unterstützen. Weitere informationen finden Sie unter [HYPERLINK:https://payid.com.au/faqs/] -payment.amazonGiftCard.info=Um mit einer Amazon eGift Geschenkkarte zu bezahlen, müssen Sie eine Amazon eGift Geschenkkarte über Ihr Amazon-Konto an den BTC-Verkäufer senden. \n\nBisq zeigt die E-Mail-Adresse oder Telefonnummer des BTC-Verkäufers an, an die die Geschenkkarte gesendet werden soll, und Sie müssen die Handels-ID in das Nachrichtenfeld der Geschenkkarte eintragen. Bitte lesen Sie das Wiki [HYPERLINK:https://bisq.wiki/Amazon_eGift_card] für weitere Details und empfohlene Vorgehensweisen. \n\nDrei wichtige Hinweise:\n- Versuchen Sie Geschenkkarten mit Beträgen von 100 USD oder weniger zu versenden, weil Amazon größere Geschenkkarten gerne als betrügerisch kennzeichnet\n- Versuchen Sie einen kreativen, glaubwürdigen Text für die Nachricht der Geschenkkarten zu verwenden (z.B. "Alles Gute zum Geburtstag Susi!"), zusammen mit der Handels-ID (und verwenden Sie den Handels-Chat, um Ihrem Handelspartner den von Ihnen gewählten Referenztext mitzuteilen, damit er Ihre Zahlung überprüfen kann)\n- Amazon Geschenkkarten können nur auf der Amazon-Website eingelöst werden, auf der sie gekauft wurden (z. B. kann eine auf amazon.it gekaufte Geschenkkarte nur auf amazon.it eingelöst werden) +payment.amazonGiftCard.info=To pay with Amazon eGift Card, you will need to send an Amazon eGift Card to the BTC seller via your Amazon account. \n\nPlease see the wiki [HYPERLINK:https://bisq.wiki/Amazon_eGift_card] for further details and best practices. \n\nThree important notes:\n- try to send gift cards with amounts of 100 USD or smaller, as Amazon is known to flag larger gift cards as fraudulent\n- try to use creative, believable text for the gift card''s message (e.g., "Happy birthday Susan!") and use trader chat to tell your trading peer the reference text you picked so they can verify your payment\n- Amazon eGift Cards can only be redeemed on the Amazon website they were purchased on (e.g., a gift card purchased on amazon.it can only be redeemed on amazon.it) # We use constants from the code so we do not use our normal naming convention # dynamic values are not recognized by IntelliJ @@ -2885,12 +2942,38 @@ PAYSERA=Paysera # suppress inspection "UnusedProperty" PAXUM=Paxum # suppress inspection "UnusedProperty" +NEFT=India/NEFT +# suppress inspection "UnusedProperty" +RTGS=India/RTGS +# suppress inspection "UnusedProperty" +IMPS=India/IMPS +# suppress inspection "UnusedProperty" +UPI=India/UPI +# suppress inspection "UnusedProperty" +PAYTM=India/PayTM +# suppress inspection "UnusedProperty" +NEQUI=Nequi +# suppress inspection "UnusedProperty" +BIZUM=Bizum +# suppress inspection "UnusedProperty" +PIX=Pix +# suppress inspection "UnusedProperty" AMAZON_GIFT_CARD=Amazon Gift-Karte # suppress inspection "UnusedProperty" BLOCK_CHAINS_INSTANT=Altcoins schnell # suppress inspection "UnusedProperty" CAPITUAL=Capitual # suppress inspection "UnusedProperty" +CELPAY=CelPay +# suppress inspection "UnusedProperty" +MONESE=Monese +# suppress inspection "UnusedProperty" +SATISPAY=Satispay +# suppress inspection "UnusedProperty" +VERSE=Verse +# suppress inspection "UnusedProperty" +STRIKE=Strike +# suppress inspection "UnusedProperty" SWIFT=SWIFT International Wire Transfer # Deprecated: Cannot be deleted as it would break old trade history entries @@ -2945,12 +3028,38 @@ PAYSERA_SHORT=Paysera # suppress inspection "UnusedProperty" PAXUM_SHORT=Paxum # suppress inspection "UnusedProperty" +NEFT_SHORT=NEFT +# suppress inspection "UnusedProperty" +RTGS_SHORT=RTGS +# suppress inspection "UnusedProperty" +IMPS_SHORT=IMPS +# suppress inspection "UnusedProperty" +UPI_SHORT=UPI +# suppress inspection "UnusedProperty" +PAYTM_SHORT=PayTM +# suppress inspection "UnusedProperty" +NEQUI_SHORT=Nequi +# suppress inspection "UnusedProperty" +BIZUM_SHORT=Bizum +# suppress inspection "UnusedProperty" +PIX_SHORT=Pix +# suppress inspection "UnusedProperty" AMAZON_GIFT_CARD_SHORT=Amazon Gift-Karte # suppress inspection "UnusedProperty" BLOCK_CHAINS_INSTANT_SHORT=Altcoins schnell # suppress inspection "UnusedProperty" CAPITUAL_SHORT=Capitual # suppress inspection "UnusedProperty" +CELPAY_SHORT=CelPay +# suppress inspection "UnusedProperty" +MONESE_SHORT=Monese +# suppress inspection "UnusedProperty" +SATISPAY_SHORT=Satispay +# suppress inspection "UnusedProperty" +VERSE_SHORT=Verse +# suppress inspection "UnusedProperty" +STRIKE_SHORT=Strike +# suppress inspection "UnusedProperty" SWIFT_SHORT=SWIFT # Deprecated: Cannot be deleted as it would break old trade history entries diff --git a/core/src/main/resources/i18n/displayStrings_es.properties b/core/src/main/resources/i18n/displayStrings_es.properties index ca345e2bc07..868c0380a63 100644 --- a/core/src/main/resources/i18n/displayStrings_es.properties +++ b/core/src/main/resources/i18n/displayStrings_es.properties @@ -284,6 +284,7 @@ mainView.walletServiceErrorMsg.rejectedTxException=Se rechazó una transacción mainView.networkWarning.allConnectionsLost=Perdió la conexión a todos los {0} usuarios de red.\nTal vez se ha interrumpido su conexión a Internet o su computadora estaba en modo suspendido. mainView.networkWarning.localhostBitcoinLost=Perdió la conexión al nodo Bitcoin localhost.\nPor favor reinicie la aplicación Bisq para conectarse a otros nodos Bitcoin o reinice el nodo Bitcoin localhost. +mainView.networkWarning.clockWatcher=Your computer was asleep for {0} seconds. Standby mode has been known to cause trades to fail. In order to operate correctly Bisq requires that standby mode be disabled in your computer's settings. mainView.version.update=(Actualización disponible) @@ -821,7 +822,7 @@ portfolio.pending.mediationResult.popup.alreadyAccepted=Ya ha aceptado portfolio.pending.failedTrade.taker.missingTakerFeeTx=Falta la transacción de tasa de tomador\n\nSin esta tx, el intercambio no se puede completar. No se han bloqueado fondos y no se ha pagado ninguna tasa de intercambio. Puede mover esta operación a intercambios fallidos. portfolio.pending.failedTrade.maker.missingTakerFeeTx=Falta la transacción de tasa de tomador de su par.\n\nSin esta tx, el intercambio no se puede completar. No se han bloqueado fondos. Su oferta aún está disponible para otros comerciantes, por lo que no ha perdido la tasa de tomador. Puede mover este intercambio a intercambios fallidos. portfolio.pending.failedTrade.missingDepositTx=Falta la transacción de depósito (la transacción multifirma 2 de 2).\n\nSin esta tx, el intercambio no se puede completar. No se han bloqueado fondos, pero se ha pagado su tarifa comercial. Puede hacer una solicitud para que se le reembolse la tarifa comercial aquí: [HYPERLINK:https://github.com/bisq-network/support/issues].\n\nSiéntase libre de mover esta operación a operaciones fallidas. -portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=Falta la transacción de pago demorado, pero los fondos se han bloqueado en la transacción de depósito.\n\nNO envíe el pago fiat o altcoin al vendedor de BTC, porque sin el tx de pago demorado, no se puede abrir el arbitraje. En su lugar, abra un ticket de mediación con Cmd / Ctrl + o. El mediador debe sugerir que ambos pares recuperen el monto total de sus depósitos de seguridad (y el vendedor también recibirá el monto total de la operación). De esta manera, no hay riesgo en la seguridad y solo se pierden las tarifas comerciales.\n\nPuede solicitar un reembolso por las tarifas comerciales perdidas aquí: [HYPERLINK:https://github.com/bisq-network/support/issues]. +portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the fiat or altcoin payment to the BTC seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=Falta la transacción del pago demorado, pero los fondos se han bloqueado en la transacción de depósito.\n\nSi al comprador también le falta la transacción de pago demorado, se le indicará que NO envíe el pago y abra un ticket de mediación. También debe abrir un ticket de mediación con Cmd / Ctrl + o.\n\nSi el comprador aún no ha enviado el pago, el mediador debe sugerir que ambos pares recuperen el monto total de sus depósitos de seguridad (y el vendedor también recibirá el monto total de la operación). De lo contrario, el monto comercial debe ir al comprador.\n\nPuede solicitar un reembolso por las tarifas comerciales perdidas aquí: [HYPERLINK:https://github.com/bisq-network/support/issues]. portfolio.pending.failedTrade.errorMsgSet=Hubo un error durante la ejecución del protocolo de intercambio.\n\nError: {0}\n\nPuede ser que este error no sea crítico y que el intercambio se pueda completar normalmente. Si no está seguro, abra un ticket de mediación para obtener consejos de los mediadores de Bisq.\n\nSi el error fue crítico y la operación no se puede completar, es posible que haya perdido su tarifa de operación. Solicite un reembolso por las tarifas comerciales perdidas aquí: [HYPERLINK:ttps://github.com/bisq-network/support/issues]. portfolio.pending.failedTrade.missingContract=El contrato del intercambio no está establecido.\n\nLa operación no se puede completar y es posible que haya perdido su tarifa de operación. Si es así, puede solicitar un reembolso por las tarifas comerciales perdidas aquí: [HYPERLINK:https://github.com/bisq-network/support/issues]. @@ -996,7 +997,9 @@ support.buyerTaker=comprador/Tomador BTC support.sellerTaker=vendedor/Tomador BTC support.backgroundInfo=Bisq no es una compañía, por ello maneja las disputas de una forma diferente.\n\nLos compradores y vendedores pueden comunicarse a través de la aplicación por un chat seguro en la pantalla de intercambios abiertos para intentar resolver una disputa por su cuenta. Si eso no es suficiente, un mediador puede intervenir para ayudar. El mediador evaluará la situación y dará una recomendación para el pago de los fondos de la transacción. Si ambos aceptan esta sugerencia, la transacción del pago se completa y el intercambio se cierra. Si uno o ambos no están de acuerdo con el pago recomendado por el mediador, pueden solicitar arbitraje. El árbitro re-evaluará la situación y, si es necesario, hará el pago personalmente y solicitará un reembolso de este pago a la DAO de Bisq. -support.initialInfo=Por favor, introduzca una descripción de su problema en el campo de texto inferior. Añada tanta información como sea posible para acelerar el tiempo de resolución de la disputa.\n\nAquí tiene una lista de la información que debería proveer:\n● Si es comprador de BTC: ¿Realizó la transferencia de FIAT o de altcoins? Si es así, ¿hizo clic en el botón "pago iniciado" de la aplicación?\n● Si es el vendedor BTC: ¿Recibió el pago FIAT o de altcoin? Si es así, ¿hizo clic en el botón "pago recibido" de la aplicación?\n● ¿Qué versión de Bisq está usando?\n● ¿Qué sistema operativo está usando?\n● Si ha encontrado algún problema con transacciones fallidas por favor considere cambiar a un nuevo directorio de datos.\nA veces el directorio de datos se corrompe y provoca fallas extrañas.\nVer: https://bisq.wiki/Switching_to_a_new_data_directory\n\nPor favor familiarícese con las reglas básicas del proceso de disputa:\n● Tiene que responder a el requerimiento del árbitro en 2 días.\n● El periodo máximo de una disputa es de 14 días.\n● Necesita cooperar con el árbitro y proveer la información que le soliciten para resolver su caso.\n● Ha aceptado las reglas descritas en el documento de disputa en el acuerdo de usuario la primera vez que inició la aplicación.\n\nPuede leer más acerca del proceso de disputa en {2} +support.initialInfo=Please enter a description of your problem in the text field below. Add as much information as possible to speed up dispute resolution time.\n\nHere is a check list for information you should provide:\n\t● If you are the BTC buyer: Did you make the Fiat or Altcoin transfer? If so, did you click the 'payment started' button in the application?\n\t● If you are the BTC seller: Did you receive the Fiat or Altcoin payment? If so, did you click the 'payment received' button in the application?\n\t● Which version of Bisq are you using?\n\t● Which operating system are you using?\n\t● If you encountered an issue with failed transactions please consider switching to a new data directory.\n\t Sometimes the data directory gets corrupted and leads to strange bugs. \n\t See: https://bisq.wiki/Switching_to_a_new_data_directory\n\nPlease make yourself familiar with the basic rules for the dispute process:\n\t● You need to respond to the {0}''s requests within 2 days.\n\t● {1}\n\t● The maximum period for a dispute is 14 days.\n\t● You need to cooperate with the {2} and provide the information they request to make your case.\n\t● You accepted the rules outlined in the dispute document in the user agreement when you first started the application.\n\nYou can read more about the dispute process at: {3} +support.initialMediatorMsg=Mediators will generally reply to you within 24 hours.\n\t If you have not had a reply after 48 hours please feel free to reach out to your mediator on Keybase.\n\t Mediators usernames on Keybase are the same as their usernames within the Bisq app.\n\t Your mediator is: {0} +support.initialArbitratorMsg=Arbitrators will generally reply to you within 5 days.\n\t If you have not had a reply after 7 days please feel free to reach out to your arbitrator on Keybase.\n\t Arbitrators usernames on Keybase are the same as their usernames within the Bisq app.\n\t Your arbitrator is: {0} support.systemMsg=Mensaje de sistema: {0} support.youOpenedTicket=Ha abierto una solicitud de soporte.\n\n{0}\n\nVersión Bisq: {1} support.youOpenedDispute=Ha abierto una solicitud de disputa.\n\n{0}\n\nVersión Bisq: {1} @@ -1064,6 +1067,8 @@ setting.preferences.dao.resyncFromResources.popup=Después de un reinicio de la setting.preferences.dao.resyncFromGenesis.popup=Una resincronización desde la transacción génesis puede llevar mucho tiempo y recursos de CPU. ¿Está seguro de que quiere hacer eso? Generalmente una resincronización de los últimos archivos de recursos es suficiente y mucho más rápida\n\nSi continúa, después de reiniciar la aplicación, los datos de gobernanza de la red Bisq se volverán a cargar desde los nodos semilla y el estado de consenso BSQ se reconstruirá a partir de la transacción génesis. setting.preferences.dao.resyncFromGenesis.resync=Resincronizar desde la transacción génesis y cerrar la aplicación setting.preferences.dao.isDaoFullNode=Ejecutar Bisq como nodo completo de la DAO +setting.preferences.dao.activated=DAO activated +setting.preferences.dao.activated.popup=The change will be applied after a restart setting.preferences.dao.rpcUser=nombre de usuario RPC setting.preferences.dao.rpcPw=contraseña RPC setting.preferences.dao.blockNotifyPort=Puerto de notificación de bloque @@ -1922,15 +1927,10 @@ dao.news.bisqDAO.title=LA DAO BISQ dao.news.bisqDAO.description=Tal como el exchange Bisq es descentralizado y resistente a la censura, lo es su modelo de governanza - y la DAO BISQ y el token BSQ son herramientas que lo hacen posible. dao.news.bisqDAO.readMoreLink=Aprender más acerca de la DAO Bisq. -dao.news.pastContribution.title=¿HIZO CONTRIBUCIONES EN EL PASADO? SOLICITE BSQ -dao.news.pastContribution.description=Si ha contribuido a Bisq por favor use la dirección BSQ de abajo y haga una solicitud por haber participado en la distribución de BSQ génesis. -dao.news.pastContribution.yourAddress=Su dirección de monedero BSQ -dao.news.pastContribution.requestNow=Solicitar ahora - -dao.news.daoInfo.title=CORRER LA DAO BISQ EN TESTNET -dao.news.daoInfo.description=La red principal de la DAO Bisq aún no se ha lanzado pero puede aprender acerca de la DAO ejecutándola en la testnet. -dao.news.daoInfo.firstSection.title=1. Cambiar a Modo Testnet -dao.news.daoInfo.firstSection.content=Cambiar a la testnet desde la pantalla de Configuración +dao.news.daoInfo.title=ENABLE THE BISQ DAO +dao.news.daoInfo.description=To participate in the Bisq DAO and to use BSQ for discounted trading fees, you need to enable the DAO. When the DAO is enabled, Bisq downloads all missing blocks and verifies BSQ transactions. This verification process requires time, during which you may see Bisq use a lot of memory and processing power. This is normal. +dao.news.daoInfo.firstSection.title=1. Enable DAO +dao.news.daoInfo.firstSection.content=Enable the Bisq DAO and restart. dao.news.DAOOnTestnet.secondSection.title=2. Adquirir algunos BSQ dao.news.DAOOnTestnet.secondSection.content=Solicitar BSQ en Slack o comprar BSQ en Bisq dao.news.DAOOnTestnet.thirdSection.title=3. Participar en un ciclo de votación @@ -2679,6 +2679,7 @@ payment.secret=Pregunta secreta payment.answer=Respuesta payment.wallet=ID de cartera: payment.capitual.cap=CAP Code +payment.upi.virtualPaymentAddress=Virtual Payment Address # suppress inspection "UnusedProperty" payment.swift.headline=International SWIFT Wire Transfer @@ -2767,11 +2768,65 @@ payment.amazonGiftCard.upgrade=El método de pago Tarjetas regalo Amazon requier payment.account.amazonGiftCard.addCountryInfo={0}\nSu cuenta actual de Tarjeta regalo Amazon ({1}) no tiene un País especificado.\nPor favor introduzca el país de su Tarjeta regalo Amazon para actualizar sus datos de cuenta.\nEsto no afectará el estatus de edad de su cuenta. payment.amazonGiftCard.upgrade.headLine=Actualizar cuenta Tarjeta regalo Amazon -payment.swift.info=Carefully review the core guidelines for using SWIFT on Bisq:\n\n- fill all fields completely and accurately \n- buyer must send payment in currency specified by the offer maker \n- buyer must use the shared fee model (SHA) \n- buyer and seller may incur fees, so they should check their bank's fee schedules beforehand \n\nSWIFT is more sophisticated than other payment methods, so please take a moment to review full guidance on the wiki [HYPERLINK:https://bisq.wiki/SWIFT]. +payment.swift.info.account=Carefully review the core guidelines for using SWIFT on Bisq:\n\n- fill all fields completely and accurately \n- buyer must send payment in currency specified by the offer maker \n- buyer must use the shared fee model (SHA) \n- buyer and seller may incur fees, so they should check their bank's fee schedules beforehand \n\nSWIFT is more sophisticated than other payment methods, so please take a moment to review full guidance on the wiki [HYPERLINK:https://bisq.wiki/SWIFT]. payment.swift.info.buyer=To buy bitcoin with SWIFT, you must:\n\n- send payment in the currency specified by the offer maker \n- use the shared fee model (SHA) to send payment\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. -payment.swift.info.seller=SWIFT senders are required to use the shared payment model (SHA) to send payments.\n\nIf you receive a SWIFT payment that does not use SHA, open a mediation ticket.\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. +payment.swift.info.seller=SWIFT senders are required to use the shared fee model (SHA) to send payments.\n\nIf you receive a SWIFT payment that does not use SHA, open a mediation ticket.\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. + +payment.imps.info.account=Please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a maximum of Rs. 200,000 that can be sent per transaction. If you are trading over this amount multiple transactions will be needed. However be aware their is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nSome banks have different limits for their customers. +payment.imps.info.buyer=Please send payment only to the account details provided in Bisq.\n\nThe maximum trade size is Rs. 200,000 per transaction.\n\nIf your trade is over Rs. 200,000 you will have to make multiple transfers. However be aware their is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nPlease note some banks have different limits for their customers. +payment.imps.info.seller=If you intend to receive over Rs. 200,000 per trade you should expect the buyer to have to make multiple transfers. However be aware there is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nPlease note some banks have different limits for their customers. + +payment.neft.info.account=Please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a maximum of Rs. 50,000 that can be sent per transaction. If you are trading over this amount multiple transactions will be needed.\n\nPlease note some banks have different limits for their customers. +payment.neft.info.buyer=Please send payment only to the account details provided in Bisq.\n\nThe maximum trade size is Rs. 50,000 per transaction.\n\nIf your trade is over Rs. 50,000 you will have to make multiple transfers.\n\nPlease note some banks have different limits for their customers. +payment.neft.info.seller=If you intend to receive over Rs. 50,000 per trade you should expect the buyer to have to make multiple transfers.\n\nPlease note some banks have different limits for their customers. + +payment.paytm.info.account=Please make sure to include your email or phone number that matches your email or phone number in your PayTM account. \n\nWhen users set up a PayTM account with No KYC users are limited to: \n\n● Maximum of Rs. 5,000 can be sent per transaction.\n● Maximum of Rs. 10,000 can be held in someone's PayTM wallet.\n\nIf you intend to trade amount of over 5,000 per trade you will need to complete KYC with PayTM. With KYC users are limited to:\n\n● Maximum of Rs. 100,000 can be sent per transaction.\n● Maximum of Rs. 100,000 can be held in someone's PayTM wallet.\n\nUsers should also be aware of account limits. Trades above PayTM account limits will likely have to take place over more than one day, or, be cancelled. +payment.paytm.info.buyer=Please send payment only to the email address or phone number provided.\n\nIf you intend to trade amount of over Rs. 5,000 per trade you will need to complete KYC with PayTM.\n\nWith No KYC Rs. 5,000 can be sent per transaction.\n\nWith KYC users Rs. 100,000 can be sent per transaction. +payment.paytm.info.seller=If you intend to trade amount of over Rs. 5,000 per trade you will need to complete KYC with PayTM. With KYC users are limited to:\n\n● Maximum of Rs. 100,000 can be sent per transaction.\n● Maximum of Rs. 100,000 can be held in your PayTM wallet .\n\nUsers should also be aware of account limits. As a maximum of Rs. 100,000 can be held in your PayTM wallet please make sure you transfer out your rupees regularly. + +payment.rtgs.info.account=RTGS is for payments of large trades of Rs. 200,000 or over.\n\nWhen setting up your RTGS payment account please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). +payment.rtgs.info.buyer=Please send payment only to the account details provided in Bisq.\n\nPlease be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). +payment.rtgs.info.seller=Please be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). + +payment.upi.info.account=Please make sure to include your Virtual Payment Address (VPA) also called your UPI ID. The format for this is like an email ID: with the sign “@” in the middle. For example, your UPI ID could be “receiver’s_name@bank_name” or “phone_number@bank_name.” \n\nFor UPI there is a maximum limit of Rs. 100,000 that can be sent per transaction. \n\nIf you intend to trade amount of over Rs. 100,000 per trade it is likely trades will have to take place over multiple transfers. \n\nPlease note some banks have different limits for their customers. +payment.upi.info.buyer=Please send payment only to the VPA / UPI ID provided in Bisq. \n\nThe maximum trade size is Rs. 100,000 per transaction. \n\nIf your trade is over Rs. 100,000 you will have to make multiple transfers. \n\nPlease note some banks have different limits for their customers. +payment.upi.info.seller=If you intend to receive over Rs. 100,000 per trade you should expect the buyer to have to make multiple transfers. \n\nPlease note some banks have different limits for their customers. + +payment.celpay.info.account=Please make sure to include the email your Celsius account is registered to. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account.\n\nCelPay users are limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Buyers can send any matching currency stablecoin to the BTC Seller. +payment.celpay.info.buyer=Please send payment only to the email address provided by the BTC Seller by sending a payment link.\n\nCelPay is limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Buyers can send any matching currency stablecoin to the BTC Seller. +payment.celpay.info.seller=BTC Sellers should expect to receive payment via a secure payment link. Please make sure the email payment link contains the email address provided by the BTC Buyer.\n\nCelPay users are limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Sellers should expect to receive any matching currency stablecoin from the BTC Buyer. It is possible for the BTC Buyer to send any matching currency stablecoin. +payment.celpay.supportedCurrenciesForReceiver=Supported currencies (please note: all the currencies below are supported stable coins within the Celcius app. Trades are for stable coins, not fiat.) + +payment.nequi.info.account=Please make sure to include your phone number that is associated with your Nequi account.\n\nWhen users set up a Nequi account payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. +payment.nequi.info.buyer=Please send payment only to the phone number provided in the BTC Seller's Bisq account.\n\nWhen users set up a Nequi account, payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. +payment.nequi.info.seller=Please check that the payment received matches the phone number provided in the BTC Buyer's Bisq account.\n\nWhen users set up a Nequi account, payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. + +payment.bizum.info.account=To use Bizum you need a bank account (IBAN) in Spain and to be registered for the service.\n\nBizum can be used for trades between €0.50 and €1,000.\n\nThe maximum amount of transactions you can send/receive using Bizum is €2,000 Euros per day.\n\nBizum users can have a maximum of 150 operations per month.\n\nEach bank however may establish its own limits, within the above limits, for its clients.\n\nTraders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.bizum.info.buyer=Please send payment only to the BTC Seller's mobile phone number as provided in Bisq.\n\nThe maximum trade size is €1,000 per payment. The maximum amount of transactions you can send using Bizum is €2,000 Euros per day.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.bizum.info.seller=Please make sure your payment is received from the BTC Buyer's mobile phone number as provided in Bisq.\n\nThe maximum trade size is €1,000 per payment. The maximum amount of transactions you can receive using Bizum is €2,000 Euros per day.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. + +payment.pix.info.account=Please make sure to include your chosen Pix Key. There are four types of keys: CPF (Natural Persons Register) or CNPJ (National Registry of Legal Entities), e-mail address, telephone number or a random key generated by the system called a universally unique identifier (UUID). A different key must be used for each Pix account you have. Individuals can create up to five keys for each account they own.\n\nWhen trading on Bisq, BTC Buyers should use their Pix Keys as the payment description so that it is easy for the BTC Sellers to identify the payment as coming from themselves. +payment.pix.info.buyer=Please send payment only the Pix Key provided in the BTC Seller's Bisq account.\n\nPlease use your Pix Key as the payment reference so that it is easy for the BTC Seller to identify the payment as coming from yourself. +payment.pix.info.seller=Please check that the payment received description matches the Pix Key provided in the BTC Buyer's Bisq account. +payment.pix.key=Pix Key (CPF, CNPJ, Email, Phone number or UUID) + +payment.monese.info.account=Monese is a bank app for users of GBP, EUR and RON*. Monese allows users to send money to other Monese accounts instantly and for free in any supported currency.\n\n*To open a RON account in Monese, you need to either live in Romania or have Romanian citizenship.\n\nWhen setting up your Monese account in Bisq please make sure to include your name and phone number that matches your Monese account. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account. +payment.monese.info.buyer=Please send payment only to the phone number provided by the BTC Seller in their Bisq account. Please leave the payment description blank. +payment.monese.info.seller=BTC Sellers should expect to receive payment from the phone number / name shown in the BTC Buyer's Bisq account. + +payment.satispay.info.account=To use Satispay you need a bank account (IBAN) in Italy and to be registered for the service.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.satispay.info.buyer=Please send payment only to the BTC Seller's mobile phone number as provided in Bisq.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.satispay.info.seller=Please make sure your payment is received from the BTC Buyer's mobile phone number / name as provided in Bisq.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. + +payment.verse.info.account=Verse is a multiple currency payment method that can send and receive payment in EUR, SEK, HUF, DKK, PLN.\n\nWhen setting up your Verse account in Bisq please make sure to include the username that matches your username in your Verse account. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. +payment.verse.info.buyer=Please send payment only to the username provided by the BTC Seller in their Bisq account. Please leave the payment description blank.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. +payment.verse.info.seller=BTC Sellers should expect to receive payment from the username shown in the BTC Buyer's Bisq account.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. + +payment.strike.info.account=Please make sure to include your Strike username.\n\nIn Bisq, Strike is used for fiat to fiat payments only.\n\nPlease make sure you are aware of the Strike limits:\n\nUsers who have registered with only their email, name, and phone number have the following limits:\n\n● $100 maximum per deposit\n● $1,000 maximum total deposits per week\n● $100 maximum per payment\n\nUsers can increase their limits by providing Strike with more information. These users have the following limits:\n\n● $1,000 maximum per deposit\n● $1,000 maximum total deposits per week\n● $1,000 maximum per payment\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.strike.info.buyer=Please send payment only to the BTC Seller's Strike username as provided in Bisq.\n\nThe maximum trade size is $1,000 per payment.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.strike.info.seller=Please make sure your payment is received from the BTC Buyer's Strike username as provided in Bisq.\n\nThe maximum trade size is $1,000 per payment.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. payment.usPostalMoneyOrder.info=Los intercambios usando US Postal Money Orders (USPMO) en Bisq requiere que entienda lo siguiente:\n\n- Los compradores de BTC deben escribir la dirección del vendedor en los campos de "Payer" y "Payee" y tomar una foto en alta resolución de la USPMO y del sobre con la prueba de seguimiento antes de enviar.\n- Los compradores de BTC deben enviar la USPMO con confirmación de entrega.\n\nEn caso de que sea necesaria la mediación, se requerirá al comprador que entregue las fotos al mediador o agente de devolución de fondos, junto con el número de serie de la USPMO, número de oficina postal, y la cantidad de USD, para que puedan verificar los detalles en la web de US Post Office.\n\nNo entregar la información requerida al Mediador o Árbitro resultará en pérdida del caso de disputa. \n\nEn todos los casos de disputa, el emisor de la USPMO tiene el 100% de responsabilidad en aportar la evidencia al Mediador o Árbitro.\n\nSi no entiende estos requerimientos, no comercie usando USPMO en Bisq. @@ -2792,6 +2847,8 @@ payment.f2f.info=Los intercambios 'Cara a Cara' tienen diferentes reglas y riesg payment.f2f.info.openURL=Abrir paǵina web payment.f2f.offerbook.tooltip.countryAndCity=País y ciudad: {0} / {1} payment.f2f.offerbook.tooltip.extra=Información adicional: {0} +payment.ifsc=IFS Code +payment.ifsc.validation=IFSC format: XXXX0999999 payment.japan.bank=Banco payment.japan.branch=Branch @@ -2800,7 +2857,7 @@ payment.japan.recipient=Nombre payment.australia.payid=PayID payment.payid=PayID conectado a una institución financiera. Como la dirección email o el número de móvil. payment.payid.info=Un PayID como un número de teléfono, dirección email o Australian Business Number (ABN), que puede conectar con seguridad a su banco, unión de crédito o cuenta de construcción de sociedad. Necesita haber creado una PayID con su institución financiera australiana. Tanto para enviar y recibir las instituciones financieras deben soportar PayID. Para más información por favor compruebe [HYPERLINK:https://payid.com.au/faqs/] -payment.amazonGiftCard.info=Para pagar con Tarjeta eGift Amazon. necesitará enviar una Tarjeta eGift Amazon al vendedor BTC a través de su cuenta Amazon.\n\nBisq mostrará la dirección e-mail del vendedor de BTC o el número de teléfono donde la tarjeta de regalo deberá enviarse. Por favor vea la wiki [HYPERLINK:https://bisq.wiki/Amazon_eGift_card] para más detalles y mejores prácticas.\n\nNotas importantes:\n- Pruebe a enviar las tarjetas regalo en cantidades de 100USD o menores, ya que Amazon está señalando tarjetas regalo mayores como fraudulentas.\n- Intente usar textos para el mensaje de la tarjeta regalo creíbles y creativos ("Feliz cumpleaños!").\n- Las tarjetas Amazon eGift pueden ser redimidas únicamente en la web de Amazon en la que se compraron (por ejemplo, una tarjeta comprada en amazon.it solo puede ser redimida en amazon.it) +payment.amazonGiftCard.info=To pay with Amazon eGift Card, you will need to send an Amazon eGift Card to the BTC seller via your Amazon account. \n\nPlease see the wiki [HYPERLINK:https://bisq.wiki/Amazon_eGift_card] for further details and best practices. \n\nThree important notes:\n- try to send gift cards with amounts of 100 USD or smaller, as Amazon is known to flag larger gift cards as fraudulent\n- try to use creative, believable text for the gift card''s message (e.g., "Happy birthday Susan!") and use trader chat to tell your trading peer the reference text you picked so they can verify your payment\n- Amazon eGift Cards can only be redeemed on the Amazon website they were purchased on (e.g., a gift card purchased on amazon.it can only be redeemed on amazon.it) # We use constants from the code so we do not use our normal naming convention # dynamic values are not recognized by IntelliJ @@ -2885,12 +2942,38 @@ PAYSERA=Paysera # suppress inspection "UnusedProperty" PAXUM=Paxum # suppress inspection "UnusedProperty" +NEFT=India/NEFT +# suppress inspection "UnusedProperty" +RTGS=India/RTGS +# suppress inspection "UnusedProperty" +IMPS=India/IMPS +# suppress inspection "UnusedProperty" +UPI=India/UPI +# suppress inspection "UnusedProperty" +PAYTM=India/PayTM +# suppress inspection "UnusedProperty" +NEQUI=Nequi +# suppress inspection "UnusedProperty" +BIZUM=Bizum +# suppress inspection "UnusedProperty" +PIX=Pix +# suppress inspection "UnusedProperty" AMAZON_GIFT_CARD=Tarjeta Amazon eGift # suppress inspection "UnusedProperty" BLOCK_CHAINS_INSTANT=Altcoins instant # suppress inspection "UnusedProperty" CAPITUAL=Capitual # suppress inspection "UnusedProperty" +CELPAY=CelPay +# suppress inspection "UnusedProperty" +MONESE=Monese +# suppress inspection "UnusedProperty" +SATISPAY=Satispay +# suppress inspection "UnusedProperty" +VERSE=Verse +# suppress inspection "UnusedProperty" +STRIKE=Strike +# suppress inspection "UnusedProperty" SWIFT=SWIFT International Wire Transfer # Deprecated: Cannot be deleted as it would break old trade history entries @@ -2945,12 +3028,38 @@ PAYSERA_SHORT=Paysera # suppress inspection "UnusedProperty" PAXUM_SHORT=Paxum # suppress inspection "UnusedProperty" +NEFT_SHORT=NEFT +# suppress inspection "UnusedProperty" +RTGS_SHORT=RTGS +# suppress inspection "UnusedProperty" +IMPS_SHORT=IMPS +# suppress inspection "UnusedProperty" +UPI_SHORT=UPI +# suppress inspection "UnusedProperty" +PAYTM_SHORT=PayTM +# suppress inspection "UnusedProperty" +NEQUI_SHORT=Nequi +# suppress inspection "UnusedProperty" +BIZUM_SHORT=Bizum +# suppress inspection "UnusedProperty" +PIX_SHORT=Pix +# suppress inspection "UnusedProperty" AMAZON_GIFT_CARD_SHORT=Tarjeta Amazon eGift # suppress inspection "UnusedProperty" BLOCK_CHAINS_INSTANT_SHORT=Altcoins instant # suppress inspection "UnusedProperty" CAPITUAL_SHORT=Capitual # suppress inspection "UnusedProperty" +CELPAY_SHORT=CelPay +# suppress inspection "UnusedProperty" +MONESE_SHORT=Monese +# suppress inspection "UnusedProperty" +SATISPAY_SHORT=Satispay +# suppress inspection "UnusedProperty" +VERSE_SHORT=Verse +# suppress inspection "UnusedProperty" +STRIKE_SHORT=Strike +# suppress inspection "UnusedProperty" SWIFT_SHORT=SWIFT # Deprecated: Cannot be deleted as it would break old trade history entries diff --git a/core/src/main/resources/i18n/displayStrings_fa.properties b/core/src/main/resources/i18n/displayStrings_fa.properties index d6add3c0ca5..99dbb977ac1 100644 --- a/core/src/main/resources/i18n/displayStrings_fa.properties +++ b/core/src/main/resources/i18n/displayStrings_fa.properties @@ -284,6 +284,7 @@ mainView.walletServiceErrorMsg.rejectedTxException=A transaction was rejected fr mainView.networkWarning.allConnectionsLost=اتصال شما به تمام {0} همتایان شبکه قطع شد.\nشاید ارتباط کامپیوتر شما قطع شده است یا کامپیوتر در حالت Standby است. mainView.networkWarning.localhostBitcoinLost=اتصال شما به Node لوکال هاست بیتکوین قطع شد.\nلطفاً به منظور اتصال به سایر Nodeهای بیتکوین، برنامه‌ی Bisq یا Node لوکال هاست بیتکوین را مجددا راه اندازی کنید. +mainView.networkWarning.clockWatcher=Your computer was asleep for {0} seconds. Standby mode has been known to cause trades to fail. In order to operate correctly Bisq requires that standby mode be disabled in your computer's settings. mainView.version.update=(به روز رسانی موجود است) @@ -821,7 +822,7 @@ portfolio.pending.mediationResult.popup.alreadyAccepted=You've already accepted portfolio.pending.failedTrade.taker.missingTakerFeeTx=The taker fee transaction is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked and no trade fee has been paid. You can move this trade to failed trades. portfolio.pending.failedTrade.maker.missingTakerFeeTx=The peer's taker fee transaction is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked. Your offer is still available to other traders, so you have not lost the maker fee. You can move this trade to failed trades. portfolio.pending.failedTrade.missingDepositTx=The deposit transaction (the 2-of-2 multisig transaction) is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked but your trade fee has been paid. You can make a request to be reimbursed the trade fee here: [HYPERLINK:https://github.com/bisq-network/support/issues]\n\nFeel free to move this trade to failed trades. -portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the fiat or altcoin payment to the BTC seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] +portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the fiat or altcoin payment to the BTC seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing but funds have been locked in the deposit transaction.\n\nIf the buyer is also missing the delayed payout transaction, they will be instructed to NOT send the payment and open a mediation ticket instead. You should also open a mediation ticket with Cmd/Ctrl+o. \n\nIf the buyer has not sent payment yet, the mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). Otherwise the trade amount should go to the buyer. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.errorMsgSet=There was an error during trade protocol execution.\n\nError: {0}\n\nIt might be that this error is not critical, and the trade can be completed normally. If you are unsure, open a mediation ticket to get advice from Bisq mediators. \n\nIf the error was critical and the trade cannot be completed, you might have lost your trade fee. Request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.missingContract=The trade contract is not set.\n\nThe trade cannot be completed and you might have lost your trade fee. If so, you can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] @@ -996,7 +997,9 @@ support.buyerTaker=خریدار/پذیرنده‌ی بیتکوین support.sellerTaker=فروشنده/پذیرنده‌ی بیتکوین support.backgroundInfo=Bisq is not a company, so it handles disputes differently.\n\nTraders can communicate within the application via secure chat on the open trades screen to try solving disputes on their own. If that is not sufficient, a mediator can step in to help. The mediator will evaluate the situation and suggest a payout of trade funds. If both traders accept this suggestion, the payout transaction is completed and the trade is closed. If one or both traders do not agree to the mediator's suggested payout, they can request arbitration.The arbitrator will re-evaluate the situation and, if warranted, personally pay the trader back and request reimbursement for this payment from the Bisq DAO. -support.initialInfo=Please enter a description of your problem in the text field below. Add as much information as possible to speed up dispute resolution time.\n\nHere is a check list for information you should provide:\n\t● If you are the BTC buyer: Did you make the Fiat or Altcoin transfer? If so, did you click the 'payment started' button in the application?\n\t● If you are the BTC seller: Did you receive the Fiat or Altcoin payment? If so, did you click the 'payment received' button in the application?\n\t● Which version of Bisq are you using?\n\t● Which operating system are you using?\n\t● If you encountered an issue with failed transactions please consider switching to a new data directory.\n\t Sometimes the data directory gets corrupted and leads to strange bugs. \n\t See: https://bisq.wiki/Switching_to_a_new_data_directory\n\nPlease make yourself familiar with the basic rules for the dispute process:\n\t● You need to respond to the {0}''s requests within 2 days.\n\t● Mediators respond in between 2 days. Arbitrators respond in between 5 business days.\n\t● The maximum period for a dispute is 14 days.\n\t● You need to cooperate with the {1} and provide the information they request to make your case.\n\t● You accepted the rules outlined in the dispute document in the user agreement when you first started the application.\n\nYou can read more about the dispute process at: {2} +support.initialInfo=Please enter a description of your problem in the text field below. Add as much information as possible to speed up dispute resolution time.\n\nHere is a check list for information you should provide:\n\t● If you are the BTC buyer: Did you make the Fiat or Altcoin transfer? If so, did you click the 'payment started' button in the application?\n\t● If you are the BTC seller: Did you receive the Fiat or Altcoin payment? If so, did you click the 'payment received' button in the application?\n\t● Which version of Bisq are you using?\n\t● Which operating system are you using?\n\t● If you encountered an issue with failed transactions please consider switching to a new data directory.\n\t Sometimes the data directory gets corrupted and leads to strange bugs. \n\t See: https://bisq.wiki/Switching_to_a_new_data_directory\n\nPlease make yourself familiar with the basic rules for the dispute process:\n\t● You need to respond to the {0}''s requests within 2 days.\n\t● {1}\n\t● The maximum period for a dispute is 14 days.\n\t● You need to cooperate with the {2} and provide the information they request to make your case.\n\t● You accepted the rules outlined in the dispute document in the user agreement when you first started the application.\n\nYou can read more about the dispute process at: {3} +support.initialMediatorMsg=Mediators will generally reply to you within 24 hours.\n\t If you have not had a reply after 48 hours please feel free to reach out to your mediator on Keybase.\n\t Mediators usernames on Keybase are the same as their usernames within the Bisq app.\n\t Your mediator is: {0} +support.initialArbitratorMsg=Arbitrators will generally reply to you within 5 days.\n\t If you have not had a reply after 7 days please feel free to reach out to your arbitrator on Keybase.\n\t Arbitrators usernames on Keybase are the same as their usernames within the Bisq app.\n\t Your arbitrator is: {0} support.systemMsg=پیغام سیستم: {0} support.youOpenedTicket=شما یک درخواست برای پشتیبانی باز کردید.\n\n{0}\n\nنسخه Bisq شما: {1} support.youOpenedDispute=شما یک درخواست برای یک اختلاف باز کردید.\n\n{0}\n\nنسخه Bisq شما: {1} @@ -1064,6 +1067,8 @@ setting.preferences.dao.resyncFromResources.popup=After an application restart t setting.preferences.dao.resyncFromGenesis.popup=A resync from genesis transaction can take considerable time and CPU resources. Are you sure you want to do that? Mostly a resync from latest resource files is sufficient and much faster.\n\nIf you proceed, after an application restart the Bisq network governance data will be reloaded from the seed nodes and the BSQ consensus state will be rebuilt from the genesis transaction. setting.preferences.dao.resyncFromGenesis.resync=Resync from genesis and shutdown setting.preferences.dao.isDaoFullNode=Bisq را به عنوان یک گره کامل DAO اجرا کن +setting.preferences.dao.activated=DAO activated +setting.preferences.dao.activated.popup=The change will be applied after a restart setting.preferences.dao.rpcUser=نام کاربری RPC setting.preferences.dao.rpcPw=رمزعبور RPC setting.preferences.dao.blockNotifyPort=Block notify port @@ -1922,15 +1927,10 @@ dao.news.bisqDAO.title=سازمان مستقل غیر متمرکز (Bisq (DAO dao.news.bisqDAO.description=درست همانطور که مبادله کیف پول Bisq، غیر متمرکز و مقاوم در برابر سانسور است، مدل حاکمیت آن نیز وجود دارد و توکن های Bisq DAO و BSQ ابزارهایی هستند که آنرا محقق می سازند. dao.news.bisqDAO.readMoreLink=درباره Bisq DAO بیشتر بدانید -dao.news.pastContribution.title=آیا مشارکت قبلی داشته اید؟ برای BSQ درخواست دهید. -dao.news.pastContribution.description=اگر در رابطه با کیف پول Bisq مشارکت داشته اید، لطفا از آدرس BSQ زیر استفاده کنید و برای دریافت بخشی از توزیع جنسیس BSQ درخواست دهید. -dao.news.pastContribution.yourAddress=آدرس کیف‌پول BSQ شما -dao.news.pastContribution.requestNow=حالا درخواست دهید. - -dao.news.daoInfo.title=BISQ DAO را روی شبکه تستی، اجرا کنید. -dao.news.daoInfo.description=کیف پول Bisq DAO شبکه اصلی هنوز راه اندازی نشده است، اما شما می توانید با اجرای کیف پول Bisq DAO روی شبکه تستی، در مورد آن چیزهایی را یاد بگیرید. -dao.news.daoInfo.firstSection.title=1. به حالت شبکه تستی DAO تغییر وضعیت دهید. -dao.news.daoInfo.firstSection.content=از صفحه تنظیمات، به شبکه تستی DAO بروید. +dao.news.daoInfo.title=ENABLE THE BISQ DAO +dao.news.daoInfo.description=To participate in the Bisq DAO and to use BSQ for discounted trading fees, you need to enable the DAO. When the DAO is enabled, Bisq downloads all missing blocks and verifies BSQ transactions. This verification process requires time, during which you may see Bisq use a lot of memory and processing power. This is normal. +dao.news.daoInfo.firstSection.title=1. Enable DAO +dao.news.daoInfo.firstSection.content=Enable the Bisq DAO and restart. dao.news.DAOOnTestnet.secondSection.title=2. چند BSQ را خریداری نمایید. dao.news.DAOOnTestnet.secondSection.content=روی اسلک برای BSQ درخواست دهید و یا روی کیف پول Bisq ، BSQ را خریداری کنید. dao.news.DAOOnTestnet.thirdSection.title=3. در یک چرخه رای گیری شرکت کنید. @@ -2679,6 +2679,7 @@ payment.secret=سوال محرمانه payment.answer=پاسخ payment.wallet=شناسه کیف پول payment.capitual.cap=CAP Code +payment.upi.virtualPaymentAddress=Virtual Payment Address # suppress inspection "UnusedProperty" payment.swift.headline=International SWIFT Wire Transfer @@ -2767,11 +2768,65 @@ payment.amazonGiftCard.upgrade=Amazon gift cards payment method requires the cou payment.account.amazonGiftCard.addCountryInfo={0}\nYour existing Amazon Gift Card account ({1}) does not have a Country specified.\nPlease enter your Amazon Gift Card Country to update your account data.\nThis will not affect your account age status. payment.amazonGiftCard.upgrade.headLine=Update Amazon Gift Card account -payment.swift.info=Carefully review the core guidelines for using SWIFT on Bisq:\n\n- fill all fields completely and accurately \n- buyer must send payment in currency specified by the offer maker \n- buyer must use the shared fee model (SHA) \n- buyer and seller may incur fees, so they should check their bank's fee schedules beforehand \n\nSWIFT is more sophisticated than other payment methods, so please take a moment to review full guidance on the wiki [HYPERLINK:https://bisq.wiki/SWIFT]. +payment.swift.info.account=Carefully review the core guidelines for using SWIFT on Bisq:\n\n- fill all fields completely and accurately \n- buyer must send payment in currency specified by the offer maker \n- buyer must use the shared fee model (SHA) \n- buyer and seller may incur fees, so they should check their bank's fee schedules beforehand \n\nSWIFT is more sophisticated than other payment methods, so please take a moment to review full guidance on the wiki [HYPERLINK:https://bisq.wiki/SWIFT]. payment.swift.info.buyer=To buy bitcoin with SWIFT, you must:\n\n- send payment in the currency specified by the offer maker \n- use the shared fee model (SHA) to send payment\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. -payment.swift.info.seller=SWIFT senders are required to use the shared payment model (SHA) to send payments.\n\nIf you receive a SWIFT payment that does not use SHA, open a mediation ticket.\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. +payment.swift.info.seller=SWIFT senders are required to use the shared fee model (SHA) to send payments.\n\nIf you receive a SWIFT payment that does not use SHA, open a mediation ticket.\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. + +payment.imps.info.account=Please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a maximum of Rs. 200,000 that can be sent per transaction. If you are trading over this amount multiple transactions will be needed. However be aware their is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nSome banks have different limits for their customers. +payment.imps.info.buyer=Please send payment only to the account details provided in Bisq.\n\nThe maximum trade size is Rs. 200,000 per transaction.\n\nIf your trade is over Rs. 200,000 you will have to make multiple transfers. However be aware their is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nPlease note some banks have different limits for their customers. +payment.imps.info.seller=If you intend to receive over Rs. 200,000 per trade you should expect the buyer to have to make multiple transfers. However be aware there is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nPlease note some banks have different limits for their customers. + +payment.neft.info.account=Please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a maximum of Rs. 50,000 that can be sent per transaction. If you are trading over this amount multiple transactions will be needed.\n\nPlease note some banks have different limits for their customers. +payment.neft.info.buyer=Please send payment only to the account details provided in Bisq.\n\nThe maximum trade size is Rs. 50,000 per transaction.\n\nIf your trade is over Rs. 50,000 you will have to make multiple transfers.\n\nPlease note some banks have different limits for their customers. +payment.neft.info.seller=If you intend to receive over Rs. 50,000 per trade you should expect the buyer to have to make multiple transfers.\n\nPlease note some banks have different limits for their customers. + +payment.paytm.info.account=Please make sure to include your email or phone number that matches your email or phone number in your PayTM account. \n\nWhen users set up a PayTM account with No KYC users are limited to: \n\n● Maximum of Rs. 5,000 can be sent per transaction.\n● Maximum of Rs. 10,000 can be held in someone's PayTM wallet.\n\nIf you intend to trade amount of over 5,000 per trade you will need to complete KYC with PayTM. With KYC users are limited to:\n\n● Maximum of Rs. 100,000 can be sent per transaction.\n● Maximum of Rs. 100,000 can be held in someone's PayTM wallet.\n\nUsers should also be aware of account limits. Trades above PayTM account limits will likely have to take place over more than one day, or, be cancelled. +payment.paytm.info.buyer=Please send payment only to the email address or phone number provided.\n\nIf you intend to trade amount of over Rs. 5,000 per trade you will need to complete KYC with PayTM.\n\nWith No KYC Rs. 5,000 can be sent per transaction.\n\nWith KYC users Rs. 100,000 can be sent per transaction. +payment.paytm.info.seller=If you intend to trade amount of over Rs. 5,000 per trade you will need to complete KYC with PayTM. With KYC users are limited to:\n\n● Maximum of Rs. 100,000 can be sent per transaction.\n● Maximum of Rs. 100,000 can be held in your PayTM wallet .\n\nUsers should also be aware of account limits. As a maximum of Rs. 100,000 can be held in your PayTM wallet please make sure you transfer out your rupees regularly. + +payment.rtgs.info.account=RTGS is for payments of large trades of Rs. 200,000 or over.\n\nWhen setting up your RTGS payment account please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). +payment.rtgs.info.buyer=Please send payment only to the account details provided in Bisq.\n\nPlease be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). +payment.rtgs.info.seller=Please be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). + +payment.upi.info.account=Please make sure to include your Virtual Payment Address (VPA) also called your UPI ID. The format for this is like an email ID: with the sign “@” in the middle. For example, your UPI ID could be “receiver’s_name@bank_name” or “phone_number@bank_name.” \n\nFor UPI there is a maximum limit of Rs. 100,000 that can be sent per transaction. \n\nIf you intend to trade amount of over Rs. 100,000 per trade it is likely trades will have to take place over multiple transfers. \n\nPlease note some banks have different limits for their customers. +payment.upi.info.buyer=Please send payment only to the VPA / UPI ID provided in Bisq. \n\nThe maximum trade size is Rs. 100,000 per transaction. \n\nIf your trade is over Rs. 100,000 you will have to make multiple transfers. \n\nPlease note some banks have different limits for their customers. +payment.upi.info.seller=If you intend to receive over Rs. 100,000 per trade you should expect the buyer to have to make multiple transfers. \n\nPlease note some banks have different limits for their customers. + +payment.celpay.info.account=Please make sure to include the email your Celsius account is registered to. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account.\n\nCelPay users are limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Buyers can send any matching currency stablecoin to the BTC Seller. +payment.celpay.info.buyer=Please send payment only to the email address provided by the BTC Seller by sending a payment link.\n\nCelPay is limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Buyers can send any matching currency stablecoin to the BTC Seller. +payment.celpay.info.seller=BTC Sellers should expect to receive payment via a secure payment link. Please make sure the email payment link contains the email address provided by the BTC Buyer.\n\nCelPay users are limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Sellers should expect to receive any matching currency stablecoin from the BTC Buyer. It is possible for the BTC Buyer to send any matching currency stablecoin. +payment.celpay.supportedCurrenciesForReceiver=Supported currencies (please note: all the currencies below are supported stable coins within the Celcius app. Trades are for stable coins, not fiat.) + +payment.nequi.info.account=Please make sure to include your phone number that is associated with your Nequi account.\n\nWhen users set up a Nequi account payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. +payment.nequi.info.buyer=Please send payment only to the phone number provided in the BTC Seller's Bisq account.\n\nWhen users set up a Nequi account, payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. +payment.nequi.info.seller=Please check that the payment received matches the phone number provided in the BTC Buyer's Bisq account.\n\nWhen users set up a Nequi account, payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. + +payment.bizum.info.account=To use Bizum you need a bank account (IBAN) in Spain and to be registered for the service.\n\nBizum can be used for trades between €0.50 and €1,000.\n\nThe maximum amount of transactions you can send/receive using Bizum is €2,000 Euros per day.\n\nBizum users can have a maximum of 150 operations per month.\n\nEach bank however may establish its own limits, within the above limits, for its clients.\n\nTraders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.bizum.info.buyer=Please send payment only to the BTC Seller's mobile phone number as provided in Bisq.\n\nThe maximum trade size is €1,000 per payment. The maximum amount of transactions you can send using Bizum is €2,000 Euros per day.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.bizum.info.seller=Please make sure your payment is received from the BTC Buyer's mobile phone number as provided in Bisq.\n\nThe maximum trade size is €1,000 per payment. The maximum amount of transactions you can receive using Bizum is €2,000 Euros per day.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. + +payment.pix.info.account=Please make sure to include your chosen Pix Key. There are four types of keys: CPF (Natural Persons Register) or CNPJ (National Registry of Legal Entities), e-mail address, telephone number or a random key generated by the system called a universally unique identifier (UUID). A different key must be used for each Pix account you have. Individuals can create up to five keys for each account they own.\n\nWhen trading on Bisq, BTC Buyers should use their Pix Keys as the payment description so that it is easy for the BTC Sellers to identify the payment as coming from themselves. +payment.pix.info.buyer=Please send payment only the Pix Key provided in the BTC Seller's Bisq account.\n\nPlease use your Pix Key as the payment reference so that it is easy for the BTC Seller to identify the payment as coming from yourself. +payment.pix.info.seller=Please check that the payment received description matches the Pix Key provided in the BTC Buyer's Bisq account. +payment.pix.key=Pix Key (CPF, CNPJ, Email, Phone number or UUID) + +payment.monese.info.account=Monese is a bank app for users of GBP, EUR and RON*. Monese allows users to send money to other Monese accounts instantly and for free in any supported currency.\n\n*To open a RON account in Monese, you need to either live in Romania or have Romanian citizenship.\n\nWhen setting up your Monese account in Bisq please make sure to include your name and phone number that matches your Monese account. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account. +payment.monese.info.buyer=Please send payment only to the phone number provided by the BTC Seller in their Bisq account. Please leave the payment description blank. +payment.monese.info.seller=BTC Sellers should expect to receive payment from the phone number / name shown in the BTC Buyer's Bisq account. + +payment.satispay.info.account=To use Satispay you need a bank account (IBAN) in Italy and to be registered for the service.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.satispay.info.buyer=Please send payment only to the BTC Seller's mobile phone number as provided in Bisq.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.satispay.info.seller=Please make sure your payment is received from the BTC Buyer's mobile phone number / name as provided in Bisq.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. + +payment.verse.info.account=Verse is a multiple currency payment method that can send and receive payment in EUR, SEK, HUF, DKK, PLN.\n\nWhen setting up your Verse account in Bisq please make sure to include the username that matches your username in your Verse account. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. +payment.verse.info.buyer=Please send payment only to the username provided by the BTC Seller in their Bisq account. Please leave the payment description blank.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. +payment.verse.info.seller=BTC Sellers should expect to receive payment from the username shown in the BTC Buyer's Bisq account.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. + +payment.strike.info.account=Please make sure to include your Strike username.\n\nIn Bisq, Strike is used for fiat to fiat payments only.\n\nPlease make sure you are aware of the Strike limits:\n\nUsers who have registered with only their email, name, and phone number have the following limits:\n\n● $100 maximum per deposit\n● $1,000 maximum total deposits per week\n● $100 maximum per payment\n\nUsers can increase their limits by providing Strike with more information. These users have the following limits:\n\n● $1,000 maximum per deposit\n● $1,000 maximum total deposits per week\n● $1,000 maximum per payment\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.strike.info.buyer=Please send payment only to the BTC Seller's Strike username as provided in Bisq.\n\nThe maximum trade size is $1,000 per payment.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.strike.info.seller=Please make sure your payment is received from the BTC Buyer's Strike username as provided in Bisq.\n\nThe maximum trade size is $1,000 per payment.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. payment.usPostalMoneyOrder.info=Trading using US Postal Money Orders (USPMO) on Bisq requires that you understand the following:\n\n- BTC buyers must write the BTC Seller’s name in both the Payer and the Payee’s fields & take a high-resolution photo of the USPMO and envelope with proof of tracking before sending.\n- BTC buyers must send the USPMO to the BTC seller with Delivery Confirmation.\n\nIn the event mediation is necessary, or if there is a trade dispute, you will be required to send the photos to the Bisq mediator or refund agent, together with the USPMO Serial Number, Post Office Number, and dollar amount, so they can verify the details on the US Post Office website.\n\nFailure to provide the required information to the Mediator or Arbitrator will result in losing the dispute case.\n\nIn all dispute cases, the USPMO sender bears 100% of the burden of responsibility in providing evidence/proof to the Mediator or Arbitrator.\n\nIf you do not understand these requirements, do not trade using USPMO on Bisq. @@ -2792,6 +2847,8 @@ payment.f2f.info='Face to Face' trades have different rules and come with differ payment.f2f.info.openURL=باز کردن صفحه وب payment.f2f.offerbook.tooltip.countryAndCity=Country and city: {0} / {1} payment.f2f.offerbook.tooltip.extra=اطلاعات اضافی: {0} +payment.ifsc=IFS Code +payment.ifsc.validation=IFSC format: XXXX0999999 payment.japan.bank=بانک payment.japan.branch=Branch @@ -2800,7 +2857,7 @@ payment.japan.recipient=نام payment.australia.payid=PayID payment.payid=PayID linked to financial institution. Like email address or mobile phone. payment.payid.info=A PayID like a phone number, email address or an Australian Business Number (ABN), that you can securely link to your bank, credit union or building society account. You need to have already created a PayID with your Australian financial institution. Both sending and receiving financial institutions must support PayID. For more information please check [HYPERLINK:https://payid.com.au/faqs/] -payment.amazonGiftCard.info=To pay with Amazon eGift Card, you will need to send an Amazon eGift Card to the BTC seller via your Amazon account. \n\nBisq will show the BTC seller''s email address or phone number where the gift card should be sent, and you must include the trade ID in the gift card''s message field. Please see the wiki [HYPERLINK:https://bisq.wiki/Amazon_eGift_card] for further details and best practices. \n\nThree important notes:\n- try to send gift cards with amounts of 100 USD or smaller, as Amazon is known to flag larger gift cards as fraudulent\n- try to use creative, believable text for the gift card''s message (e.g., "Happy birthday Susan!") along with the trade ID (and use trader chat to tell your trading peer the reference text you picked so they can verify your payment)\n- Amazon eGift Cards can only be redeemed on the Amazon website they were purchased on (e.g., a gift card purchased on amazon.it can only be redeemed on amazon.it) +payment.amazonGiftCard.info=To pay with Amazon eGift Card, you will need to send an Amazon eGift Card to the BTC seller via your Amazon account. \n\nPlease see the wiki [HYPERLINK:https://bisq.wiki/Amazon_eGift_card] for further details and best practices. \n\nThree important notes:\n- try to send gift cards with amounts of 100 USD or smaller, as Amazon is known to flag larger gift cards as fraudulent\n- try to use creative, believable text for the gift card''s message (e.g., "Happy birthday Susan!") and use trader chat to tell your trading peer the reference text you picked so they can verify your payment\n- Amazon eGift Cards can only be redeemed on the Amazon website they were purchased on (e.g., a gift card purchased on amazon.it can only be redeemed on amazon.it) # We use constants from the code so we do not use our normal naming convention # dynamic values are not recognized by IntelliJ @@ -2885,12 +2942,38 @@ PAYSERA=Paysera # suppress inspection "UnusedProperty" PAXUM=Paxum # suppress inspection "UnusedProperty" +NEFT=India/NEFT +# suppress inspection "UnusedProperty" +RTGS=India/RTGS +# suppress inspection "UnusedProperty" +IMPS=India/IMPS +# suppress inspection "UnusedProperty" +UPI=India/UPI +# suppress inspection "UnusedProperty" +PAYTM=India/PayTM +# suppress inspection "UnusedProperty" +NEQUI=Nequi +# suppress inspection "UnusedProperty" +BIZUM=Bizum +# suppress inspection "UnusedProperty" +PIX=Pix +# suppress inspection "UnusedProperty" AMAZON_GIFT_CARD=Amazon eGift Card # suppress inspection "UnusedProperty" BLOCK_CHAINS_INSTANT=Altcoins Instant # suppress inspection "UnusedProperty" CAPITUAL=Capitual # suppress inspection "UnusedProperty" +CELPAY=CelPay +# suppress inspection "UnusedProperty" +MONESE=Monese +# suppress inspection "UnusedProperty" +SATISPAY=Satispay +# suppress inspection "UnusedProperty" +VERSE=Verse +# suppress inspection "UnusedProperty" +STRIKE=Strike +# suppress inspection "UnusedProperty" SWIFT=SWIFT International Wire Transfer # Deprecated: Cannot be deleted as it would break old trade history entries @@ -2945,12 +3028,38 @@ PAYSERA_SHORT=Paysera # suppress inspection "UnusedProperty" PAXUM_SHORT=Paxum # suppress inspection "UnusedProperty" +NEFT_SHORT=NEFT +# suppress inspection "UnusedProperty" +RTGS_SHORT=RTGS +# suppress inspection "UnusedProperty" +IMPS_SHORT=IMPS +# suppress inspection "UnusedProperty" +UPI_SHORT=UPI +# suppress inspection "UnusedProperty" +PAYTM_SHORT=PayTM +# suppress inspection "UnusedProperty" +NEQUI_SHORT=Nequi +# suppress inspection "UnusedProperty" +BIZUM_SHORT=Bizum +# suppress inspection "UnusedProperty" +PIX_SHORT=Pix +# suppress inspection "UnusedProperty" AMAZON_GIFT_CARD_SHORT=Amazon eGift Card # suppress inspection "UnusedProperty" BLOCK_CHAINS_INSTANT_SHORT=Altcoins Instant # suppress inspection "UnusedProperty" CAPITUAL_SHORT=Capitual # suppress inspection "UnusedProperty" +CELPAY_SHORT=CelPay +# suppress inspection "UnusedProperty" +MONESE_SHORT=Monese +# suppress inspection "UnusedProperty" +SATISPAY_SHORT=Satispay +# suppress inspection "UnusedProperty" +VERSE_SHORT=Verse +# suppress inspection "UnusedProperty" +STRIKE_SHORT=Strike +# suppress inspection "UnusedProperty" SWIFT_SHORT=SWIFT # Deprecated: Cannot be deleted as it would break old trade history entries diff --git a/core/src/main/resources/i18n/displayStrings_fr.properties b/core/src/main/resources/i18n/displayStrings_fr.properties index 418989485f0..a703fa31f13 100644 --- a/core/src/main/resources/i18n/displayStrings_fr.properties +++ b/core/src/main/resources/i18n/displayStrings_fr.properties @@ -284,6 +284,7 @@ mainView.walletServiceErrorMsg.rejectedTxException=Le réseau a rejeté une tran mainView.networkWarning.allConnectionsLost=Vous avez perdu la connexion avec tous les {0} pairs du réseau.\nVous avez peut-être perdu votre connexion Internet ou votre ordinateur était passé en mode veille. mainView.networkWarning.localhostBitcoinLost=Vous avez perdu la connexion avec le localhost Bitcoin node.\nVeuillez redémarrer l'application Bisq pour vous connecter à d'autres Bitcoin nodes ou redémarrer le localhost Bitcoin node. +mainView.networkWarning.clockWatcher=Your computer was asleep for {0} seconds. Standby mode has been known to cause trades to fail. In order to operate correctly Bisq requires that standby mode be disabled in your computer's settings. mainView.version.update=(Mise à jour disponible) @@ -821,7 +822,7 @@ portfolio.pending.mediationResult.popup.alreadyAccepted=Vous avez déjà accept portfolio.pending.failedTrade.taker.missingTakerFeeTx=Les frais de transaction du preneur sont manquants.\n\nSans ce tx, le trade ne peut être complété. Aucun des fonds n'a été verrouillé et aucun frais de trade a été payé. Vous pouvez déplacer ce trade vers les trades échoués. portfolio.pending.failedTrade.maker.missingTakerFeeTx=Le frais de transaction du pair preneur sont manquants.\n\nSans ce tx, le trade ne peut être complété. Aucun des fonds n'a été verrouillé. Votre offre est toujours disponible pour les autres traders, vous n'avez donc pas perdu les frais de maker. Vous pouvez déplacer ce trade vers les trades échoués. portfolio.pending.failedTrade.missingDepositTx=Cette transaction de dépôt (transaction multi-signature de 2 à 2) est manquante.\n\nSans ce tx, la transaction ne peut pas être complétée. Aucun fonds n'est bloqué, mais vos frais de transaction sont toujours payés. Vous pouvez lancer une demande de compensation des frais de transaction ici: [HYPERLINK:https://github.com/bisq-network/support/issues] \nN'hésitez pas à déplacer la transaction vers les transactions échouées. -portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=La transaction de paiement différée est manquante, mais les fonds ont été verrouillés dans la transaction de dépôt.\n\nVeuillez NE PAS envoyer de Fiat ou d'altcoin au vendeur de BTC, car avec le tx de paiement différé, le jugement ne peut être ouvert. À la place, ouvrez un ticket de médiation avec Cmd/Ctrl+O. Le médiateur devrait suggérer que les deux pairs reçoivent tous les deux le montant total de leurs dépôts de sécurité (le vendeur aussi doit recevoir le montant total du trade). De cette manière, il n'y a pas de risque de sécurisation, et seuls les frais du trade sont perdus.\n\nVous pouvez demander le remboursement des frais de trade perdus ici:\n[HYPERLINK:https://github.com/bisq-network/support/issues] +portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the fiat or altcoin payment to the BTC seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=La transaction de paiement différée est manquante, mais les fonds ont été verrouillés dans la transaction de dépôt.\n\nSi l'acheteur n'a pas non plus la transaction de paiement différée, il sera informé du fait de ne PAS envoyer le paiement et d'ouvrir un ticket de médiation à la place. Vous devriez aussi ouvrir un ticket de médiation avec Cmd/Ctrl+o.\n\nSi l'acheteur n'a pas encore envoyé le paiement, le médiateur devrait suggérer que les deux pairs reçoivent le montant total de leurs dépôts de sécurité (le vendeur doit aussi recevoir le montant total du trade). Sinon, le montant du trade revient à l'acheteur.\n\nVous pouvez effectuer une demande de remboursement pour les frais de trade perdus ici: [LIEN:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.errorMsgSet=Il y a eu une erreur durant l'exécution du protocole de trade.\n\nErreur: {0}\n\nIl est possible que cette erreur ne soit pas critique, et que le trade puisse être complété normalement. Si vous n'en êtes pas certain, ouvrez un ticket de médiation pour avoir des conseils de la part des médiateurs de Bisq.\n\nSi cette erreur est critique et que le trade ne peut être complété, il est possible que vous perdiez le frais du trade. Effectuez une demande de remboursement ici: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.missingContract=Le contrat de trade n'est pas complété.\n\nCe trade ne peut être complété et il est possible que vous ayez perdu votre frais de trade. Dans ce cas, vous pouvez demander un remboursement des frais de trade perdus ici: [HYPERLINK:https://github.com/bisq-network/support/issues] @@ -996,7 +997,9 @@ support.buyerTaker=Acheteur BTC/Taker support.sellerTaker=Vendeur BTC/Taker support.backgroundInfo=Bisq n'est pas une entreprise, donc elle traite les litiges différemment.\n\nLes traders peuvent communiquer au sein de l'application via un chat sécurisé sur l'écran des transactions ouvertes pour essayer de résoudre les litiges par eux-mêmes. Si cela ne suffit pas, un médiateur peut intervenir pour les aider. Le médiateur évaluera la situation et suggérera un paiement des fonds de transaction. Si les deux traders acceptent cette suggestion, la transaction de paiement est réalisée et l'échange est clos. Si un ou les deux traders n'acceptent pas le paiement suggéré par le médiateur, ils peuvent demander un arbitrage. L'arbitre réévaluera la situation et, si cela est justifié, remboursera personnellement le négociateur et demandera le remboursement de ce paiement à la DAO Bisq. -support.initialInfo=Please enter a description of your problem in the text field below. Add as much information as possible to speed up dispute resolution time.\n\nHere is a check list for information you should provide:\n\t● If you are the BTC buyer: Did you make the Fiat or Altcoin transfer? If so, did you click the 'payment started' button in the application?\n\t● If you are the BTC seller: Did you receive the Fiat or Altcoin payment? If so, did you click the 'payment received' button in the application?\n\t● Which version of Bisq are you using?\n\t● Which operating system are you using?\n\t● If you encountered an issue with failed transactions please consider switching to a new data directory.\n\t Sometimes the data directory gets corrupted and leads to strange bugs. \n\t See: https://bisq.wiki/Switching_to_a_new_data_directory\n\nPlease make yourself familiar with the basic rules for the dispute process:\n\t● You need to respond to the {0}''s requests within 2 days.\n\t● Mediators respond in between 2 days. Arbitrators respond in between 5 business days.\n\t● The maximum period for a dispute is 14 days.\n\t● You need to cooperate with the {1} and provide the information they request to make your case.\n\t● You accepted the rules outlined in the dispute document in the user agreement when you first started the application.\n\nYou can read more about the dispute process at: {2} +support.initialInfo=Please enter a description of your problem in the text field below. Add as much information as possible to speed up dispute resolution time.\n\nHere is a check list for information you should provide:\n\t● If you are the BTC buyer: Did you make the Fiat or Altcoin transfer? If so, did you click the 'payment started' button in the application?\n\t● If you are the BTC seller: Did you receive the Fiat or Altcoin payment? If so, did you click the 'payment received' button in the application?\n\t● Which version of Bisq are you using?\n\t● Which operating system are you using?\n\t● If you encountered an issue with failed transactions please consider switching to a new data directory.\n\t Sometimes the data directory gets corrupted and leads to strange bugs. \n\t See: https://bisq.wiki/Switching_to_a_new_data_directory\n\nPlease make yourself familiar with the basic rules for the dispute process:\n\t● You need to respond to the {0}''s requests within 2 days.\n\t● {1}\n\t● The maximum period for a dispute is 14 days.\n\t● You need to cooperate with the {2} and provide the information they request to make your case.\n\t● You accepted the rules outlined in the dispute document in the user agreement when you first started the application.\n\nYou can read more about the dispute process at: {3} +support.initialMediatorMsg=Mediators will generally reply to you within 24 hours.\n\t If you have not had a reply after 48 hours please feel free to reach out to your mediator on Keybase.\n\t Mediators usernames on Keybase are the same as their usernames within the Bisq app.\n\t Your mediator is: {0} +support.initialArbitratorMsg=Arbitrators will generally reply to you within 5 days.\n\t If you have not had a reply after 7 days please feel free to reach out to your arbitrator on Keybase.\n\t Arbitrators usernames on Keybase are the same as their usernames within the Bisq app.\n\t Your arbitrator is: {0} support.systemMsg=Message du système: {0} support.youOpenedTicket=Vous avez ouvert une demande de support.\n\n{0}\n\nBisq version: {1} support.youOpenedDispute=Vous avez ouvert une demande de litige.\n\n{0}\n\nBisq version: {1} @@ -1064,6 +1067,8 @@ setting.preferences.dao.resyncFromResources.popup=Après un redémarrage de l'ap setting.preferences.dao.resyncFromGenesis.popup=La synchronisation à partir de la transaction de genèse consomme beaucoup de temps et de ressources CPU. Êtes-vous sûr de vouloir resynchroniser ? En général, la resynchronisation à partir du dernier fichier de ressources est suffisante et plus rapide. \n\nAprès le redémarrage de l'application, les données de gestion du réseau Bisq seront rechargées à partir du nœud d'amorçage et l'état de synchronisation BSQ sera reconstruit à partir de la transaction initiale. setting.preferences.dao.resyncFromGenesis.resync=Resynchroniser depuis la genèse et fermer setting.preferences.dao.isDaoFullNode=Exécuter la DAO de Bisq en tant que full node +setting.preferences.dao.activated=DAO activated +setting.preferences.dao.activated.popup=The change will be applied after a restart setting.preferences.dao.rpcUser=Nom d'utilisateur RPC setting.preferences.dao.rpcPw=Mot de passe RPC setting.preferences.dao.blockNotifyPort=Bloquer le port de notification @@ -1922,15 +1927,10 @@ dao.news.bisqDAO.title=La DAO de BISQ dao.news.bisqDAO.description=Tout comme la plateforme d'échange Bisq est décentralisée et résistante à la censure, son modèle de gouvernance l'est aussi - ainsi que les jetons de la DAO de Bisq et BSQ sont les outils qui rendent cela possible. dao.news.bisqDAO.readMoreLink=En savoir plus sur la DAO de Bisq -dao.news.pastContribution.title=VOUS AVEZ PARTICIPÉ ANTÉRIEUREMENT ? DEMANDEZ DES BSQ -dao.news.pastContribution.description=Si vous avez participé à Bisq, veuillez utiliser l'adresse BSQ ci-dessous et faire une demande pour prendre part à la distribution genesis de BSQ. -dao.news.pastContribution.yourAddress=Adresse de votre portefeuille BSQ -dao.news.pastContribution.requestNow=Demander maintenant - -dao.news.daoInfo.title=LANCEZ LA DAO DE BISQ SUR NOTRE TESTNET -dao.news.daoInfo.description=Le mainnet de la DAO de Bisq n'est pas encore lancé mais vous pouvez en savoir plus sur la DAO de Bisq en l'exécutant sur notre testnet. -dao.news.daoInfo.firstSection.title=1. Passer sur le mode Testnet de la DAO -dao.news.daoInfo.firstSection.content=Passez au Testnet de la DAO à partir de l'écran des paramètres. +dao.news.daoInfo.title=ENABLE THE BISQ DAO +dao.news.daoInfo.description=To participate in the Bisq DAO and to use BSQ for discounted trading fees, you need to enable the DAO. When the DAO is enabled, Bisq downloads all missing blocks and verifies BSQ transactions. This verification process requires time, during which you may see Bisq use a lot of memory and processing power. This is normal. +dao.news.daoInfo.firstSection.title=1. Enable DAO +dao.news.daoInfo.firstSection.content=Enable the Bisq DAO and restart. dao.news.DAOOnTestnet.secondSection.title=2. Acquérir des BSQ dao.news.DAOOnTestnet.secondSection.content=Demander des BSQ sur Slack ou acheter des BSQ sur Bisq. dao.news.DAOOnTestnet.thirdSection.title=3. Participer à un cycle de vote @@ -2679,6 +2679,7 @@ payment.secret=Question secrète payment.answer=Réponse payment.wallet=ID du portefeuille payment.capitual.cap=CAP Code +payment.upi.virtualPaymentAddress=Virtual Payment Address # suppress inspection "UnusedProperty" payment.swift.headline=International SWIFT Wire Transfer @@ -2767,11 +2768,65 @@ payment.amazonGiftCard.upgrade=La méthode de paiement par carte cadeaux Amazon payment.account.amazonGiftCard.addCountryInfo={0}\nVotre compte carte cadeau Amazon existant ({1}) n'a pas de pays spécifié.\nVeuillez entrer le pays de votre compte carte cadeau Amazon pour mettre à jour les données de votre compte.\nCeci n'affectera pas le statut de l'âge du compte. payment.amazonGiftCard.upgrade.headLine=Mettre à jour le compte cartes cadeaux Amazon -payment.swift.info=Carefully review the core guidelines for using SWIFT on Bisq:\n\n- fill all fields completely and accurately \n- buyer must send payment in currency specified by the offer maker \n- buyer must use the shared fee model (SHA) \n- buyer and seller may incur fees, so they should check their bank's fee schedules beforehand \n\nSWIFT is more sophisticated than other payment methods, so please take a moment to review full guidance on the wiki [HYPERLINK:https://bisq.wiki/SWIFT]. +payment.swift.info.account=Carefully review the core guidelines for using SWIFT on Bisq:\n\n- fill all fields completely and accurately \n- buyer must send payment in currency specified by the offer maker \n- buyer must use the shared fee model (SHA) \n- buyer and seller may incur fees, so they should check their bank's fee schedules beforehand \n\nSWIFT is more sophisticated than other payment methods, so please take a moment to review full guidance on the wiki [HYPERLINK:https://bisq.wiki/SWIFT]. payment.swift.info.buyer=To buy bitcoin with SWIFT, you must:\n\n- send payment in the currency specified by the offer maker \n- use the shared fee model (SHA) to send payment\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. -payment.swift.info.seller=SWIFT senders are required to use the shared payment model (SHA) to send payments.\n\nIf you receive a SWIFT payment that does not use SHA, open a mediation ticket.\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. +payment.swift.info.seller=SWIFT senders are required to use the shared fee model (SHA) to send payments.\n\nIf you receive a SWIFT payment that does not use SHA, open a mediation ticket.\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. + +payment.imps.info.account=Please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a maximum of Rs. 200,000 that can be sent per transaction. If you are trading over this amount multiple transactions will be needed. However be aware their is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nSome banks have different limits for their customers. +payment.imps.info.buyer=Please send payment only to the account details provided in Bisq.\n\nThe maximum trade size is Rs. 200,000 per transaction.\n\nIf your trade is over Rs. 200,000 you will have to make multiple transfers. However be aware their is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nPlease note some banks have different limits for their customers. +payment.imps.info.seller=If you intend to receive over Rs. 200,000 per trade you should expect the buyer to have to make multiple transfers. However be aware there is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nPlease note some banks have different limits for their customers. + +payment.neft.info.account=Please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a maximum of Rs. 50,000 that can be sent per transaction. If you are trading over this amount multiple transactions will be needed.\n\nPlease note some banks have different limits for their customers. +payment.neft.info.buyer=Please send payment only to the account details provided in Bisq.\n\nThe maximum trade size is Rs. 50,000 per transaction.\n\nIf your trade is over Rs. 50,000 you will have to make multiple transfers.\n\nPlease note some banks have different limits for their customers. +payment.neft.info.seller=If you intend to receive over Rs. 50,000 per trade you should expect the buyer to have to make multiple transfers.\n\nPlease note some banks have different limits for their customers. + +payment.paytm.info.account=Please make sure to include your email or phone number that matches your email or phone number in your PayTM account. \n\nWhen users set up a PayTM account with No KYC users are limited to: \n\n● Maximum of Rs. 5,000 can be sent per transaction.\n● Maximum of Rs. 10,000 can be held in someone's PayTM wallet.\n\nIf you intend to trade amount of over 5,000 per trade you will need to complete KYC with PayTM. With KYC users are limited to:\n\n● Maximum of Rs. 100,000 can be sent per transaction.\n● Maximum of Rs. 100,000 can be held in someone's PayTM wallet.\n\nUsers should also be aware of account limits. Trades above PayTM account limits will likely have to take place over more than one day, or, be cancelled. +payment.paytm.info.buyer=Please send payment only to the email address or phone number provided.\n\nIf you intend to trade amount of over Rs. 5,000 per trade you will need to complete KYC with PayTM.\n\nWith No KYC Rs. 5,000 can be sent per transaction.\n\nWith KYC users Rs. 100,000 can be sent per transaction. +payment.paytm.info.seller=If you intend to trade amount of over Rs. 5,000 per trade you will need to complete KYC with PayTM. With KYC users are limited to:\n\n● Maximum of Rs. 100,000 can be sent per transaction.\n● Maximum of Rs. 100,000 can be held in your PayTM wallet .\n\nUsers should also be aware of account limits. As a maximum of Rs. 100,000 can be held in your PayTM wallet please make sure you transfer out your rupees regularly. + +payment.rtgs.info.account=RTGS is for payments of large trades of Rs. 200,000 or over.\n\nWhen setting up your RTGS payment account please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). +payment.rtgs.info.buyer=Please send payment only to the account details provided in Bisq.\n\nPlease be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). +payment.rtgs.info.seller=Please be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). + +payment.upi.info.account=Please make sure to include your Virtual Payment Address (VPA) also called your UPI ID. The format for this is like an email ID: with the sign “@” in the middle. For example, your UPI ID could be “receiver’s_name@bank_name” or “phone_number@bank_name.” \n\nFor UPI there is a maximum limit of Rs. 100,000 that can be sent per transaction. \n\nIf you intend to trade amount of over Rs. 100,000 per trade it is likely trades will have to take place over multiple transfers. \n\nPlease note some banks have different limits for their customers. +payment.upi.info.buyer=Please send payment only to the VPA / UPI ID provided in Bisq. \n\nThe maximum trade size is Rs. 100,000 per transaction. \n\nIf your trade is over Rs. 100,000 you will have to make multiple transfers. \n\nPlease note some banks have different limits for their customers. +payment.upi.info.seller=If you intend to receive over Rs. 100,000 per trade you should expect the buyer to have to make multiple transfers. \n\nPlease note some banks have different limits for their customers. + +payment.celpay.info.account=Please make sure to include the email your Celsius account is registered to. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account.\n\nCelPay users are limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Buyers can send any matching currency stablecoin to the BTC Seller. +payment.celpay.info.buyer=Please send payment only to the email address provided by the BTC Seller by sending a payment link.\n\nCelPay is limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Buyers can send any matching currency stablecoin to the BTC Seller. +payment.celpay.info.seller=BTC Sellers should expect to receive payment via a secure payment link. Please make sure the email payment link contains the email address provided by the BTC Buyer.\n\nCelPay users are limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Sellers should expect to receive any matching currency stablecoin from the BTC Buyer. It is possible for the BTC Buyer to send any matching currency stablecoin. +payment.celpay.supportedCurrenciesForReceiver=Supported currencies (please note: all the currencies below are supported stable coins within the Celcius app. Trades are for stable coins, not fiat.) + +payment.nequi.info.account=Please make sure to include your phone number that is associated with your Nequi account.\n\nWhen users set up a Nequi account payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. +payment.nequi.info.buyer=Please send payment only to the phone number provided in the BTC Seller's Bisq account.\n\nWhen users set up a Nequi account, payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. +payment.nequi.info.seller=Please check that the payment received matches the phone number provided in the BTC Buyer's Bisq account.\n\nWhen users set up a Nequi account, payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. + +payment.bizum.info.account=To use Bizum you need a bank account (IBAN) in Spain and to be registered for the service.\n\nBizum can be used for trades between €0.50 and €1,000.\n\nThe maximum amount of transactions you can send/receive using Bizum is €2,000 Euros per day.\n\nBizum users can have a maximum of 150 operations per month.\n\nEach bank however may establish its own limits, within the above limits, for its clients.\n\nTraders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.bizum.info.buyer=Please send payment only to the BTC Seller's mobile phone number as provided in Bisq.\n\nThe maximum trade size is €1,000 per payment. The maximum amount of transactions you can send using Bizum is €2,000 Euros per day.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.bizum.info.seller=Please make sure your payment is received from the BTC Buyer's mobile phone number as provided in Bisq.\n\nThe maximum trade size is €1,000 per payment. The maximum amount of transactions you can receive using Bizum is €2,000 Euros per day.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. + +payment.pix.info.account=Please make sure to include your chosen Pix Key. There are four types of keys: CPF (Natural Persons Register) or CNPJ (National Registry of Legal Entities), e-mail address, telephone number or a random key generated by the system called a universally unique identifier (UUID). A different key must be used for each Pix account you have. Individuals can create up to five keys for each account they own.\n\nWhen trading on Bisq, BTC Buyers should use their Pix Keys as the payment description so that it is easy for the BTC Sellers to identify the payment as coming from themselves. +payment.pix.info.buyer=Please send payment only the Pix Key provided in the BTC Seller's Bisq account.\n\nPlease use your Pix Key as the payment reference so that it is easy for the BTC Seller to identify the payment as coming from yourself. +payment.pix.info.seller=Please check that the payment received description matches the Pix Key provided in the BTC Buyer's Bisq account. +payment.pix.key=Pix Key (CPF, CNPJ, Email, Phone number or UUID) + +payment.monese.info.account=Monese is a bank app for users of GBP, EUR and RON*. Monese allows users to send money to other Monese accounts instantly and for free in any supported currency.\n\n*To open a RON account in Monese, you need to either live in Romania or have Romanian citizenship.\n\nWhen setting up your Monese account in Bisq please make sure to include your name and phone number that matches your Monese account. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account. +payment.monese.info.buyer=Please send payment only to the phone number provided by the BTC Seller in their Bisq account. Please leave the payment description blank. +payment.monese.info.seller=BTC Sellers should expect to receive payment from the phone number / name shown in the BTC Buyer's Bisq account. + +payment.satispay.info.account=To use Satispay you need a bank account (IBAN) in Italy and to be registered for the service.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.satispay.info.buyer=Please send payment only to the BTC Seller's mobile phone number as provided in Bisq.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.satispay.info.seller=Please make sure your payment is received from the BTC Buyer's mobile phone number / name as provided in Bisq.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. + +payment.verse.info.account=Verse is a multiple currency payment method that can send and receive payment in EUR, SEK, HUF, DKK, PLN.\n\nWhen setting up your Verse account in Bisq please make sure to include the username that matches your username in your Verse account. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. +payment.verse.info.buyer=Please send payment only to the username provided by the BTC Seller in their Bisq account. Please leave the payment description blank.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. +payment.verse.info.seller=BTC Sellers should expect to receive payment from the username shown in the BTC Buyer's Bisq account.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. + +payment.strike.info.account=Please make sure to include your Strike username.\n\nIn Bisq, Strike is used for fiat to fiat payments only.\n\nPlease make sure you are aware of the Strike limits:\n\nUsers who have registered with only their email, name, and phone number have the following limits:\n\n● $100 maximum per deposit\n● $1,000 maximum total deposits per week\n● $100 maximum per payment\n\nUsers can increase their limits by providing Strike with more information. These users have the following limits:\n\n● $1,000 maximum per deposit\n● $1,000 maximum total deposits per week\n● $1,000 maximum per payment\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.strike.info.buyer=Please send payment only to the BTC Seller's Strike username as provided in Bisq.\n\nThe maximum trade size is $1,000 per payment.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.strike.info.seller=Please make sure your payment is received from the BTC Buyer's Strike username as provided in Bisq.\n\nThe maximum trade size is $1,000 per payment.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. payment.usPostalMoneyOrder.info=Pour échanger US Postal Money Orders (USPMO) sur Bisq, vous devez comprendre les termes suivants: \n\n- L'acheteur BTC doit écrire le nom du vendeur BTC dans les champs expéditeur et bénéficiaire, et prendre une photo à haute résolution de USPMO et de l'enveloppe avec une preuve de suivi avant l'envoi. \n\n- L'acheteur BTC doit envoyer USPMO avec la confirmation de livraison au vendeur BTC. \n\nSi une médiation est nécessaire, ou s'il y a un différend de transaction, vous devrez envoyer la photo avec le numéro USPMO, le numéro du bureau de poste et le montant de la transaction au médiateur Bisq ou à l'agent de remboursement afin qu'il puisse vérifier les détails sur le site web de la poste américaine. \n\nSi vous ne fournissez pas les données de transaction requises, vous perdrez directement le litige\n\nDans tous les cas de litige, l'expéditeur de l'USPMO assume à 100% la responsabilité lors de la fourniture de preuves / certification au médiateur ou à l'arbitre. \n\nSi vous ne comprenez pas ces exigences, veuillez ne pas échanger USPMO sur Bisq. @@ -2792,6 +2847,8 @@ payment.f2f.info='Face to Face' trades have different rules and come with differ payment.f2f.info.openURL=Ouvrir la page web payment.f2f.offerbook.tooltip.countryAndCity=Pays et ville: {0} / {1} payment.f2f.offerbook.tooltip.extra=Informations complémentaires: {0} +payment.ifsc=IFS Code +payment.ifsc.validation=IFSC format: XXXX0999999 payment.japan.bank=Banque payment.japan.branch=Filiale @@ -2800,7 +2857,7 @@ payment.japan.recipient=Nom payment.australia.payid=PayID payment.payid=Un PayID lié à une institution financière. Comme une adresse e-mail ou un téléphone portable. payment.payid.info=Un PayID, tel qu'un numéro de téléphone, une adresse électronique ou un numéro d'entreprise Australienne (ABN), que vous pourrez lier en toute sécurité à votre compte bancaire, votre crédit mutuel ou votre société de crédit immobilier. Vous devez avoir déjà créé un PayID auprès de votre institution financière Australienne. Les institutions financières émettrices et réceptrices doivent toutes deux prendre en charge PayID. Pour plus d'informations, veuillez consulter [HYPERLINK:https://payid.com.au/faqs/] -payment.amazonGiftCard.info=Pour payer avec une carte cadeau Amazon eGift Card, vous devrez envoyer une carte cadeau Amazon eGift Card au vendeur de BTC via votre compte Amazon. \n\nBisq indiquera l'adresse e-mail ou le numéro de téléphone du vendeur BTC où la carte cadeau doit être envoyée, et vous devrez inclure l'ID du trade dans le champ du message de la carte cadeau. Veuillez consulter le wiki [HYPERLINK:https://bisq.wiki/Amazon_eGift_card] pour plus de détails et pour les meilleures pratiques à adopter. \n\nTrois remarques importantes :\n- essayez d'envoyer des cartes-cadeaux d'un montant inférieur ou égal à 100 USD, car Amazon est connu pour signaler les cartes-cadeaux plus importantes comme frauduleuses\n- essayez d'utiliser un texte créatif et crédible pour le message de la carte cadeau (par exemple, "Joyeux anniversaire Susan !") incluant l'ID du trade (et utilisez le chat du trade pour indiquer à votre pair de trading le texte de référence que vous avez choisi afin qu'il puisse vérifier votre paiement).\n- Les cartes cadeaux électroniques Amazon ne peuvent être échangées que sur le site Amazon où elles ont été achetées (par exemple, une carte cadeau achetée sur amazon.it ne peut être échangée que sur amazon.it). +payment.amazonGiftCard.info=To pay with Amazon eGift Card, you will need to send an Amazon eGift Card to the BTC seller via your Amazon account. \n\nPlease see the wiki [HYPERLINK:https://bisq.wiki/Amazon_eGift_card] for further details and best practices. \n\nThree important notes:\n- try to send gift cards with amounts of 100 USD or smaller, as Amazon is known to flag larger gift cards as fraudulent\n- try to use creative, believable text for the gift card''s message (e.g., "Happy birthday Susan!") and use trader chat to tell your trading peer the reference text you picked so they can verify your payment\n- Amazon eGift Cards can only be redeemed on the Amazon website they were purchased on (e.g., a gift card purchased on amazon.it can only be redeemed on amazon.it) # We use constants from the code so we do not use our normal naming convention # dynamic values are not recognized by IntelliJ @@ -2885,12 +2942,38 @@ PAYSERA=Paysera # suppress inspection "UnusedProperty" PAXUM=Paxum # suppress inspection "UnusedProperty" +NEFT=India/NEFT +# suppress inspection "UnusedProperty" +RTGS=India/RTGS +# suppress inspection "UnusedProperty" +IMPS=India/IMPS +# suppress inspection "UnusedProperty" +UPI=India/UPI +# suppress inspection "UnusedProperty" +PAYTM=India/PayTM +# suppress inspection "UnusedProperty" +NEQUI=Nequi +# suppress inspection "UnusedProperty" +BIZUM=Bizum +# suppress inspection "UnusedProperty" +PIX=Pix +# suppress inspection "UnusedProperty" AMAZON_GIFT_CARD=eCarte cadeau Amazon # suppress inspection "UnusedProperty" BLOCK_CHAINS_INSTANT=Altcoins Instant # suppress inspection "UnusedProperty" CAPITUAL=Capitual # suppress inspection "UnusedProperty" +CELPAY=CelPay +# suppress inspection "UnusedProperty" +MONESE=Monese +# suppress inspection "UnusedProperty" +SATISPAY=Satispay +# suppress inspection "UnusedProperty" +VERSE=Verse +# suppress inspection "UnusedProperty" +STRIKE=Strike +# suppress inspection "UnusedProperty" SWIFT=SWIFT International Wire Transfer # Deprecated: Cannot be deleted as it would break old trade history entries @@ -2945,12 +3028,38 @@ PAYSERA_SHORT=Paysera # suppress inspection "UnusedProperty" PAXUM_SHORT=Paxum # suppress inspection "UnusedProperty" +NEFT_SHORT=NEFT +# suppress inspection "UnusedProperty" +RTGS_SHORT=RTGS +# suppress inspection "UnusedProperty" +IMPS_SHORT=IMPS +# suppress inspection "UnusedProperty" +UPI_SHORT=UPI +# suppress inspection "UnusedProperty" +PAYTM_SHORT=PayTM +# suppress inspection "UnusedProperty" +NEQUI_SHORT=Nequi +# suppress inspection "UnusedProperty" +BIZUM_SHORT=Bizum +# suppress inspection "UnusedProperty" +PIX_SHORT=Pix +# suppress inspection "UnusedProperty" AMAZON_GIFT_CARD_SHORT=eCarte cadeau Amazon # suppress inspection "UnusedProperty" BLOCK_CHAINS_INSTANT_SHORT=Altcoins Instant # suppress inspection "UnusedProperty" CAPITUAL_SHORT=Capitual # suppress inspection "UnusedProperty" +CELPAY_SHORT=CelPay +# suppress inspection "UnusedProperty" +MONESE_SHORT=Monese +# suppress inspection "UnusedProperty" +SATISPAY_SHORT=Satispay +# suppress inspection "UnusedProperty" +VERSE_SHORT=Verse +# suppress inspection "UnusedProperty" +STRIKE_SHORT=Strike +# suppress inspection "UnusedProperty" SWIFT_SHORT=SWIFT # Deprecated: Cannot be deleted as it would break old trade history entries diff --git a/core/src/main/resources/i18n/displayStrings_it.properties b/core/src/main/resources/i18n/displayStrings_it.properties index 968ddd8e7cb..cdaa623fbd9 100644 --- a/core/src/main/resources/i18n/displayStrings_it.properties +++ b/core/src/main/resources/i18n/displayStrings_it.properties @@ -284,6 +284,7 @@ mainView.walletServiceErrorMsg.rejectedTxException=Una transazione è stata rifi mainView.networkWarning.allConnectionsLost=Hai perso la connessione a tutti i {0} peer di rete.\nForse hai perso la connessione a Internet o il computer era in modalità standby. mainView.networkWarning.localhostBitcoinLost=Hai perso la connessione al nodo Bitcoin in localhost.\nRiavvia l'applicazione Bisq per connetterti ad altri nodi Bitcoin o riavvia il nodo Bitcoin in localhost. +mainView.networkWarning.clockWatcher=Your computer was asleep for {0} seconds. Standby mode has been known to cause trades to fail. In order to operate correctly Bisq requires that standby mode be disabled in your computer's settings. mainView.version.update=(Aggiornamento disponibile) @@ -821,7 +822,7 @@ portfolio.pending.mediationResult.popup.alreadyAccepted=Hai già accettato portfolio.pending.failedTrade.taker.missingTakerFeeTx=The taker fee transaction is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked and no trade fee has been paid. You can move this trade to failed trades. portfolio.pending.failedTrade.maker.missingTakerFeeTx=The peer's taker fee transaction is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked. Your offer is still available to other traders, so you have not lost the maker fee. You can move this trade to failed trades. portfolio.pending.failedTrade.missingDepositTx=The deposit transaction (the 2-of-2 multisig transaction) is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked but your trade fee has been paid. You can make a request to be reimbursed the trade fee here: [HYPERLINK:https://github.com/bisq-network/support/issues]\n\nFeel free to move this trade to failed trades. -portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the fiat or altcoin payment to the BTC seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] +portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the fiat or altcoin payment to the BTC seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing but funds have been locked in the deposit transaction.\n\nIf the buyer is also missing the delayed payout transaction, they will be instructed to NOT send the payment and open a mediation ticket instead. You should also open a mediation ticket with Cmd/Ctrl+o. \n\nIf the buyer has not sent payment yet, the mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). Otherwise the trade amount should go to the buyer. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.errorMsgSet=There was an error during trade protocol execution.\n\nError: {0}\n\nIt might be that this error is not critical, and the trade can be completed normally. If you are unsure, open a mediation ticket to get advice from Bisq mediators. \n\nIf the error was critical and the trade cannot be completed, you might have lost your trade fee. Request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.missingContract=The trade contract is not set.\n\nThe trade cannot be completed and you might have lost your trade fee. If so, you can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] @@ -996,7 +997,9 @@ support.buyerTaker=Acquirente/Taker BTC support.sellerTaker=Venditore/Taker BTC support.backgroundInfo=Bisq non è una società, quindi gestisce le controversie in modo diverso.\n\nI trader possono comunicare all'interno dell'applicazione tramite chat sicura nella schermata degli scambi aperti per provare a risolvere le controversie da soli. Se ciò non è sufficiente, un mediatore può intervenire per aiutare. Il mediatore valuterà la situazione e suggerirà un pagamento di fondi commerciali. Se entrambi i trader accettano questo suggerimento, la transazione di pagamento è completata e lo scambio è chiuso. Se uno o entrambi i trader non accettano il pagamento suggerito dal mediatore, possono richiedere l'arbitrato. L'arbitro rivaluterà la situazione e, se garantito, ripagherà personalmente il trader e chiederà il rimborso per questo pagamento dal DAO Bisq. -support.initialInfo=Please enter a description of your problem in the text field below. Add as much information as possible to speed up dispute resolution time.\n\nHere is a check list for information you should provide:\n\t● If you are the BTC buyer: Did you make the Fiat or Altcoin transfer? If so, did you click the 'payment started' button in the application?\n\t● If you are the BTC seller: Did you receive the Fiat or Altcoin payment? If so, did you click the 'payment received' button in the application?\n\t● Which version of Bisq are you using?\n\t● Which operating system are you using?\n\t● If you encountered an issue with failed transactions please consider switching to a new data directory.\n\t Sometimes the data directory gets corrupted and leads to strange bugs. \n\t See: https://bisq.wiki/Switching_to_a_new_data_directory\n\nPlease make yourself familiar with the basic rules for the dispute process:\n\t● You need to respond to the {0}''s requests within 2 days.\n\t● Mediators respond in between 2 days. Arbitrators respond in between 5 business days.\n\t● The maximum period for a dispute is 14 days.\n\t● You need to cooperate with the {1} and provide the information they request to make your case.\n\t● You accepted the rules outlined in the dispute document in the user agreement when you first started the application.\n\nYou can read more about the dispute process at: {2} +support.initialInfo=Please enter a description of your problem in the text field below. Add as much information as possible to speed up dispute resolution time.\n\nHere is a check list for information you should provide:\n\t● If you are the BTC buyer: Did you make the Fiat or Altcoin transfer? If so, did you click the 'payment started' button in the application?\n\t● If you are the BTC seller: Did you receive the Fiat or Altcoin payment? If so, did you click the 'payment received' button in the application?\n\t● Which version of Bisq are you using?\n\t● Which operating system are you using?\n\t● If you encountered an issue with failed transactions please consider switching to a new data directory.\n\t Sometimes the data directory gets corrupted and leads to strange bugs. \n\t See: https://bisq.wiki/Switching_to_a_new_data_directory\n\nPlease make yourself familiar with the basic rules for the dispute process:\n\t● You need to respond to the {0}''s requests within 2 days.\n\t● {1}\n\t● The maximum period for a dispute is 14 days.\n\t● You need to cooperate with the {2} and provide the information they request to make your case.\n\t● You accepted the rules outlined in the dispute document in the user agreement when you first started the application.\n\nYou can read more about the dispute process at: {3} +support.initialMediatorMsg=Mediators will generally reply to you within 24 hours.\n\t If you have not had a reply after 48 hours please feel free to reach out to your mediator on Keybase.\n\t Mediators usernames on Keybase are the same as their usernames within the Bisq app.\n\t Your mediator is: {0} +support.initialArbitratorMsg=Arbitrators will generally reply to you within 5 days.\n\t If you have not had a reply after 7 days please feel free to reach out to your arbitrator on Keybase.\n\t Arbitrators usernames on Keybase are the same as their usernames within the Bisq app.\n\t Your arbitrator is: {0} support.systemMsg=Messaggio di sistema: {0} support.youOpenedTicket=Hai aperto una richiesta di supporto.\n\n{0}\n\nVersione Bisq: {1} support.youOpenedDispute=Hai aperto una richiesta per una controversia.\n\n{0}\n\nVersione Bisq: {1} @@ -1064,6 +1067,8 @@ setting.preferences.dao.resyncFromResources.popup=After an application restart t setting.preferences.dao.resyncFromGenesis.popup=A resync from genesis transaction can take considerable time and CPU resources. Are you sure you want to do that? Mostly a resync from latest resource files is sufficient and much faster.\n\nIf you proceed, after an application restart the Bisq network governance data will be reloaded from the seed nodes and the BSQ consensus state will be rebuilt from the genesis transaction. setting.preferences.dao.resyncFromGenesis.resync=Resync from genesis and shutdown setting.preferences.dao.isDaoFullNode=Lancia Bisq come full node DAO +setting.preferences.dao.activated=DAO activated +setting.preferences.dao.activated.popup=The change will be applied after a restart setting.preferences.dao.rpcUser=Username RPC setting.preferences.dao.rpcPw=Password RPC setting.preferences.dao.blockNotifyPort=Blocca porta di notifica @@ -1922,15 +1927,10 @@ dao.news.bisqDAO.title=LA DAO BISQ dao.news.bisqDAO.description=Proprio come lo scambio di Bisq è decentralizzato e resistente alla censura, così è il suo modello di governance - e il token Bisq DAO e BSQ sono gli strumenti che lo rendono possibile. dao.news.bisqDAO.readMoreLink=Ulteriori informazioni sulla Bisq DAO -dao.news.pastContribution.title=HAI EFFETTUATO CONTRIBUTI IN PASSATO? RICHIEDI BSQ -dao.news.pastContribution.description=Se hai contribuito a Bisq, utilizza l'indirizzo BSQ qui sotto e fai una richiesta per prendere parte alla distribuzione genesi BSQ. -dao.news.pastContribution.yourAddress=L'indirizzo del tuo wallet BSQ -dao.news.pastContribution.requestNow=Richiedi ora - -dao.news.daoInfo.title=ESEGUI LA BISQ DAO NEL NOSTRO TESTNET -dao.news.daoInfo.description=La mainnet Bisq DAO non è ancora stata lanciata, ma puoi conoscere Bisq DAO eseguendolo sulla nostra testnet. -dao.news.daoInfo.firstSection.title=1. Passa alla modalità Testnet DAO -dao.news.daoInfo.firstSection.content=Passa a DAO Testnet dalla schermata Impostazioni. +dao.news.daoInfo.title=ENABLE THE BISQ DAO +dao.news.daoInfo.description=To participate in the Bisq DAO and to use BSQ for discounted trading fees, you need to enable the DAO. When the DAO is enabled, Bisq downloads all missing blocks and verifies BSQ transactions. This verification process requires time, during which you may see Bisq use a lot of memory and processing power. This is normal. +dao.news.daoInfo.firstSection.title=1. Enable DAO +dao.news.daoInfo.firstSection.content=Enable the Bisq DAO and restart. dao.news.DAOOnTestnet.secondSection.title=2. Acquista un po' di BSQ dao.news.DAOOnTestnet.secondSection.content=Richiedi BSQ su Slack o Acquista BSQ su Bisq. dao.news.DAOOnTestnet.thirdSection.title=3. Partecipa a un Ciclo di Votazione @@ -2398,7 +2398,7 @@ popup.info.multiplePaymentAccounts.headline=Disponibili più conti di pagamento popup.info.multiplePaymentAccounts.msg=Hai più account di pagamento disponibili per questa offerta. Assicurati di aver scelto quello giusto. popup.accountSigning.selectAccounts.headline=Seleziona conti di pagamento -popup.accountSigning.selectAccounts.description=In base al metodo di pagamento e al momento in cui verranno selezionati tutti i conti di pagamento collegati a una controversia in cui si è verificato un pagamento +popup.accountSigning.selectAccounts.description=In base al metodo di pagamento e al momento in cui verranno selezionati tutti i conti di pagamento collegati a una controversia in cui si è verificato un pagamento popup.accountSigning.selectAccounts.signAll=Firma tutti i metodi di pagamento popup.accountSigning.selectAccounts.datePicker=Seleziona il momento in cui verranno firmati gli account @@ -2679,6 +2679,7 @@ payment.secret=Domanda segreta payment.answer=Risposta payment.wallet=ID portafoglio payment.capitual.cap=CAP Code +payment.upi.virtualPaymentAddress=Virtual Payment Address # suppress inspection "UnusedProperty" payment.swift.headline=International SWIFT Wire Transfer @@ -2767,11 +2768,65 @@ payment.amazonGiftCard.upgrade=Amazon gift cards payment method requires the cou payment.account.amazonGiftCard.addCountryInfo={0}\nYour existing Amazon Gift Card account ({1}) does not have a Country specified.\nPlease enter your Amazon Gift Card Country to update your account data.\nThis will not affect your account age status. payment.amazonGiftCard.upgrade.headLine=Update Amazon Gift Card account -payment.swift.info=Carefully review the core guidelines for using SWIFT on Bisq:\n\n- fill all fields completely and accurately \n- buyer must send payment in currency specified by the offer maker \n- buyer must use the shared fee model (SHA) \n- buyer and seller may incur fees, so they should check their bank's fee schedules beforehand \n\nSWIFT is more sophisticated than other payment methods, so please take a moment to review full guidance on the wiki [HYPERLINK:https://bisq.wiki/SWIFT]. +payment.swift.info.account=Carefully review the core guidelines for using SWIFT on Bisq:\n\n- fill all fields completely and accurately \n- buyer must send payment in currency specified by the offer maker \n- buyer must use the shared fee model (SHA) \n- buyer and seller may incur fees, so they should check their bank's fee schedules beforehand \n\nSWIFT is more sophisticated than other payment methods, so please take a moment to review full guidance on the wiki [HYPERLINK:https://bisq.wiki/SWIFT]. payment.swift.info.buyer=To buy bitcoin with SWIFT, you must:\n\n- send payment in the currency specified by the offer maker \n- use the shared fee model (SHA) to send payment\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. -payment.swift.info.seller=SWIFT senders are required to use the shared payment model (SHA) to send payments.\n\nIf you receive a SWIFT payment that does not use SHA, open a mediation ticket.\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. +payment.swift.info.seller=SWIFT senders are required to use the shared fee model (SHA) to send payments.\n\nIf you receive a SWIFT payment that does not use SHA, open a mediation ticket.\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. + +payment.imps.info.account=Please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a maximum of Rs. 200,000 that can be sent per transaction. If you are trading over this amount multiple transactions will be needed. However be aware their is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nSome banks have different limits for their customers. +payment.imps.info.buyer=Please send payment only to the account details provided in Bisq.\n\nThe maximum trade size is Rs. 200,000 per transaction.\n\nIf your trade is over Rs. 200,000 you will have to make multiple transfers. However be aware their is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nPlease note some banks have different limits for their customers. +payment.imps.info.seller=If you intend to receive over Rs. 200,000 per trade you should expect the buyer to have to make multiple transfers. However be aware there is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nPlease note some banks have different limits for their customers. + +payment.neft.info.account=Please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a maximum of Rs. 50,000 that can be sent per transaction. If you are trading over this amount multiple transactions will be needed.\n\nPlease note some banks have different limits for their customers. +payment.neft.info.buyer=Please send payment only to the account details provided in Bisq.\n\nThe maximum trade size is Rs. 50,000 per transaction.\n\nIf your trade is over Rs. 50,000 you will have to make multiple transfers.\n\nPlease note some banks have different limits for their customers. +payment.neft.info.seller=If you intend to receive over Rs. 50,000 per trade you should expect the buyer to have to make multiple transfers.\n\nPlease note some banks have different limits for their customers. + +payment.paytm.info.account=Please make sure to include your email or phone number that matches your email or phone number in your PayTM account. \n\nWhen users set up a PayTM account with No KYC users are limited to: \n\n● Maximum of Rs. 5,000 can be sent per transaction.\n● Maximum of Rs. 10,000 can be held in someone's PayTM wallet.\n\nIf you intend to trade amount of over 5,000 per trade you will need to complete KYC with PayTM. With KYC users are limited to:\n\n● Maximum of Rs. 100,000 can be sent per transaction.\n● Maximum of Rs. 100,000 can be held in someone's PayTM wallet.\n\nUsers should also be aware of account limits. Trades above PayTM account limits will likely have to take place over more than one day, or, be cancelled. +payment.paytm.info.buyer=Please send payment only to the email address or phone number provided.\n\nIf you intend to trade amount of over Rs. 5,000 per trade you will need to complete KYC with PayTM.\n\nWith No KYC Rs. 5,000 can be sent per transaction.\n\nWith KYC users Rs. 100,000 can be sent per transaction. +payment.paytm.info.seller=If you intend to trade amount of over Rs. 5,000 per trade you will need to complete KYC with PayTM. With KYC users are limited to:\n\n● Maximum of Rs. 100,000 can be sent per transaction.\n● Maximum of Rs. 100,000 can be held in your PayTM wallet .\n\nUsers should also be aware of account limits. As a maximum of Rs. 100,000 can be held in your PayTM wallet please make sure you transfer out your rupees regularly. + +payment.rtgs.info.account=RTGS is for payments of large trades of Rs. 200,000 or over.\n\nWhen setting up your RTGS payment account please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). +payment.rtgs.info.buyer=Please send payment only to the account details provided in Bisq.\n\nPlease be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). +payment.rtgs.info.seller=Please be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). + +payment.upi.info.account=Please make sure to include your Virtual Payment Address (VPA) also called your UPI ID. The format for this is like an email ID: with the sign “@” in the middle. For example, your UPI ID could be “receiver’s_name@bank_name” or “phone_number@bank_name.” \n\nFor UPI there is a maximum limit of Rs. 100,000 that can be sent per transaction. \n\nIf you intend to trade amount of over Rs. 100,000 per trade it is likely trades will have to take place over multiple transfers. \n\nPlease note some banks have different limits for their customers. +payment.upi.info.buyer=Please send payment only to the VPA / UPI ID provided in Bisq. \n\nThe maximum trade size is Rs. 100,000 per transaction. \n\nIf your trade is over Rs. 100,000 you will have to make multiple transfers. \n\nPlease note some banks have different limits for their customers. +payment.upi.info.seller=If you intend to receive over Rs. 100,000 per trade you should expect the buyer to have to make multiple transfers. \n\nPlease note some banks have different limits for their customers. + +payment.celpay.info.account=Please make sure to include the email your Celsius account is registered to. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account.\n\nCelPay users are limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Buyers can send any matching currency stablecoin to the BTC Seller. +payment.celpay.info.buyer=Please send payment only to the email address provided by the BTC Seller by sending a payment link.\n\nCelPay is limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Buyers can send any matching currency stablecoin to the BTC Seller. +payment.celpay.info.seller=BTC Sellers should expect to receive payment via a secure payment link. Please make sure the email payment link contains the email address provided by the BTC Buyer.\n\nCelPay users are limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Sellers should expect to receive any matching currency stablecoin from the BTC Buyer. It is possible for the BTC Buyer to send any matching currency stablecoin. +payment.celpay.supportedCurrenciesForReceiver=Supported currencies (please note: all the currencies below are supported stable coins within the Celcius app. Trades are for stable coins, not fiat.) + +payment.nequi.info.account=Please make sure to include your phone number that is associated with your Nequi account.\n\nWhen users set up a Nequi account payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. +payment.nequi.info.buyer=Please send payment only to the phone number provided in the BTC Seller's Bisq account.\n\nWhen users set up a Nequi account, payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. +payment.nequi.info.seller=Please check that the payment received matches the phone number provided in the BTC Buyer's Bisq account.\n\nWhen users set up a Nequi account, payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. + +payment.bizum.info.account=To use Bizum you need a bank account (IBAN) in Spain and to be registered for the service.\n\nBizum can be used for trades between €0.50 and €1,000.\n\nThe maximum amount of transactions you can send/receive using Bizum is €2,000 Euros per day.\n\nBizum users can have a maximum of 150 operations per month.\n\nEach bank however may establish its own limits, within the above limits, for its clients.\n\nTraders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.bizum.info.buyer=Please send payment only to the BTC Seller's mobile phone number as provided in Bisq.\n\nThe maximum trade size is €1,000 per payment. The maximum amount of transactions you can send using Bizum is €2,000 Euros per day.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.bizum.info.seller=Please make sure your payment is received from the BTC Buyer's mobile phone number as provided in Bisq.\n\nThe maximum trade size is €1,000 per payment. The maximum amount of transactions you can receive using Bizum is €2,000 Euros per day.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. + +payment.pix.info.account=Please make sure to include your chosen Pix Key. There are four types of keys: CPF (Natural Persons Register) or CNPJ (National Registry of Legal Entities), e-mail address, telephone number or a random key generated by the system called a universally unique identifier (UUID). A different key must be used for each Pix account you have. Individuals can create up to five keys for each account they own.\n\nWhen trading on Bisq, BTC Buyers should use their Pix Keys as the payment description so that it is easy for the BTC Sellers to identify the payment as coming from themselves. +payment.pix.info.buyer=Please send payment only the Pix Key provided in the BTC Seller's Bisq account.\n\nPlease use your Pix Key as the payment reference so that it is easy for the BTC Seller to identify the payment as coming from yourself. +payment.pix.info.seller=Please check that the payment received description matches the Pix Key provided in the BTC Buyer's Bisq account. +payment.pix.key=Pix Key (CPF, CNPJ, Email, Phone number or UUID) + +payment.monese.info.account=Monese is a bank app for users of GBP, EUR and RON*. Monese allows users to send money to other Monese accounts instantly and for free in any supported currency.\n\n*To open a RON account in Monese, you need to either live in Romania or have Romanian citizenship.\n\nWhen setting up your Monese account in Bisq please make sure to include your name and phone number that matches your Monese account. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account. +payment.monese.info.buyer=Please send payment only to the phone number provided by the BTC Seller in their Bisq account. Please leave the payment description blank. +payment.monese.info.seller=BTC Sellers should expect to receive payment from the phone number / name shown in the BTC Buyer's Bisq account. + +payment.satispay.info.account=To use Satispay you need a bank account (IBAN) in Italy and to be registered for the service.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.satispay.info.buyer=Please send payment only to the BTC Seller's mobile phone number as provided in Bisq.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.satispay.info.seller=Please make sure your payment is received from the BTC Buyer's mobile phone number / name as provided in Bisq.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. + +payment.verse.info.account=Verse is a multiple currency payment method that can send and receive payment in EUR, SEK, HUF, DKK, PLN.\n\nWhen setting up your Verse account in Bisq please make sure to include the username that matches your username in your Verse account. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. +payment.verse.info.buyer=Please send payment only to the username provided by the BTC Seller in their Bisq account. Please leave the payment description blank.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. +payment.verse.info.seller=BTC Sellers should expect to receive payment from the username shown in the BTC Buyer's Bisq account.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. + +payment.strike.info.account=Please make sure to include your Strike username.\n\nIn Bisq, Strike is used for fiat to fiat payments only.\n\nPlease make sure you are aware of the Strike limits:\n\nUsers who have registered with only their email, name, and phone number have the following limits:\n\n● $100 maximum per deposit\n● $1,000 maximum total deposits per week\n● $100 maximum per payment\n\nUsers can increase their limits by providing Strike with more information. These users have the following limits:\n\n● $1,000 maximum per deposit\n● $1,000 maximum total deposits per week\n● $1,000 maximum per payment\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.strike.info.buyer=Please send payment only to the BTC Seller's Strike username as provided in Bisq.\n\nThe maximum trade size is $1,000 per payment.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.strike.info.seller=Please make sure your payment is received from the BTC Buyer's Strike username as provided in Bisq.\n\nThe maximum trade size is $1,000 per payment.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. payment.usPostalMoneyOrder.info=Trading using US Postal Money Orders (USPMO) on Bisq requires that you understand the following:\n\n- BTC buyers must write the BTC Seller’s name in both the Payer and the Payee’s fields & take a high-resolution photo of the USPMO and envelope with proof of tracking before sending.\n- BTC buyers must send the USPMO to the BTC seller with Delivery Confirmation.\n\nIn the event mediation is necessary, or if there is a trade dispute, you will be required to send the photos to the Bisq mediator or refund agent, together with the USPMO Serial Number, Post Office Number, and dollar amount, so they can verify the details on the US Post Office website.\n\nFailure to provide the required information to the Mediator or Arbitrator will result in losing the dispute case.\n\nIn all dispute cases, the USPMO sender bears 100% of the burden of responsibility in providing evidence/proof to the Mediator or Arbitrator.\n\nIf you do not understand these requirements, do not trade using USPMO on Bisq. @@ -2792,6 +2847,8 @@ payment.f2f.info='Face to Face' trades have different rules and come with differ payment.f2f.info.openURL=Apri sito web payment.f2f.offerbook.tooltip.countryAndCity=Paese e città: {0} / {1} payment.f2f.offerbook.tooltip.extra=Ulteriori informazioni: {0} +payment.ifsc=IFS Code +payment.ifsc.validation=IFSC format: XXXX0999999 payment.japan.bank=Banca payment.japan.branch=Filiale @@ -2800,7 +2857,7 @@ payment.japan.recipient=Nome payment.australia.payid=PayID payment.payid=PayID linked to financial institution. Like email address or mobile phone. payment.payid.info=A PayID like a phone number, email address or an Australian Business Number (ABN), that you can securely link to your bank, credit union or building society account. You need to have already created a PayID with your Australian financial institution. Both sending and receiving financial institutions must support PayID. For more information please check [HYPERLINK:https://payid.com.au/faqs/] -payment.amazonGiftCard.info=To pay with Amazon eGift Card, you will need to send an Amazon eGift Card to the BTC seller via your Amazon account. \n\nBisq will show the BTC seller''s email address or phone number where the gift card should be sent, and you must include the trade ID in the gift card''s message field. Please see the wiki [HYPERLINK:https://bisq.wiki/Amazon_eGift_card] for further details and best practices. \n\nThree important notes:\n- try to send gift cards with amounts of 100 USD or smaller, as Amazon is known to flag larger gift cards as fraudulent\n- try to use creative, believable text for the gift card''s message (e.g., "Happy birthday Susan!") along with the trade ID (and use trader chat to tell your trading peer the reference text you picked so they can verify your payment)\n- Amazon eGift Cards can only be redeemed on the Amazon website they were purchased on (e.g., a gift card purchased on amazon.it can only be redeemed on amazon.it) +payment.amazonGiftCard.info=To pay with Amazon eGift Card, you will need to send an Amazon eGift Card to the BTC seller via your Amazon account. \n\nPlease see the wiki [HYPERLINK:https://bisq.wiki/Amazon_eGift_card] for further details and best practices. \n\nThree important notes:\n- try to send gift cards with amounts of 100 USD or smaller, as Amazon is known to flag larger gift cards as fraudulent\n- try to use creative, believable text for the gift card''s message (e.g., "Happy birthday Susan!") and use trader chat to tell your trading peer the reference text you picked so they can verify your payment\n- Amazon eGift Cards can only be redeemed on the Amazon website they were purchased on (e.g., a gift card purchased on amazon.it can only be redeemed on amazon.it) # We use constants from the code so we do not use our normal naming convention # dynamic values are not recognized by IntelliJ @@ -2885,12 +2942,38 @@ PAYSERA=Paysera # suppress inspection "UnusedProperty" PAXUM=Paxum # suppress inspection "UnusedProperty" +NEFT=India/NEFT +# suppress inspection "UnusedProperty" +RTGS=India/RTGS +# suppress inspection "UnusedProperty" +IMPS=India/IMPS +# suppress inspection "UnusedProperty" +UPI=India/UPI +# suppress inspection "UnusedProperty" +PAYTM=India/PayTM +# suppress inspection "UnusedProperty" +NEQUI=Nequi +# suppress inspection "UnusedProperty" +BIZUM=Bizum +# suppress inspection "UnusedProperty" +PIX=Pix +# suppress inspection "UnusedProperty" AMAZON_GIFT_CARD=Amazon eGift Card # suppress inspection "UnusedProperty" BLOCK_CHAINS_INSTANT=Altcoin Instant # suppress inspection "UnusedProperty" CAPITUAL=Capitual # suppress inspection "UnusedProperty" +CELPAY=CelPay +# suppress inspection "UnusedProperty" +MONESE=Monese +# suppress inspection "UnusedProperty" +SATISPAY=Satispay +# suppress inspection "UnusedProperty" +VERSE=Verse +# suppress inspection "UnusedProperty" +STRIKE=Strike +# suppress inspection "UnusedProperty" SWIFT=SWIFT International Wire Transfer # Deprecated: Cannot be deleted as it would break old trade history entries @@ -2945,12 +3028,38 @@ PAYSERA_SHORT=Paysera # suppress inspection "UnusedProperty" PAXUM_SHORT=Paxum # suppress inspection "UnusedProperty" +NEFT_SHORT=NEFT +# suppress inspection "UnusedProperty" +RTGS_SHORT=RTGS +# suppress inspection "UnusedProperty" +IMPS_SHORT=IMPS +# suppress inspection "UnusedProperty" +UPI_SHORT=UPI +# suppress inspection "UnusedProperty" +PAYTM_SHORT=PayTM +# suppress inspection "UnusedProperty" +NEQUI_SHORT=Nequi +# suppress inspection "UnusedProperty" +BIZUM_SHORT=Bizum +# suppress inspection "UnusedProperty" +PIX_SHORT=Pix +# suppress inspection "UnusedProperty" AMAZON_GIFT_CARD_SHORT=Amazon eGift Card # suppress inspection "UnusedProperty" BLOCK_CHAINS_INSTANT_SHORT=Altcoin Instant # suppress inspection "UnusedProperty" CAPITUAL_SHORT=Capitual # suppress inspection "UnusedProperty" +CELPAY_SHORT=CelPay +# suppress inspection "UnusedProperty" +MONESE_SHORT=Monese +# suppress inspection "UnusedProperty" +SATISPAY_SHORT=Satispay +# suppress inspection "UnusedProperty" +VERSE_SHORT=Verse +# suppress inspection "UnusedProperty" +STRIKE_SHORT=Strike +# suppress inspection "UnusedProperty" SWIFT_SHORT=SWIFT # Deprecated: Cannot be deleted as it would break old trade history entries diff --git a/core/src/main/resources/i18n/displayStrings_ja.properties b/core/src/main/resources/i18n/displayStrings_ja.properties index 9d4a6767fdd..f73a2de0c87 100644 --- a/core/src/main/resources/i18n/displayStrings_ja.properties +++ b/core/src/main/resources/i18n/displayStrings_ja.properties @@ -284,6 +284,7 @@ mainView.walletServiceErrorMsg.rejectedTxException=トランザクションは mainView.networkWarning.allConnectionsLost=全ての{0}のネットワークピアへの接続が切断されました。\nインターネット接続が切断されたか、コンピュータがスタンバイモードになった可能性があります。 mainView.networkWarning.localhostBitcoinLost=ローカルホストビットコインノードへの接続が切断されました。\nBisqアプリケーションを再起動して他のビットコインノードに接続するか、ローカルホストのビットコインノードを再起動してください。 +mainView.networkWarning.clockWatcher=Your computer was asleep for {0} seconds. Standby mode has been known to cause trades to fail. In order to operate correctly Bisq requires that standby mode be disabled in your computer's settings. mainView.version.update=(更新が利用可能) @@ -821,7 +822,7 @@ portfolio.pending.mediationResult.popup.alreadyAccepted=すでに受け入れて portfolio.pending.failedTrade.taker.missingTakerFeeTx=欠測テイカー手数料のトランザクション。\n\nこのtxがなければ、トレードを完了できません。資金はロックされず、トレード手数料は支払いませんでした。「失敗トレード」へ送ることができます。 portfolio.pending.failedTrade.maker.missingTakerFeeTx=ピアのテイカー手数料のトランザクションは欠測します。\n\nこのtxがなければ、トレードを完了できません。資金はロックされませんでした。あなたのオファーがまだ他の取引者には有効ですので、メイカー手数料は失っていません。このトレードを「失敗トレード」へ送ることができます。 portfolio.pending.failedTrade.missingDepositTx=入金トランザクション(2-of-2マルチシグトランザクション)は欠測します。\n\nこのtxがなければ、トレードを完了できません。資金はロックされませんでしたが、トレード手数料は支払いました。トレード手数料の返済要求はここから提出できます: [HYPERLINK:https://github.com/bisq-network/support/issues]\n\nこのトレードを「失敗トレード」へ送れます。 -portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=遅延支払いトランザクションは欠測しますが、資金は入金トランザクションにロックされました。\n\nこの法定通貨・アルトコイン支払いをBTC売り手に送信しないで下さい。遅延支払いtxがなければ、係争仲裁は開始されることができません。代りに、「Cmd/Ctrl+o」で調停チケットをオープンして下さい。調停者はおそらく両方のピアへセキュリティデポジットの全額を払い戻しを提案します(売り手はトレード金額も払い戻しを受ける)。このような方法でセキュリティーのリスクがなし、トレード手数料のみが失われます。\n\n失われたトレード手数料の払い戻し要求はここから提出できます: [HYPERLINK:https://github.com/bisq-network/support/issues] +portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the fiat or altcoin payment to the BTC seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=遅延支払いトランザクションは欠測しますが、資金は入金トランザクションにロックされました。\n\n買い手の遅延支払いトランザクションが同じく欠測される場合、相手は支払いを送信せず調停チケットをオープンするように指示されます。同様に「Cmd/Ctrl+o」で調停チケットをオープンするのは賢明でしょう。\n\n買い手はまだ支払いを送信しなかった場合、調停者はおそらく両方のピアへセキュリティデポジットの全額を払い戻しを提案します(売り手はトレード金額も払い戻しを受ける)。さもなければ、トレード金額は買い手に支払われるでしょう。\n\n失われたトレード手数料の払い戻し要求はここから提出できます: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.errorMsgSet=トレードプロトコルの実行にはエラーが生じました。\n\nエラー: {0}\n\nクリティカル・エラーではない可能性はあり、トレードは普通に完了できるかもしれない。迷う場合は調停チケットをオープンして、Bisq調停者からアドバイスを受けることができます。\n\nクリティカル・エラーでトレードが完了できなかった場合はトレード手数料は失われた可能性があります。失われたトレード手数料の払い戻し要求はここから提出できます: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.missingContract=トレード契約書は設定されません。\n\nトレードは完了できません。トレード手数料は失われた可能性もあります。その場合は失われたトレード手数料の払い戻し要求はここから提出できます: [HYPERLINK:https://github.com/bisq-network/support/issues] @@ -996,7 +997,9 @@ support.buyerTaker=BTC 買い手/テイカー support.sellerTaker=BTC 売り手/テイカー support.backgroundInfo=Bisqは会社ではないので、係争の扱いが異なります。\n\n取引者はアプリ内の「オープントレード」画面からセキュアチャットでお互いに紛争を解決しようと努めれる。その方法は十分でない場合、調停者は間に入って助けることもできます。調停者は状況を判断して、トレード資金の支払い配分を提案します。両当事者は提案に同意する場合、支払いトランザクションは完了され、トレードは閉じられます。片当事者もしくは両当事者は調停者の支払い提案に同意しない場合、仲裁を求めることができます。調停人は再びに問題を検討し、正当な場合は個人的に取引者に払い戻す、そしてBisqのDAOからその分の払い戻し要求を提出します。 -support.initialInfo=Please enter a description of your problem in the text field below. Add as much information as possible to speed up dispute resolution time.\n\nHere is a check list for information you should provide:\n\t● If you are the BTC buyer: Did you make the Fiat or Altcoin transfer? If so, did you click the 'payment started' button in the application?\n\t● If you are the BTC seller: Did you receive the Fiat or Altcoin payment? If so, did you click the 'payment received' button in the application?\n\t● Which version of Bisq are you using?\n\t● Which operating system are you using?\n\t● If you encountered an issue with failed transactions please consider switching to a new data directory.\n\t Sometimes the data directory gets corrupted and leads to strange bugs. \n\t See: https://bisq.wiki/Switching_to_a_new_data_directory\n\nPlease make yourself familiar with the basic rules for the dispute process:\n\t● You need to respond to the {0}''s requests within 2 days.\n\t● Mediators respond in between 2 days. Arbitrators respond in between 5 business days.\n\t● The maximum period for a dispute is 14 days.\n\t● You need to cooperate with the {1} and provide the information they request to make your case.\n\t● You accepted the rules outlined in the dispute document in the user agreement when you first started the application.\n\nYou can read more about the dispute process at: {2} +support.initialInfo=Please enter a description of your problem in the text field below. Add as much information as possible to speed up dispute resolution time.\n\nHere is a check list for information you should provide:\n\t● If you are the BTC buyer: Did you make the Fiat or Altcoin transfer? If so, did you click the 'payment started' button in the application?\n\t● If you are the BTC seller: Did you receive the Fiat or Altcoin payment? If so, did you click the 'payment received' button in the application?\n\t● Which version of Bisq are you using?\n\t● Which operating system are you using?\n\t● If you encountered an issue with failed transactions please consider switching to a new data directory.\n\t Sometimes the data directory gets corrupted and leads to strange bugs. \n\t See: https://bisq.wiki/Switching_to_a_new_data_directory\n\nPlease make yourself familiar with the basic rules for the dispute process:\n\t● You need to respond to the {0}''s requests within 2 days.\n\t● {1}\n\t● The maximum period for a dispute is 14 days.\n\t● You need to cooperate with the {2} and provide the information they request to make your case.\n\t● You accepted the rules outlined in the dispute document in the user agreement when you first started the application.\n\nYou can read more about the dispute process at: {3} +support.initialMediatorMsg=Mediators will generally reply to you within 24 hours.\n\t If you have not had a reply after 48 hours please feel free to reach out to your mediator on Keybase.\n\t Mediators usernames on Keybase are the same as their usernames within the Bisq app.\n\t Your mediator is: {0} +support.initialArbitratorMsg=Arbitrators will generally reply to you within 5 days.\n\t If you have not had a reply after 7 days please feel free to reach out to your arbitrator on Keybase.\n\t Arbitrators usernames on Keybase are the same as their usernames within the Bisq app.\n\t Your arbitrator is: {0} support.systemMsg=システムメッセージ: {0} support.youOpenedTicket=サポートのリクエスト開始しました。\n\n{0}\n\nBisqバージョン: {1} support.youOpenedDispute=係争のリクエスト開始しました。\n\n{0}\n\nBisqバージョン: {1} @@ -1064,6 +1067,8 @@ setting.preferences.dao.resyncFromResources.popup=アプリケーションの再 setting.preferences.dao.resyncFromGenesis.popup=ジェネシストランザクションからの再同期はかなりの時間をかけて、かなりのCPUリソースを消費します。本当によろしいですか?大抵の場合は最新リソースファイルからの再同期は十分、より早い選択です。\n\n進めたら、アプリケーションの再起動後、Bisqネットワークガバナンスデータはシードノードから再ロードされ、BSQのコンセンサス状態はジェネシストランザクションから再構築されるでしょう。 setting.preferences.dao.resyncFromGenesis.resync=ジェネシスから再同期して終了 setting.preferences.dao.isDaoFullNode=BisqをDAOのフルノードで実行 +setting.preferences.dao.activated=DAO activated +setting.preferences.dao.activated.popup=The change will be applied after a restart setting.preferences.dao.rpcUser=RPCユーザ名 setting.preferences.dao.rpcPw=RPCパスワード setting.preferences.dao.blockNotifyPort=通知ポートをブロック @@ -1922,15 +1927,10 @@ dao.news.bisqDAO.title=THE BISQ DAO dao.news.bisqDAO.description=Bisqの交換が非中央集権化され検閲に抵抗するように、そのガバナンスモデルも、つまりBisq DAOとBSQトークンはそれを可能にするツールです。 dao.news.bisqDAO.readMoreLink=Bisq DAOについてもっと詳しく知る -dao.news.pastContribution.title=過去に貢献しましたか?BSQをリクエストしましょう -dao.news.pastContribution.description=Bisqに貢献している場合は、以下のBSQアドレスを使用して、BSQジェネシス配布に参加するようにリクエストしてください。 -dao.news.pastContribution.yourAddress=あなたのBSQウォレットアドレス -dao.news.pastContribution.requestNow=今すぐリクエスト - -dao.news.daoInfo.title=私達のテストネットでBISQ DAOを起動 -dao.news.daoInfo.description=メインネットBisq DAOはまだ起動されていませんが、私達のテストネットで実行することでBisq DAOについて学ぶことができます。 -dao.news.daoInfo.firstSection.title=1. DAOテストネットモードに切り替え -dao.news.daoInfo.firstSection.content=設定画面からDAOテストネットへ切り替え +dao.news.daoInfo.title=ENABLE THE BISQ DAO +dao.news.daoInfo.description=To participate in the Bisq DAO and to use BSQ for discounted trading fees, you need to enable the DAO. When the DAO is enabled, Bisq downloads all missing blocks and verifies BSQ transactions. This verification process requires time, during which you may see Bisq use a lot of memory and processing power. This is normal. +dao.news.daoInfo.firstSection.title=1. Enable DAO +dao.news.daoInfo.firstSection.content=Enable the Bisq DAO and restart. dao.news.DAOOnTestnet.secondSection.title=2. BSQを取得する dao.news.DAOOnTestnet.secondSection.content=SlackでBSQをリクエストするか、BisqでBSQを購入してください。 dao.news.DAOOnTestnet.thirdSection.title=3. 投票サイクルに参加する @@ -2679,6 +2679,7 @@ payment.secret=秘密の質問 payment.answer=答え payment.wallet=ウォレットID payment.capitual.cap=CAP Code +payment.upi.virtualPaymentAddress=Virtual Payment Address # suppress inspection "UnusedProperty" payment.swift.headline=International SWIFT Wire Transfer @@ -2767,11 +2768,65 @@ payment.amazonGiftCard.upgrade=Amazon gift cards payment method requires the cou payment.account.amazonGiftCard.addCountryInfo={0}\nYour existing Amazon Gift Card account ({1}) does not have a Country specified.\nPlease enter your Amazon Gift Card Country to update your account data.\nThis will not affect your account age status. payment.amazonGiftCard.upgrade.headLine=Update Amazon Gift Card account -payment.swift.info=Carefully review the core guidelines for using SWIFT on Bisq:\n\n- fill all fields completely and accurately \n- buyer must send payment in currency specified by the offer maker \n- buyer must use the shared fee model (SHA) \n- buyer and seller may incur fees, so they should check their bank's fee schedules beforehand \n\nSWIFT is more sophisticated than other payment methods, so please take a moment to review full guidance on the wiki [HYPERLINK:https://bisq.wiki/SWIFT]. +payment.swift.info.account=Carefully review the core guidelines for using SWIFT on Bisq:\n\n- fill all fields completely and accurately \n- buyer must send payment in currency specified by the offer maker \n- buyer must use the shared fee model (SHA) \n- buyer and seller may incur fees, so they should check their bank's fee schedules beforehand \n\nSWIFT is more sophisticated than other payment methods, so please take a moment to review full guidance on the wiki [HYPERLINK:https://bisq.wiki/SWIFT]. payment.swift.info.buyer=To buy bitcoin with SWIFT, you must:\n\n- send payment in the currency specified by the offer maker \n- use the shared fee model (SHA) to send payment\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. -payment.swift.info.seller=SWIFT senders are required to use the shared payment model (SHA) to send payments.\n\nIf you receive a SWIFT payment that does not use SHA, open a mediation ticket.\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. +payment.swift.info.seller=SWIFT senders are required to use the shared fee model (SHA) to send payments.\n\nIf you receive a SWIFT payment that does not use SHA, open a mediation ticket.\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. + +payment.imps.info.account=Please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a maximum of Rs. 200,000 that can be sent per transaction. If you are trading over this amount multiple transactions will be needed. However be aware their is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nSome banks have different limits for their customers. +payment.imps.info.buyer=Please send payment only to the account details provided in Bisq.\n\nThe maximum trade size is Rs. 200,000 per transaction.\n\nIf your trade is over Rs. 200,000 you will have to make multiple transfers. However be aware their is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nPlease note some banks have different limits for their customers. +payment.imps.info.seller=If you intend to receive over Rs. 200,000 per trade you should expect the buyer to have to make multiple transfers. However be aware there is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nPlease note some banks have different limits for their customers. + +payment.neft.info.account=Please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a maximum of Rs. 50,000 that can be sent per transaction. If you are trading over this amount multiple transactions will be needed.\n\nPlease note some banks have different limits for their customers. +payment.neft.info.buyer=Please send payment only to the account details provided in Bisq.\n\nThe maximum trade size is Rs. 50,000 per transaction.\n\nIf your trade is over Rs. 50,000 you will have to make multiple transfers.\n\nPlease note some banks have different limits for their customers. +payment.neft.info.seller=If you intend to receive over Rs. 50,000 per trade you should expect the buyer to have to make multiple transfers.\n\nPlease note some banks have different limits for their customers. + +payment.paytm.info.account=Please make sure to include your email or phone number that matches your email or phone number in your PayTM account. \n\nWhen users set up a PayTM account with No KYC users are limited to: \n\n● Maximum of Rs. 5,000 can be sent per transaction.\n● Maximum of Rs. 10,000 can be held in someone's PayTM wallet.\n\nIf you intend to trade amount of over 5,000 per trade you will need to complete KYC with PayTM. With KYC users are limited to:\n\n● Maximum of Rs. 100,000 can be sent per transaction.\n● Maximum of Rs. 100,000 can be held in someone's PayTM wallet.\n\nUsers should also be aware of account limits. Trades above PayTM account limits will likely have to take place over more than one day, or, be cancelled. +payment.paytm.info.buyer=Please send payment only to the email address or phone number provided.\n\nIf you intend to trade amount of over Rs. 5,000 per trade you will need to complete KYC with PayTM.\n\nWith No KYC Rs. 5,000 can be sent per transaction.\n\nWith KYC users Rs. 100,000 can be sent per transaction. +payment.paytm.info.seller=If you intend to trade amount of over Rs. 5,000 per trade you will need to complete KYC with PayTM. With KYC users are limited to:\n\n● Maximum of Rs. 100,000 can be sent per transaction.\n● Maximum of Rs. 100,000 can be held in your PayTM wallet .\n\nUsers should also be aware of account limits. As a maximum of Rs. 100,000 can be held in your PayTM wallet please make sure you transfer out your rupees regularly. + +payment.rtgs.info.account=RTGS is for payments of large trades of Rs. 200,000 or over.\n\nWhen setting up your RTGS payment account please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). +payment.rtgs.info.buyer=Please send payment only to the account details provided in Bisq.\n\nPlease be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). +payment.rtgs.info.seller=Please be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). + +payment.upi.info.account=Please make sure to include your Virtual Payment Address (VPA) also called your UPI ID. The format for this is like an email ID: with the sign “@” in the middle. For example, your UPI ID could be “receiver’s_name@bank_name” or “phone_number@bank_name.” \n\nFor UPI there is a maximum limit of Rs. 100,000 that can be sent per transaction. \n\nIf you intend to trade amount of over Rs. 100,000 per trade it is likely trades will have to take place over multiple transfers. \n\nPlease note some banks have different limits for their customers. +payment.upi.info.buyer=Please send payment only to the VPA / UPI ID provided in Bisq. \n\nThe maximum trade size is Rs. 100,000 per transaction. \n\nIf your trade is over Rs. 100,000 you will have to make multiple transfers. \n\nPlease note some banks have different limits for their customers. +payment.upi.info.seller=If you intend to receive over Rs. 100,000 per trade you should expect the buyer to have to make multiple transfers. \n\nPlease note some banks have different limits for their customers. + +payment.celpay.info.account=Please make sure to include the email your Celsius account is registered to. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account.\n\nCelPay users are limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Buyers can send any matching currency stablecoin to the BTC Seller. +payment.celpay.info.buyer=Please send payment only to the email address provided by the BTC Seller by sending a payment link.\n\nCelPay is limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Buyers can send any matching currency stablecoin to the BTC Seller. +payment.celpay.info.seller=BTC Sellers should expect to receive payment via a secure payment link. Please make sure the email payment link contains the email address provided by the BTC Buyer.\n\nCelPay users are limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Sellers should expect to receive any matching currency stablecoin from the BTC Buyer. It is possible for the BTC Buyer to send any matching currency stablecoin. +payment.celpay.supportedCurrenciesForReceiver=Supported currencies (please note: all the currencies below are supported stable coins within the Celcius app. Trades are for stable coins, not fiat.) + +payment.nequi.info.account=Please make sure to include your phone number that is associated with your Nequi account.\n\nWhen users set up a Nequi account payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. +payment.nequi.info.buyer=Please send payment only to the phone number provided in the BTC Seller's Bisq account.\n\nWhen users set up a Nequi account, payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. +payment.nequi.info.seller=Please check that the payment received matches the phone number provided in the BTC Buyer's Bisq account.\n\nWhen users set up a Nequi account, payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. + +payment.bizum.info.account=To use Bizum you need a bank account (IBAN) in Spain and to be registered for the service.\n\nBizum can be used for trades between €0.50 and €1,000.\n\nThe maximum amount of transactions you can send/receive using Bizum is €2,000 Euros per day.\n\nBizum users can have a maximum of 150 operations per month.\n\nEach bank however may establish its own limits, within the above limits, for its clients.\n\nTraders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.bizum.info.buyer=Please send payment only to the BTC Seller's mobile phone number as provided in Bisq.\n\nThe maximum trade size is €1,000 per payment. The maximum amount of transactions you can send using Bizum is €2,000 Euros per day.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.bizum.info.seller=Please make sure your payment is received from the BTC Buyer's mobile phone number as provided in Bisq.\n\nThe maximum trade size is €1,000 per payment. The maximum amount of transactions you can receive using Bizum is €2,000 Euros per day.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. + +payment.pix.info.account=Please make sure to include your chosen Pix Key. There are four types of keys: CPF (Natural Persons Register) or CNPJ (National Registry of Legal Entities), e-mail address, telephone number or a random key generated by the system called a universally unique identifier (UUID). A different key must be used for each Pix account you have. Individuals can create up to five keys for each account they own.\n\nWhen trading on Bisq, BTC Buyers should use their Pix Keys as the payment description so that it is easy for the BTC Sellers to identify the payment as coming from themselves. +payment.pix.info.buyer=Please send payment only the Pix Key provided in the BTC Seller's Bisq account.\n\nPlease use your Pix Key as the payment reference so that it is easy for the BTC Seller to identify the payment as coming from yourself. +payment.pix.info.seller=Please check that the payment received description matches the Pix Key provided in the BTC Buyer's Bisq account. +payment.pix.key=Pix Key (CPF, CNPJ, Email, Phone number or UUID) + +payment.monese.info.account=Monese is a bank app for users of GBP, EUR and RON*. Monese allows users to send money to other Monese accounts instantly and for free in any supported currency.\n\n*To open a RON account in Monese, you need to either live in Romania or have Romanian citizenship.\n\nWhen setting up your Monese account in Bisq please make sure to include your name and phone number that matches your Monese account. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account. +payment.monese.info.buyer=Please send payment only to the phone number provided by the BTC Seller in their Bisq account. Please leave the payment description blank. +payment.monese.info.seller=BTC Sellers should expect to receive payment from the phone number / name shown in the BTC Buyer's Bisq account. + +payment.satispay.info.account=To use Satispay you need a bank account (IBAN) in Italy and to be registered for the service.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.satispay.info.buyer=Please send payment only to the BTC Seller's mobile phone number as provided in Bisq.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.satispay.info.seller=Please make sure your payment is received from the BTC Buyer's mobile phone number / name as provided in Bisq.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. + +payment.verse.info.account=Verse is a multiple currency payment method that can send and receive payment in EUR, SEK, HUF, DKK, PLN.\n\nWhen setting up your Verse account in Bisq please make sure to include the username that matches your username in your Verse account. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. +payment.verse.info.buyer=Please send payment only to the username provided by the BTC Seller in their Bisq account. Please leave the payment description blank.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. +payment.verse.info.seller=BTC Sellers should expect to receive payment from the username shown in the BTC Buyer's Bisq account.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. + +payment.strike.info.account=Please make sure to include your Strike username.\n\nIn Bisq, Strike is used for fiat to fiat payments only.\n\nPlease make sure you are aware of the Strike limits:\n\nUsers who have registered with only their email, name, and phone number have the following limits:\n\n● $100 maximum per deposit\n● $1,000 maximum total deposits per week\n● $100 maximum per payment\n\nUsers can increase their limits by providing Strike with more information. These users have the following limits:\n\n● $1,000 maximum per deposit\n● $1,000 maximum total deposits per week\n● $1,000 maximum per payment\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.strike.info.buyer=Please send payment only to the BTC Seller's Strike username as provided in Bisq.\n\nThe maximum trade size is $1,000 per payment.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.strike.info.seller=Please make sure your payment is received from the BTC Buyer's Strike username as provided in Bisq.\n\nThe maximum trade size is $1,000 per payment.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. payment.usPostalMoneyOrder.info=Bisqでアメリカ合衆国郵便為替(USPMO)をトレードするには、以下を理解する必要があります:\n\n-送る前に、BTC買い手は必ずBTC売り手の名前を支払人そして支払先フィールド両方に書いて、追跡証明も含めるUSPMOそして封筒の高解像度写真を取る必要があります。\n-BTC買い手は必ず配達確認を利用してBTC売り手にUSPMOを送る必要があります。\n\n調停が必要になる場合、あるいはトレード係争が開始される場合、調停者や調停人がアメリカ合衆国郵便のサイトで詳細を確認できるように、取った写真、USPMOシリアル番号、郵便局番号、そしてドル金額を送る必要があります。\n\n調停者や調停人に必要な情報を提供しなければ、係争で不利な裁定を下されます。\n\n全ての係争には、調停者や調停人に証明を提供するのは100%USPMO送付者の責任です。\n\n以上の条件を理解しない場合、BisqでUSPMOのトレードをしないで下さい。 @@ -2792,6 +2847,8 @@ payment.f2f.info='Face to Face' trades have different rules and come with differ payment.f2f.info.openURL=Webページを開く payment.f2f.offerbook.tooltip.countryAndCity=国と都市: {0} / {1} payment.f2f.offerbook.tooltip.extra=追加情報: {0} +payment.ifsc=IFS Code +payment.ifsc.validation=IFSC format: XXXX0999999 payment.japan.bank=銀行 payment.japan.branch=支店 @@ -2800,7 +2857,7 @@ payment.japan.recipient=名義 payment.australia.payid=PayID payment.payid=金融機関と繋がっているPayID。例えばEメールアドレスそれとも携帯電話番号。 payment.payid.info=銀行、信用金庫、あるいは住宅金融組合アカウントと安全に繋がれるPayIDとして使われる電話番号、Eメールアドレス、それともオーストラリア企業番号(ABN)。すでにオーストラリアの金融機関とPayIDを作った必要があります。送金と受取の金融機関は両方PayIDをサポートする必要があります。詳しくは以下を訪れて下さい [HYPERLINK:https://payid.com.au/faqs/] -payment.amazonGiftCard.info=アマゾンeGiftカードで支払うには、アマゾンアカウントを使ってeGiftカードをBTC売り手に送る必要があります。\n\nBisqはeGiftカードの送り先になるBTC売り手のメールアドレスそれとも電話番号を表示します。そしてeGiftカードのメッセージフィールドに、必ずトレードIDを入力して下さい。最良の慣行について詳しくはWikiを参照して下さい:[HYPERLINK:https://bisq.wiki/Amazon_eGift_card]\n\n3つの注意点:\n- 可能であれば、100米ドル価格以下のeGiftカードを送って下さい。それ以上の価格はアマゾンに不正な取引というフラグが立てられることがあります。\n- eGiftカードのメッセージフィールドに、トレードIDと一緒に信ぴょう性のあるメッセージを入力して下さい。(例えば隆さん、「お誕生日おめでとう!」)。(そして確認のため、取引者チャットでトレードピアにメッセージの内容を伝えて下さい)。\n- アマゾンeGiftカードは買われたサイトのみに交換できます(例えば、amazon.jpから買われたカードはamazon.jpのみに交換できます)。 +payment.amazonGiftCard.info=To pay with Amazon eGift Card, you will need to send an Amazon eGift Card to the BTC seller via your Amazon account. \n\nPlease see the wiki [HYPERLINK:https://bisq.wiki/Amazon_eGift_card] for further details and best practices. \n\nThree important notes:\n- try to send gift cards with amounts of 100 USD or smaller, as Amazon is known to flag larger gift cards as fraudulent\n- try to use creative, believable text for the gift card''s message (e.g., "Happy birthday Susan!") and use trader chat to tell your trading peer the reference text you picked so they can verify your payment\n- Amazon eGift Cards can only be redeemed on the Amazon website they were purchased on (e.g., a gift card purchased on amazon.it can only be redeemed on amazon.it) # We use constants from the code so we do not use our normal naming convention # dynamic values are not recognized by IntelliJ @@ -2885,12 +2942,38 @@ PAYSERA=Paysera # suppress inspection "UnusedProperty" PAXUM=Paxum # suppress inspection "UnusedProperty" +NEFT=India/NEFT +# suppress inspection "UnusedProperty" +RTGS=India/RTGS +# suppress inspection "UnusedProperty" +IMPS=India/IMPS +# suppress inspection "UnusedProperty" +UPI=India/UPI +# suppress inspection "UnusedProperty" +PAYTM=India/PayTM +# suppress inspection "UnusedProperty" +NEQUI=Nequi +# suppress inspection "UnusedProperty" +BIZUM=Bizum +# suppress inspection "UnusedProperty" +PIX=Pix +# suppress inspection "UnusedProperty" AMAZON_GIFT_CARD=アマゾンeGiftカード # suppress inspection "UnusedProperty" BLOCK_CHAINS_INSTANT=アルトコイン インスタント # suppress inspection "UnusedProperty" CAPITUAL=Capitual # suppress inspection "UnusedProperty" +CELPAY=CelPay +# suppress inspection "UnusedProperty" +MONESE=Monese +# suppress inspection "UnusedProperty" +SATISPAY=Satispay +# suppress inspection "UnusedProperty" +VERSE=Verse +# suppress inspection "UnusedProperty" +STRIKE=Strike +# suppress inspection "UnusedProperty" SWIFT=SWIFT International Wire Transfer # Deprecated: Cannot be deleted as it would break old trade history entries @@ -2945,12 +3028,38 @@ PAYSERA_SHORT=Paysera # suppress inspection "UnusedProperty" PAXUM_SHORT=Paxum # suppress inspection "UnusedProperty" +NEFT_SHORT=NEFT +# suppress inspection "UnusedProperty" +RTGS_SHORT=RTGS +# suppress inspection "UnusedProperty" +IMPS_SHORT=IMPS +# suppress inspection "UnusedProperty" +UPI_SHORT=UPI +# suppress inspection "UnusedProperty" +PAYTM_SHORT=PayTM +# suppress inspection "UnusedProperty" +NEQUI_SHORT=Nequi +# suppress inspection "UnusedProperty" +BIZUM_SHORT=Bizum +# suppress inspection "UnusedProperty" +PIX_SHORT=Pix +# suppress inspection "UnusedProperty" AMAZON_GIFT_CARD_SHORT=アマゾンeGiftカード # suppress inspection "UnusedProperty" BLOCK_CHAINS_INSTANT_SHORT=アルトコイン インスタント # suppress inspection "UnusedProperty" CAPITUAL_SHORT=Capitual # suppress inspection "UnusedProperty" +CELPAY_SHORT=CelPay +# suppress inspection "UnusedProperty" +MONESE_SHORT=Monese +# suppress inspection "UnusedProperty" +SATISPAY_SHORT=Satispay +# suppress inspection "UnusedProperty" +VERSE_SHORT=Verse +# suppress inspection "UnusedProperty" +STRIKE_SHORT=Strike +# suppress inspection "UnusedProperty" SWIFT_SHORT=SWIFT # Deprecated: Cannot be deleted as it would break old trade history entries diff --git a/core/src/main/resources/i18n/displayStrings_pt-br.properties b/core/src/main/resources/i18n/displayStrings_pt-br.properties index 754c497c397..4d9086d9e89 100644 --- a/core/src/main/resources/i18n/displayStrings_pt-br.properties +++ b/core/src/main/resources/i18n/displayStrings_pt-br.properties @@ -284,6 +284,7 @@ mainView.walletServiceErrorMsg.rejectedTxException=Uma transação foi rejeitada mainView.networkWarning.allConnectionsLost=Você perdeu sua conexão com todos os pontos da rede {0}.\nTalvez você tenha perdido a conexão com a internet ou seu computador estava em modo de espera. mainView.networkWarning.localhostBitcoinLost=Você perdeu a conexão ao nó Bitcoin do localhost.\nPor favor, reinicie o aplicativo Bisq para conectar-se a outros nós Bitcoin ou reinicie o nó Bitcoin do localhost. +mainView.networkWarning.clockWatcher=Your computer was asleep for {0} seconds. Standby mode has been known to cause trades to fail. In order to operate correctly Bisq requires that standby mode be disabled in your computer's settings. mainView.version.update=(Atualização disponível) @@ -422,7 +423,7 @@ offerbook.info.sellAboveMarketPrice=Você irá receber {0} a mais do que o atual offerbook.info.buyBelowMarketPrice=Você irá pagar {0} a menos do que o atual preço de mercado (atualizado a cada minuto). offerbook.info.buyAtFixedPrice=Você irá comprar nesse preço fixo. offerbook.info.sellAtFixedPrice=Você irá vender neste preço fixo. -offerbook.info.noArbitrationInUserLanguage=Em caso de disputa, a arbitragem para essa oferta será realizada em {0}. O idioma atualmente está definido como {1}. +offerbook.info.noArbitrationInUserLanguage=Em caso de disputa, a arbitragem para essa oferta será realizada em {0}. O idioma atualmente está definido como {1}. offerbook.info.roundedFiatVolume=O valor foi arredondado para aumentar a privacidade da sua negociação. #################################################################### @@ -821,7 +822,7 @@ portfolio.pending.mediationResult.popup.alreadyAccepted=Você já aceitou portfolio.pending.failedTrade.taker.missingTakerFeeTx=The taker fee transaction is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked and no trade fee has been paid. You can move this trade to failed trades. portfolio.pending.failedTrade.maker.missingTakerFeeTx=The peer's taker fee transaction is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked. Your offer is still available to other traders, so you have not lost the maker fee. You can move this trade to failed trades. portfolio.pending.failedTrade.missingDepositTx=The deposit transaction (the 2-of-2 multisig transaction) is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked but your trade fee has been paid. You can make a request to be reimbursed the trade fee here: [HYPERLINK:https://github.com/bisq-network/support/issues]\n\nFeel free to move this trade to failed trades. -portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the fiat or altcoin payment to the BTC seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] +portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the fiat or altcoin payment to the BTC seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing but funds have been locked in the deposit transaction.\n\nIf the buyer is also missing the delayed payout transaction, they will be instructed to NOT send the payment and open a mediation ticket instead. You should also open a mediation ticket with Cmd/Ctrl+o. \n\nIf the buyer has not sent payment yet, the mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). Otherwise the trade amount should go to the buyer. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.errorMsgSet=There was an error during trade protocol execution.\n\nError: {0}\n\nIt might be that this error is not critical, and the trade can be completed normally. If you are unsure, open a mediation ticket to get advice from Bisq mediators. \n\nIf the error was critical and the trade cannot be completed, you might have lost your trade fee. Request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.missingContract=The trade contract is not set.\n\nThe trade cannot be completed and you might have lost your trade fee. If so, you can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] @@ -996,7 +997,9 @@ support.buyerTaker=Comprador de BTC / Aceitador da oferta support.sellerTaker=Vendedor de BTC / Aceitador da oferta support.backgroundInfo=Bisq não é uma empresa, então ela lida com disputas de uma forma diferente.\n\nComerciantes podem se comunicar dentro do aplicativo usando um chat seguro na tela de negociações em aberto para tentar resolver conflitos entre eles mesmos. Se isto não for o suficiente, um mediador pode intervir para ajudar. O mediador irá avaliar a situação e sugerir um pagamento. Se ambos comerciantes aceitarem essa sugestão, a transação de pagamento é finalizada e a negociação é fechada. Se um or ambos os comerciantes não concordarem com o pagamento sugerido pelo mediador, eles podem solicitar arbitragem. O árbitro irá reavaliar a situação e, se justificado, pagará pessoalmente o comerciante e então solicitará reembolso deste pagamento à DAO Bisq. -support.initialInfo=Please enter a description of your problem in the text field below. Add as much information as possible to speed up dispute resolution time.\n\nHere is a check list for information you should provide:\n\t● If you are the BTC buyer: Did you make the Fiat or Altcoin transfer? If so, did you click the 'payment started' button in the application?\n\t● If you are the BTC seller: Did you receive the Fiat or Altcoin payment? If so, did you click the 'payment received' button in the application?\n\t● Which version of Bisq are you using?\n\t● Which operating system are you using?\n\t● If you encountered an issue with failed transactions please consider switching to a new data directory.\n\t Sometimes the data directory gets corrupted and leads to strange bugs. \n\t See: https://bisq.wiki/Switching_to_a_new_data_directory\n\nPlease make yourself familiar with the basic rules for the dispute process:\n\t● You need to respond to the {0}''s requests within 2 days.\n\t● Mediators respond in between 2 days. Arbitrators respond in between 5 business days.\n\t● The maximum period for a dispute is 14 days.\n\t● You need to cooperate with the {1} and provide the information they request to make your case.\n\t● You accepted the rules outlined in the dispute document in the user agreement when you first started the application.\n\nYou can read more about the dispute process at: {2} +support.initialInfo=Please enter a description of your problem in the text field below. Add as much information as possible to speed up dispute resolution time.\n\nHere is a check list for information you should provide:\n\t● If you are the BTC buyer: Did you make the Fiat or Altcoin transfer? If so, did you click the 'payment started' button in the application?\n\t● If you are the BTC seller: Did you receive the Fiat or Altcoin payment? If so, did you click the 'payment received' button in the application?\n\t● Which version of Bisq are you using?\n\t● Which operating system are you using?\n\t● If you encountered an issue with failed transactions please consider switching to a new data directory.\n\t Sometimes the data directory gets corrupted and leads to strange bugs. \n\t See: https://bisq.wiki/Switching_to_a_new_data_directory\n\nPlease make yourself familiar with the basic rules for the dispute process:\n\t● You need to respond to the {0}''s requests within 2 days.\n\t● {1}\n\t● The maximum period for a dispute is 14 days.\n\t● You need to cooperate with the {2} and provide the information they request to make your case.\n\t● You accepted the rules outlined in the dispute document in the user agreement when you first started the application.\n\nYou can read more about the dispute process at: {3} +support.initialMediatorMsg=Mediators will generally reply to you within 24 hours.\n\t If you have not had a reply after 48 hours please feel free to reach out to your mediator on Keybase.\n\t Mediators usernames on Keybase are the same as their usernames within the Bisq app.\n\t Your mediator is: {0} +support.initialArbitratorMsg=Arbitrators will generally reply to you within 5 days.\n\t If you have not had a reply after 7 days please feel free to reach out to your arbitrator on Keybase.\n\t Arbitrators usernames on Keybase are the same as their usernames within the Bisq app.\n\t Your arbitrator is: {0} support.systemMsg=Mensagem do sistema: {0} support.youOpenedTicket=Você abriu um pedido de suporte.\n\n{0}\n\nBisq versão: {1} support.youOpenedDispute=Você abriu um pedido para uma disputa.\n\n{0}\n\nBisq versão: {1} @@ -1064,10 +1067,12 @@ setting.preferences.dao.resyncFromResources.popup=After an application restart t setting.preferences.dao.resyncFromGenesis.popup=A resync from genesis transaction can take considerable time and CPU resources. Are you sure you want to do that? Mostly a resync from latest resource files is sufficient and much faster.\n\nIf you proceed, after an application restart the Bisq network governance data will be reloaded from the seed nodes and the BSQ consensus state will be rebuilt from the genesis transaction. setting.preferences.dao.resyncFromGenesis.resync=Resync from genesis and shutdown setting.preferences.dao.isDaoFullNode=Executar Bisq como nó completo DAO +setting.preferences.dao.activated=DAO activated +setting.preferences.dao.activated.popup=The change will be applied after a restart setting.preferences.dao.rpcUser=Nome de usuário de RPC setting.preferences.dao.rpcPw=Senha de RPC setting.preferences.dao.blockNotifyPort=Bloquear porta de notificação -setting.preferences.dao.fullNodeInfo=Para executar o Bisq como nó completo da DAO você precisa ter Bitcoin Core em rodando localmente e RPC ativado. Todos os requisitos estão documentados em '' {0} ''. +setting.preferences.dao.fullNodeInfo=Para executar o Bisq como nó completo da DAO você precisa ter Bitcoin Core em rodando localmente e RPC ativado. Todos os requisitos estão documentados em '' {0} ''. setting.preferences.dao.fullNodeInfo.ok=Abrir página de documentos setting.preferences.dao.fullNodeInfo.cancel=Não, eu fico com o modo nó lite settings.preferences.editCustomExplorer.headline=Explorer Settings @@ -1922,15 +1927,10 @@ dao.news.bisqDAO.title=A DAO BISQ dao.news.bisqDAO.description=O modelo de governança da Bisq é assim como a exchange - descentralizado e resistente à censura. As ferramentas que tornam isso realidade são a DAO da BISQ e o token BSQ. dao.news.bisqDAO.readMoreLink=Ler mais sobre o DAO da Bisq -dao.news.pastContribution.title=JÁ FEZ CONTRIBUIÇÕES? SOLICITE BSQ -dao.news.pastContribution.description=Se você já contribuiu para a Bisq, use o endereço BSQ abaixo e faça uma solicitação para fazer parte da distribuição gênese do BSQ. -dao.news.pastContribution.yourAddress=Seu Endereço de Carteira BSQ -dao.news.pastContribution.requestNow=Solicitar agora - -dao.news.daoInfo.title=RODE O DAO DA BISQ EM NOSSA TESTNET -dao.news.daoInfo.description=A rede principal da DAO do Bisq ainda não foi lançada, mas você pode aprender sobre a DAO do Bisq executando-a na nossa rede de testes. -dao.news.daoInfo.firstSection.title=1. Mude para o Modo Testnet da DAO -dao.news.daoInfo.firstSection.content=Mude para o Testnet da DAO na seção Configurações. +dao.news.daoInfo.title=ENABLE THE BISQ DAO +dao.news.daoInfo.description=To participate in the Bisq DAO and to use BSQ for discounted trading fees, you need to enable the DAO. When the DAO is enabled, Bisq downloads all missing blocks and verifies BSQ transactions. This verification process requires time, during which you may see Bisq use a lot of memory and processing power. This is normal. +dao.news.daoInfo.firstSection.title=1. Enable DAO +dao.news.daoInfo.firstSection.content=Enable the Bisq DAO and restart. dao.news.DAOOnTestnet.secondSection.title=2. Adquira alguns BSQ dao.news.DAOOnTestnet.secondSection.content=Solicite BSQ no Slack ou Compre BSQ na Bisq. dao.news.DAOOnTestnet.thirdSection.title=3. Participe de um Ciclo de Votação @@ -2679,6 +2679,7 @@ payment.secret=Pergunta secreta payment.answer=Resposta payment.wallet=ID da carteira payment.capitual.cap=CAP Code +payment.upi.virtualPaymentAddress=Virtual Payment Address # suppress inspection "UnusedProperty" payment.swift.headline=International SWIFT Wire Transfer @@ -2767,11 +2768,65 @@ payment.amazonGiftCard.upgrade=Amazon gift cards payment method requires the cou payment.account.amazonGiftCard.addCountryInfo={0}\nYour existing Amazon Gift Card account ({1}) does not have a Country specified.\nPlease enter your Amazon Gift Card Country to update your account data.\nThis will not affect your account age status. payment.amazonGiftCard.upgrade.headLine=Update Amazon Gift Card account -payment.swift.info=Carefully review the core guidelines for using SWIFT on Bisq:\n\n- fill all fields completely and accurately \n- buyer must send payment in currency specified by the offer maker \n- buyer must use the shared fee model (SHA) \n- buyer and seller may incur fees, so they should check their bank's fee schedules beforehand \n\nSWIFT is more sophisticated than other payment methods, so please take a moment to review full guidance on the wiki [HYPERLINK:https://bisq.wiki/SWIFT]. +payment.swift.info.account=Carefully review the core guidelines for using SWIFT on Bisq:\n\n- fill all fields completely and accurately \n- buyer must send payment in currency specified by the offer maker \n- buyer must use the shared fee model (SHA) \n- buyer and seller may incur fees, so they should check their bank's fee schedules beforehand \n\nSWIFT is more sophisticated than other payment methods, so please take a moment to review full guidance on the wiki [HYPERLINK:https://bisq.wiki/SWIFT]. payment.swift.info.buyer=To buy bitcoin with SWIFT, you must:\n\n- send payment in the currency specified by the offer maker \n- use the shared fee model (SHA) to send payment\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. -payment.swift.info.seller=SWIFT senders are required to use the shared payment model (SHA) to send payments.\n\nIf you receive a SWIFT payment that does not use SHA, open a mediation ticket.\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. +payment.swift.info.seller=SWIFT senders are required to use the shared fee model (SHA) to send payments.\n\nIf you receive a SWIFT payment that does not use SHA, open a mediation ticket.\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. + +payment.imps.info.account=Please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a maximum of Rs. 200,000 that can be sent per transaction. If you are trading over this amount multiple transactions will be needed. However be aware their is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nSome banks have different limits for their customers. +payment.imps.info.buyer=Please send payment only to the account details provided in Bisq.\n\nThe maximum trade size is Rs. 200,000 per transaction.\n\nIf your trade is over Rs. 200,000 you will have to make multiple transfers. However be aware their is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nPlease note some banks have different limits for their customers. +payment.imps.info.seller=If you intend to receive over Rs. 200,000 per trade you should expect the buyer to have to make multiple transfers. However be aware there is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nPlease note some banks have different limits for their customers. + +payment.neft.info.account=Please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a maximum of Rs. 50,000 that can be sent per transaction. If you are trading over this amount multiple transactions will be needed.\n\nPlease note some banks have different limits for their customers. +payment.neft.info.buyer=Please send payment only to the account details provided in Bisq.\n\nThe maximum trade size is Rs. 50,000 per transaction.\n\nIf your trade is over Rs. 50,000 you will have to make multiple transfers.\n\nPlease note some banks have different limits for their customers. +payment.neft.info.seller=If you intend to receive over Rs. 50,000 per trade you should expect the buyer to have to make multiple transfers.\n\nPlease note some banks have different limits for their customers. + +payment.paytm.info.account=Please make sure to include your email or phone number that matches your email or phone number in your PayTM account. \n\nWhen users set up a PayTM account with No KYC users are limited to: \n\n● Maximum of Rs. 5,000 can be sent per transaction.\n● Maximum of Rs. 10,000 can be held in someone's PayTM wallet.\n\nIf you intend to trade amount of over 5,000 per trade you will need to complete KYC with PayTM. With KYC users are limited to:\n\n● Maximum of Rs. 100,000 can be sent per transaction.\n● Maximum of Rs. 100,000 can be held in someone's PayTM wallet.\n\nUsers should also be aware of account limits. Trades above PayTM account limits will likely have to take place over more than one day, or, be cancelled. +payment.paytm.info.buyer=Please send payment only to the email address or phone number provided.\n\nIf you intend to trade amount of over Rs. 5,000 per trade you will need to complete KYC with PayTM.\n\nWith No KYC Rs. 5,000 can be sent per transaction.\n\nWith KYC users Rs. 100,000 can be sent per transaction. +payment.paytm.info.seller=If you intend to trade amount of over Rs. 5,000 per trade you will need to complete KYC with PayTM. With KYC users are limited to:\n\n● Maximum of Rs. 100,000 can be sent per transaction.\n● Maximum of Rs. 100,000 can be held in your PayTM wallet .\n\nUsers should also be aware of account limits. As a maximum of Rs. 100,000 can be held in your PayTM wallet please make sure you transfer out your rupees regularly. + +payment.rtgs.info.account=RTGS is for payments of large trades of Rs. 200,000 or over.\n\nWhen setting up your RTGS payment account please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). +payment.rtgs.info.buyer=Please send payment only to the account details provided in Bisq.\n\nPlease be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). +payment.rtgs.info.seller=Please be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). + +payment.upi.info.account=Please make sure to include your Virtual Payment Address (VPA) also called your UPI ID. The format for this is like an email ID: with the sign “@” in the middle. For example, your UPI ID could be “receiver’s_name@bank_name” or “phone_number@bank_name.” \n\nFor UPI there is a maximum limit of Rs. 100,000 that can be sent per transaction. \n\nIf you intend to trade amount of over Rs. 100,000 per trade it is likely trades will have to take place over multiple transfers. \n\nPlease note some banks have different limits for their customers. +payment.upi.info.buyer=Please send payment only to the VPA / UPI ID provided in Bisq. \n\nThe maximum trade size is Rs. 100,000 per transaction. \n\nIf your trade is over Rs. 100,000 you will have to make multiple transfers. \n\nPlease note some banks have different limits for their customers. +payment.upi.info.seller=If you intend to receive over Rs. 100,000 per trade you should expect the buyer to have to make multiple transfers. \n\nPlease note some banks have different limits for their customers. + +payment.celpay.info.account=Please make sure to include the email your Celsius account is registered to. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account.\n\nCelPay users are limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Buyers can send any matching currency stablecoin to the BTC Seller. +payment.celpay.info.buyer=Please send payment only to the email address provided by the BTC Seller by sending a payment link.\n\nCelPay is limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Buyers can send any matching currency stablecoin to the BTC Seller. +payment.celpay.info.seller=BTC Sellers should expect to receive payment via a secure payment link. Please make sure the email payment link contains the email address provided by the BTC Buyer.\n\nCelPay users are limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Sellers should expect to receive any matching currency stablecoin from the BTC Buyer. It is possible for the BTC Buyer to send any matching currency stablecoin. +payment.celpay.supportedCurrenciesForReceiver=Supported currencies (please note: all the currencies below are supported stable coins within the Celcius app. Trades are for stable coins, not fiat.) + +payment.nequi.info.account=Please make sure to include your phone number that is associated with your Nequi account.\n\nWhen users set up a Nequi account payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. +payment.nequi.info.buyer=Please send payment only to the phone number provided in the BTC Seller's Bisq account.\n\nWhen users set up a Nequi account, payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. +payment.nequi.info.seller=Please check that the payment received matches the phone number provided in the BTC Buyer's Bisq account.\n\nWhen users set up a Nequi account, payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. + +payment.bizum.info.account=To use Bizum you need a bank account (IBAN) in Spain and to be registered for the service.\n\nBizum can be used for trades between €0.50 and €1,000.\n\nThe maximum amount of transactions you can send/receive using Bizum is €2,000 Euros per day.\n\nBizum users can have a maximum of 150 operations per month.\n\nEach bank however may establish its own limits, within the above limits, for its clients.\n\nTraders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.bizum.info.buyer=Please send payment only to the BTC Seller's mobile phone number as provided in Bisq.\n\nThe maximum trade size is €1,000 per payment. The maximum amount of transactions you can send using Bizum is €2,000 Euros per day.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.bizum.info.seller=Please make sure your payment is received from the BTC Buyer's mobile phone number as provided in Bisq.\n\nThe maximum trade size is €1,000 per payment. The maximum amount of transactions you can receive using Bizum is €2,000 Euros per day.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. + +payment.pix.info.account=Please make sure to include your chosen Pix Key. There are four types of keys: CPF (Natural Persons Register) or CNPJ (National Registry of Legal Entities), e-mail address, telephone number or a random key generated by the system called a universally unique identifier (UUID). A different key must be used for each Pix account you have. Individuals can create up to five keys for each account they own.\n\nWhen trading on Bisq, BTC Buyers should use their Pix Keys as the payment description so that it is easy for the BTC Sellers to identify the payment as coming from themselves. +payment.pix.info.buyer=Please send payment only the Pix Key provided in the BTC Seller's Bisq account.\n\nPlease use your Pix Key as the payment reference so that it is easy for the BTC Seller to identify the payment as coming from yourself. +payment.pix.info.seller=Please check that the payment received description matches the Pix Key provided in the BTC Buyer's Bisq account. +payment.pix.key=Pix Key (CPF, CNPJ, Email, Phone number or UUID) + +payment.monese.info.account=Monese is a bank app for users of GBP, EUR and RON*. Monese allows users to send money to other Monese accounts instantly and for free in any supported currency.\n\n*To open a RON account in Monese, you need to either live in Romania or have Romanian citizenship.\n\nWhen setting up your Monese account in Bisq please make sure to include your name and phone number that matches your Monese account. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account. +payment.monese.info.buyer=Please send payment only to the phone number provided by the BTC Seller in their Bisq account. Please leave the payment description blank. +payment.monese.info.seller=BTC Sellers should expect to receive payment from the phone number / name shown in the BTC Buyer's Bisq account. + +payment.satispay.info.account=To use Satispay you need a bank account (IBAN) in Italy and to be registered for the service.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.satispay.info.buyer=Please send payment only to the BTC Seller's mobile phone number as provided in Bisq.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.satispay.info.seller=Please make sure your payment is received from the BTC Buyer's mobile phone number / name as provided in Bisq.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. + +payment.verse.info.account=Verse is a multiple currency payment method that can send and receive payment in EUR, SEK, HUF, DKK, PLN.\n\nWhen setting up your Verse account in Bisq please make sure to include the username that matches your username in your Verse account. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. +payment.verse.info.buyer=Please send payment only to the username provided by the BTC Seller in their Bisq account. Please leave the payment description blank.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. +payment.verse.info.seller=BTC Sellers should expect to receive payment from the username shown in the BTC Buyer's Bisq account.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. + +payment.strike.info.account=Please make sure to include your Strike username.\n\nIn Bisq, Strike is used for fiat to fiat payments only.\n\nPlease make sure you are aware of the Strike limits:\n\nUsers who have registered with only their email, name, and phone number have the following limits:\n\n● $100 maximum per deposit\n● $1,000 maximum total deposits per week\n● $100 maximum per payment\n\nUsers can increase their limits by providing Strike with more information. These users have the following limits:\n\n● $1,000 maximum per deposit\n● $1,000 maximum total deposits per week\n● $1,000 maximum per payment\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.strike.info.buyer=Please send payment only to the BTC Seller's Strike username as provided in Bisq.\n\nThe maximum trade size is $1,000 per payment.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.strike.info.seller=Please make sure your payment is received from the BTC Buyer's Strike username as provided in Bisq.\n\nThe maximum trade size is $1,000 per payment.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. payment.usPostalMoneyOrder.info=Trading using US Postal Money Orders (USPMO) on Bisq requires that you understand the following:\n\n- BTC buyers must write the BTC Seller’s name in both the Payer and the Payee’s fields & take a high-resolution photo of the USPMO and envelope with proof of tracking before sending.\n- BTC buyers must send the USPMO to the BTC seller with Delivery Confirmation.\n\nIn the event mediation is necessary, or if there is a trade dispute, you will be required to send the photos to the Bisq mediator or refund agent, together with the USPMO Serial Number, Post Office Number, and dollar amount, so they can verify the details on the US Post Office website.\n\nFailure to provide the required information to the Mediator or Arbitrator will result in losing the dispute case.\n\nIn all dispute cases, the USPMO sender bears 100% of the burden of responsibility in providing evidence/proof to the Mediator or Arbitrator.\n\nIf you do not understand these requirements, do not trade using USPMO on Bisq. @@ -2792,6 +2847,8 @@ payment.f2f.info='Face to Face' trades have different rules and come with differ payment.f2f.info.openURL=Abrir site payment.f2f.offerbook.tooltip.countryAndCity=País e cidade: {0} / {1} payment.f2f.offerbook.tooltip.extra=Informações adicionais: {0} +payment.ifsc=IFS Code +payment.ifsc.validation=IFSC format: XXXX0999999 payment.japan.bank=Banco payment.japan.branch=Ramo @@ -2800,7 +2857,7 @@ payment.japan.recipient=Nome payment.australia.payid=PayID payment.payid=PayID linked to financial institution. Like email address or mobile phone. payment.payid.info=A PayID like a phone number, email address or an Australian Business Number (ABN), that you can securely link to your bank, credit union or building society account. You need to have already created a PayID with your Australian financial institution. Both sending and receiving financial institutions must support PayID. For more information please check [HYPERLINK:https://payid.com.au/faqs/] -payment.amazonGiftCard.info=To pay with Amazon eGift Card, you will need to send an Amazon eGift Card to the BTC seller via your Amazon account. \n\nBisq will show the BTC seller''s email address or phone number where the gift card should be sent, and you must include the trade ID in the gift card''s message field. Please see the wiki [HYPERLINK:https://bisq.wiki/Amazon_eGift_card] for further details and best practices. \n\nThree important notes:\n- try to send gift cards with amounts of 100 USD or smaller, as Amazon is known to flag larger gift cards as fraudulent\n- try to use creative, believable text for the gift card''s message (e.g., "Happy birthday Susan!") along with the trade ID (and use trader chat to tell your trading peer the reference text you picked so they can verify your payment)\n- Amazon eGift Cards can only be redeemed on the Amazon website they were purchased on (e.g., a gift card purchased on amazon.it can only be redeemed on amazon.it) +payment.amazonGiftCard.info=To pay with Amazon eGift Card, you will need to send an Amazon eGift Card to the BTC seller via your Amazon account. \n\nPlease see the wiki [HYPERLINK:https://bisq.wiki/Amazon_eGift_card] for further details and best practices. \n\nThree important notes:\n- try to send gift cards with amounts of 100 USD or smaller, as Amazon is known to flag larger gift cards as fraudulent\n- try to use creative, believable text for the gift card''s message (e.g., "Happy birthday Susan!") and use trader chat to tell your trading peer the reference text you picked so they can verify your payment\n- Amazon eGift Cards can only be redeemed on the Amazon website they were purchased on (e.g., a gift card purchased on amazon.it can only be redeemed on amazon.it) # We use constants from the code so we do not use our normal naming convention # dynamic values are not recognized by IntelliJ @@ -2885,12 +2942,38 @@ PAYSERA=Paysera # suppress inspection "UnusedProperty" PAXUM=Paxum # suppress inspection "UnusedProperty" +NEFT=India/NEFT +# suppress inspection "UnusedProperty" +RTGS=India/RTGS +# suppress inspection "UnusedProperty" +IMPS=India/IMPS +# suppress inspection "UnusedProperty" +UPI=India/UPI +# suppress inspection "UnusedProperty" +PAYTM=India/PayTM +# suppress inspection "UnusedProperty" +NEQUI=Nequi +# suppress inspection "UnusedProperty" +BIZUM=Bizum +# suppress inspection "UnusedProperty" +PIX=Pix +# suppress inspection "UnusedProperty" AMAZON_GIFT_CARD=Amazon eGift Card # suppress inspection "UnusedProperty" BLOCK_CHAINS_INSTANT=Instant Altcoins # suppress inspection "UnusedProperty" CAPITUAL=Capitual # suppress inspection "UnusedProperty" +CELPAY=CelPay +# suppress inspection "UnusedProperty" +MONESE=Monese +# suppress inspection "UnusedProperty" +SATISPAY=Satispay +# suppress inspection "UnusedProperty" +VERSE=Verse +# suppress inspection "UnusedProperty" +STRIKE=Strike +# suppress inspection "UnusedProperty" SWIFT=SWIFT International Wire Transfer # Deprecated: Cannot be deleted as it would break old trade history entries @@ -2945,12 +3028,38 @@ PAYSERA_SHORT=Paysera # suppress inspection "UnusedProperty" PAXUM_SHORT=Paxum # suppress inspection "UnusedProperty" +NEFT_SHORT=NEFT +# suppress inspection "UnusedProperty" +RTGS_SHORT=RTGS +# suppress inspection "UnusedProperty" +IMPS_SHORT=IMPS +# suppress inspection "UnusedProperty" +UPI_SHORT=UPI +# suppress inspection "UnusedProperty" +PAYTM_SHORT=PayTM +# suppress inspection "UnusedProperty" +NEQUI_SHORT=Nequi +# suppress inspection "UnusedProperty" +BIZUM_SHORT=Bizum +# suppress inspection "UnusedProperty" +PIX_SHORT=Pix +# suppress inspection "UnusedProperty" AMAZON_GIFT_CARD_SHORT=Amazon eGift Card # suppress inspection "UnusedProperty" BLOCK_CHAINS_INSTANT_SHORT=Altcoins Instant # suppress inspection "UnusedProperty" CAPITUAL_SHORT=Capitual # suppress inspection "UnusedProperty" +CELPAY_SHORT=CelPay +# suppress inspection "UnusedProperty" +MONESE_SHORT=Monese +# suppress inspection "UnusedProperty" +SATISPAY_SHORT=Satispay +# suppress inspection "UnusedProperty" +VERSE_SHORT=Verse +# suppress inspection "UnusedProperty" +STRIKE_SHORT=Strike +# suppress inspection "UnusedProperty" SWIFT_SHORT=SWIFT # Deprecated: Cannot be deleted as it would break old trade history entries diff --git a/core/src/main/resources/i18n/displayStrings_pt.properties b/core/src/main/resources/i18n/displayStrings_pt.properties index 387a7bf9c45..1d21baa3808 100644 --- a/core/src/main/resources/i18n/displayStrings_pt.properties +++ b/core/src/main/resources/i18n/displayStrings_pt.properties @@ -284,6 +284,7 @@ mainView.walletServiceErrorMsg.rejectedTxException=Uma transação foi rejeitada mainView.networkWarning.allConnectionsLost=Você perdeu a conexão com todos os pares de rede de {0} .\nTalvez você tenha perdido sua conexão de internet ou o seu computador estivesse no modo de espera. mainView.networkWarning.localhostBitcoinLost=Perdeu a conexão ao nó Bitcoin do localhost.\nPor favor recomeçar o programa do Bisq para conectar à outros nós Bitcoin ou recomeçar o nó Bitcoin do localhost. +mainView.networkWarning.clockWatcher=Your computer was asleep for {0} seconds. Standby mode has been known to cause trades to fail. In order to operate correctly Bisq requires that standby mode be disabled in your computer's settings. mainView.version.update=(Atualização disponível) @@ -821,7 +822,7 @@ portfolio.pending.mediationResult.popup.alreadyAccepted=Você já aceitou portfolio.pending.failedTrade.taker.missingTakerFeeTx=The taker fee transaction is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked and no trade fee has been paid. You can move this trade to failed trades. portfolio.pending.failedTrade.maker.missingTakerFeeTx=The peer's taker fee transaction is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked. Your offer is still available to other traders, so you have not lost the maker fee. You can move this trade to failed trades. portfolio.pending.failedTrade.missingDepositTx=The deposit transaction (the 2-of-2 multisig transaction) is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked but your trade fee has been paid. You can make a request to be reimbursed the trade fee here: [HYPERLINK:https://github.com/bisq-network/support/issues]\n\nFeel free to move this trade to failed trades. -portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the fiat or altcoin payment to the BTC seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] +portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the fiat or altcoin payment to the BTC seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing but funds have been locked in the deposit transaction.\n\nIf the buyer is also missing the delayed payout transaction, they will be instructed to NOT send the payment and open a mediation ticket instead. You should also open a mediation ticket with Cmd/Ctrl+o. \n\nIf the buyer has not sent payment yet, the mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). Otherwise the trade amount should go to the buyer. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.errorMsgSet=There was an error during trade protocol execution.\n\nError: {0}\n\nIt might be that this error is not critical, and the trade can be completed normally. If you are unsure, open a mediation ticket to get advice from Bisq mediators. \n\nIf the error was critical and the trade cannot be completed, you might have lost your trade fee. Request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.missingContract=The trade contract is not set.\n\nThe trade cannot be completed and you might have lost your trade fee. If so, you can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] @@ -996,7 +997,9 @@ support.buyerTaker=Comprador de BTC/Aceitador support.sellerTaker=Vendedor de BTC/Aceitador support.backgroundInfo=O Bisq não é uma empresa, por isso as disputas são tratadas diferentemente.\n\nOs negociadores podem se comunicar dentro do programa via chat seguro no ecrã de negócios abertos para tentar resolver disputas por conta própria. Se isso não for suficiente, um mediador pode ajudar. O mediador avaliará a situação e sugerirá um pagamento dos fundos de negócio. Se ambos os negociadores aceitarem essa sugestão, a transação de pagamento será concluída e o negócio será encerrado. Se um ou ambos os negociadores não concordarem com o pagamento sugerido pelo mediador, eles podem solicitar a arbitragem. O árbitro reavaliará a situação e, se justificado, pagará pessoalmente o comerciante de volta e solicitará reembolso à OAD do Bisq. -support.initialInfo=Please enter a description of your problem in the text field below. Add as much information as possible to speed up dispute resolution time.\n\nHere is a check list for information you should provide:\n\t● If you are the BTC buyer: Did you make the Fiat or Altcoin transfer? If so, did you click the 'payment started' button in the application?\n\t● If you are the BTC seller: Did you receive the Fiat or Altcoin payment? If so, did you click the 'payment received' button in the application?\n\t● Which version of Bisq are you using?\n\t● Which operating system are you using?\n\t● If you encountered an issue with failed transactions please consider switching to a new data directory.\n\t Sometimes the data directory gets corrupted and leads to strange bugs. \n\t See: https://bisq.wiki/Switching_to_a_new_data_directory\n\nPlease make yourself familiar with the basic rules for the dispute process:\n\t● You need to respond to the {0}''s requests within 2 days.\n\t● Mediators respond in between 2 days. Arbitrators respond in between 5 business days.\n\t● The maximum period for a dispute is 14 days.\n\t● You need to cooperate with the {1} and provide the information they request to make your case.\n\t● You accepted the rules outlined in the dispute document in the user agreement when you first started the application.\n\nYou can read more about the dispute process at: {2} +support.initialInfo=Please enter a description of your problem in the text field below. Add as much information as possible to speed up dispute resolution time.\n\nHere is a check list for information you should provide:\n\t● If you are the BTC buyer: Did you make the Fiat or Altcoin transfer? If so, did you click the 'payment started' button in the application?\n\t● If you are the BTC seller: Did you receive the Fiat or Altcoin payment? If so, did you click the 'payment received' button in the application?\n\t● Which version of Bisq are you using?\n\t● Which operating system are you using?\n\t● If you encountered an issue with failed transactions please consider switching to a new data directory.\n\t Sometimes the data directory gets corrupted and leads to strange bugs. \n\t See: https://bisq.wiki/Switching_to_a_new_data_directory\n\nPlease make yourself familiar with the basic rules for the dispute process:\n\t● You need to respond to the {0}''s requests within 2 days.\n\t● {1}\n\t● The maximum period for a dispute is 14 days.\n\t● You need to cooperate with the {2} and provide the information they request to make your case.\n\t● You accepted the rules outlined in the dispute document in the user agreement when you first started the application.\n\nYou can read more about the dispute process at: {3} +support.initialMediatorMsg=Mediators will generally reply to you within 24 hours.\n\t If you have not had a reply after 48 hours please feel free to reach out to your mediator on Keybase.\n\t Mediators usernames on Keybase are the same as their usernames within the Bisq app.\n\t Your mediator is: {0} +support.initialArbitratorMsg=Arbitrators will generally reply to you within 5 days.\n\t If you have not had a reply after 7 days please feel free to reach out to your arbitrator on Keybase.\n\t Arbitrators usernames on Keybase are the same as their usernames within the Bisq app.\n\t Your arbitrator is: {0} support.systemMsg=Mensagem do sistema: {0} support.youOpenedTicket=Você abriu um pedido para apoio.\n\n{0}\n\nBisq versão: {1} support.youOpenedDispute=Você abriu um pedido para uma disputa.\n\n{0}\n\nBisq versão: {1} @@ -1064,10 +1067,12 @@ setting.preferences.dao.resyncFromResources.popup=After an application restart t setting.preferences.dao.resyncFromGenesis.popup=A resync from genesis transaction can take considerable time and CPU resources. Are you sure you want to do that? Mostly a resync from latest resource files is sufficient and much faster.\n\nIf you proceed, after an application restart the Bisq network governance data will be reloaded from the seed nodes and the BSQ consensus state will be rebuilt from the genesis transaction. setting.preferences.dao.resyncFromGenesis.resync=Resync from genesis and shutdown setting.preferences.dao.isDaoFullNode=Executar Bisq como nó completo OAD +setting.preferences.dao.activated=DAO activated +setting.preferences.dao.activated.popup=The change will be applied after a restart setting.preferences.dao.rpcUser=Nome de usuário de RPC setting.preferences.dao.rpcPw=Senha de RPC setting.preferences.dao.blockNotifyPort=Bloquear porta de notificação -setting.preferences.dao.fullNodeInfo=Para executar o Bisq como nó completo da OAD você precisa ter Bitcoin Core em execução local e RPC ativado. Todos os requerimentos estão documentados em '' {0} ''. +setting.preferences.dao.fullNodeInfo=Para executar o Bisq como nó completo da OAD você precisa ter Bitcoin Core em execução local e RPC ativado. Todos os requerimentos estão documentados em '' {0} ''. setting.preferences.dao.fullNodeInfo.ok=Abrir página de documentos setting.preferences.dao.fullNodeInfo.cancel=Não, eu fico com o modo nó lite settings.preferences.editCustomExplorer.headline=Explorer Settings @@ -1922,15 +1927,10 @@ dao.news.bisqDAO.title=A OAD DO BISQ dao.news.bisqDAO.description=Assim como o mercado de câmbio do Bisq é descentralizado e resistente à censura, o seu modelo de governação também o é - e a OAD do Bisq e o token da BSQ são as ferramentas que tornam isso possível. dao.news.bisqDAO.readMoreLink=Saber Mais Sobre a OAD do Bisq -dao.news.pastContribution.title=FEZ CONTRIBUIÇÕES NO PASSADO? PEÇA BSQ -dao.news.pastContribution.description=Se você contribuiu para o Bisq, por favor, use o endereço BSQ abaixo e faça um pedido para participar da distribuição genesis de BSQ. -dao.news.pastContribution.yourAddress=O seu endereço da carteira BSQ -dao.news.pastContribution.requestNow=Solicitar agora - -dao.news.daoInfo.title=EXECUTE A OAD DO BISQ NA NOSSA REDE DE TESTES -dao.news.daoInfo.description=A mainnet da OAD do Bisq ainda não foi lançada, mas você pode aprender sobre a OAD do Bisq executando-a na nossa testnet. -dao.news.daoInfo.firstSection.title=1. Mudar para Modo Testnet da OAD -dao.news.daoInfo.firstSection.content=Mude para a Testnet da OAD no painel de Definições +dao.news.daoInfo.title=ENABLE THE BISQ DAO +dao.news.daoInfo.description=To participate in the Bisq DAO and to use BSQ for discounted trading fees, you need to enable the DAO. When the DAO is enabled, Bisq downloads all missing blocks and verifies BSQ transactions. This verification process requires time, during which you may see Bisq use a lot of memory and processing power. This is normal. +dao.news.daoInfo.firstSection.title=1. Enable DAO +dao.news.daoInfo.firstSection.content=Enable the Bisq DAO and restart. dao.news.DAOOnTestnet.secondSection.title=2. Obtenha alguns BSQ dao.news.DAOOnTestnet.secondSection.content=Solicite BSQ no Slack ou Compre BSQ no Bisq dao.news.DAOOnTestnet.thirdSection.title=3. Participe num Ciclo de Votação @@ -2679,6 +2679,7 @@ payment.secret=Pergunta secreta payment.answer=Resposta payment.wallet=ID da carteira payment.capitual.cap=CAP Code +payment.upi.virtualPaymentAddress=Virtual Payment Address # suppress inspection "UnusedProperty" payment.swift.headline=International SWIFT Wire Transfer @@ -2767,11 +2768,65 @@ payment.amazonGiftCard.upgrade=Amazon gift cards payment method requires the cou payment.account.amazonGiftCard.addCountryInfo={0}\nYour existing Amazon Gift Card account ({1}) does not have a Country specified.\nPlease enter your Amazon Gift Card Country to update your account data.\nThis will not affect your account age status. payment.amazonGiftCard.upgrade.headLine=Update Amazon Gift Card account -payment.swift.info=Carefully review the core guidelines for using SWIFT on Bisq:\n\n- fill all fields completely and accurately \n- buyer must send payment in currency specified by the offer maker \n- buyer must use the shared fee model (SHA) \n- buyer and seller may incur fees, so they should check their bank's fee schedules beforehand \n\nSWIFT is more sophisticated than other payment methods, so please take a moment to review full guidance on the wiki [HYPERLINK:https://bisq.wiki/SWIFT]. +payment.swift.info.account=Carefully review the core guidelines for using SWIFT on Bisq:\n\n- fill all fields completely and accurately \n- buyer must send payment in currency specified by the offer maker \n- buyer must use the shared fee model (SHA) \n- buyer and seller may incur fees, so they should check their bank's fee schedules beforehand \n\nSWIFT is more sophisticated than other payment methods, so please take a moment to review full guidance on the wiki [HYPERLINK:https://bisq.wiki/SWIFT]. payment.swift.info.buyer=To buy bitcoin with SWIFT, you must:\n\n- send payment in the currency specified by the offer maker \n- use the shared fee model (SHA) to send payment\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. -payment.swift.info.seller=SWIFT senders are required to use the shared payment model (SHA) to send payments.\n\nIf you receive a SWIFT payment that does not use SHA, open a mediation ticket.\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. +payment.swift.info.seller=SWIFT senders are required to use the shared fee model (SHA) to send payments.\n\nIf you receive a SWIFT payment that does not use SHA, open a mediation ticket.\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. + +payment.imps.info.account=Please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a maximum of Rs. 200,000 that can be sent per transaction. If you are trading over this amount multiple transactions will be needed. However be aware their is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nSome banks have different limits for their customers. +payment.imps.info.buyer=Please send payment only to the account details provided in Bisq.\n\nThe maximum trade size is Rs. 200,000 per transaction.\n\nIf your trade is over Rs. 200,000 you will have to make multiple transfers. However be aware their is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nPlease note some banks have different limits for their customers. +payment.imps.info.seller=If you intend to receive over Rs. 200,000 per trade you should expect the buyer to have to make multiple transfers. However be aware there is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nPlease note some banks have different limits for their customers. + +payment.neft.info.account=Please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a maximum of Rs. 50,000 that can be sent per transaction. If you are trading over this amount multiple transactions will be needed.\n\nPlease note some banks have different limits for their customers. +payment.neft.info.buyer=Please send payment only to the account details provided in Bisq.\n\nThe maximum trade size is Rs. 50,000 per transaction.\n\nIf your trade is over Rs. 50,000 you will have to make multiple transfers.\n\nPlease note some banks have different limits for their customers. +payment.neft.info.seller=If you intend to receive over Rs. 50,000 per trade you should expect the buyer to have to make multiple transfers.\n\nPlease note some banks have different limits for their customers. + +payment.paytm.info.account=Please make sure to include your email or phone number that matches your email or phone number in your PayTM account. \n\nWhen users set up a PayTM account with No KYC users are limited to: \n\n● Maximum of Rs. 5,000 can be sent per transaction.\n● Maximum of Rs. 10,000 can be held in someone's PayTM wallet.\n\nIf you intend to trade amount of over 5,000 per trade you will need to complete KYC with PayTM. With KYC users are limited to:\n\n● Maximum of Rs. 100,000 can be sent per transaction.\n● Maximum of Rs. 100,000 can be held in someone's PayTM wallet.\n\nUsers should also be aware of account limits. Trades above PayTM account limits will likely have to take place over more than one day, or, be cancelled. +payment.paytm.info.buyer=Please send payment only to the email address or phone number provided.\n\nIf you intend to trade amount of over Rs. 5,000 per trade you will need to complete KYC with PayTM.\n\nWith No KYC Rs. 5,000 can be sent per transaction.\n\nWith KYC users Rs. 100,000 can be sent per transaction. +payment.paytm.info.seller=If you intend to trade amount of over Rs. 5,000 per trade you will need to complete KYC with PayTM. With KYC users are limited to:\n\n● Maximum of Rs. 100,000 can be sent per transaction.\n● Maximum of Rs. 100,000 can be held in your PayTM wallet .\n\nUsers should also be aware of account limits. As a maximum of Rs. 100,000 can be held in your PayTM wallet please make sure you transfer out your rupees regularly. + +payment.rtgs.info.account=RTGS is for payments of large trades of Rs. 200,000 or over.\n\nWhen setting up your RTGS payment account please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). +payment.rtgs.info.buyer=Please send payment only to the account details provided in Bisq.\n\nPlease be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). +payment.rtgs.info.seller=Please be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). + +payment.upi.info.account=Please make sure to include your Virtual Payment Address (VPA) also called your UPI ID. The format for this is like an email ID: with the sign “@” in the middle. For example, your UPI ID could be “receiver’s_name@bank_name” or “phone_number@bank_name.” \n\nFor UPI there is a maximum limit of Rs. 100,000 that can be sent per transaction. \n\nIf you intend to trade amount of over Rs. 100,000 per trade it is likely trades will have to take place over multiple transfers. \n\nPlease note some banks have different limits for their customers. +payment.upi.info.buyer=Please send payment only to the VPA / UPI ID provided in Bisq. \n\nThe maximum trade size is Rs. 100,000 per transaction. \n\nIf your trade is over Rs. 100,000 you will have to make multiple transfers. \n\nPlease note some banks have different limits for their customers. +payment.upi.info.seller=If you intend to receive over Rs. 100,000 per trade you should expect the buyer to have to make multiple transfers. \n\nPlease note some banks have different limits for their customers. + +payment.celpay.info.account=Please make sure to include the email your Celsius account is registered to. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account.\n\nCelPay users are limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Buyers can send any matching currency stablecoin to the BTC Seller. +payment.celpay.info.buyer=Please send payment only to the email address provided by the BTC Seller by sending a payment link.\n\nCelPay is limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Buyers can send any matching currency stablecoin to the BTC Seller. +payment.celpay.info.seller=BTC Sellers should expect to receive payment via a secure payment link. Please make sure the email payment link contains the email address provided by the BTC Buyer.\n\nCelPay users are limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Sellers should expect to receive any matching currency stablecoin from the BTC Buyer. It is possible for the BTC Buyer to send any matching currency stablecoin. +payment.celpay.supportedCurrenciesForReceiver=Supported currencies (please note: all the currencies below are supported stable coins within the Celcius app. Trades are for stable coins, not fiat.) + +payment.nequi.info.account=Please make sure to include your phone number that is associated with your Nequi account.\n\nWhen users set up a Nequi account payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. +payment.nequi.info.buyer=Please send payment only to the phone number provided in the BTC Seller's Bisq account.\n\nWhen users set up a Nequi account, payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. +payment.nequi.info.seller=Please check that the payment received matches the phone number provided in the BTC Buyer's Bisq account.\n\nWhen users set up a Nequi account, payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. + +payment.bizum.info.account=To use Bizum you need a bank account (IBAN) in Spain and to be registered for the service.\n\nBizum can be used for trades between €0.50 and €1,000.\n\nThe maximum amount of transactions you can send/receive using Bizum is €2,000 Euros per day.\n\nBizum users can have a maximum of 150 operations per month.\n\nEach bank however may establish its own limits, within the above limits, for its clients.\n\nTraders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.bizum.info.buyer=Please send payment only to the BTC Seller's mobile phone number as provided in Bisq.\n\nThe maximum trade size is €1,000 per payment. The maximum amount of transactions you can send using Bizum is €2,000 Euros per day.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.bizum.info.seller=Please make sure your payment is received from the BTC Buyer's mobile phone number as provided in Bisq.\n\nThe maximum trade size is €1,000 per payment. The maximum amount of transactions you can receive using Bizum is €2,000 Euros per day.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. + +payment.pix.info.account=Please make sure to include your chosen Pix Key. There are four types of keys: CPF (Natural Persons Register) or CNPJ (National Registry of Legal Entities), e-mail address, telephone number or a random key generated by the system called a universally unique identifier (UUID). A different key must be used for each Pix account you have. Individuals can create up to five keys for each account they own.\n\nWhen trading on Bisq, BTC Buyers should use their Pix Keys as the payment description so that it is easy for the BTC Sellers to identify the payment as coming from themselves. +payment.pix.info.buyer=Please send payment only the Pix Key provided in the BTC Seller's Bisq account.\n\nPlease use your Pix Key as the payment reference so that it is easy for the BTC Seller to identify the payment as coming from yourself. +payment.pix.info.seller=Please check that the payment received description matches the Pix Key provided in the BTC Buyer's Bisq account. +payment.pix.key=Pix Key (CPF, CNPJ, Email, Phone number or UUID) + +payment.monese.info.account=Monese is a bank app for users of GBP, EUR and RON*. Monese allows users to send money to other Monese accounts instantly and for free in any supported currency.\n\n*To open a RON account in Monese, you need to either live in Romania or have Romanian citizenship.\n\nWhen setting up your Monese account in Bisq please make sure to include your name and phone number that matches your Monese account. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account. +payment.monese.info.buyer=Please send payment only to the phone number provided by the BTC Seller in their Bisq account. Please leave the payment description blank. +payment.monese.info.seller=BTC Sellers should expect to receive payment from the phone number / name shown in the BTC Buyer's Bisq account. + +payment.satispay.info.account=To use Satispay you need a bank account (IBAN) in Italy and to be registered for the service.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.satispay.info.buyer=Please send payment only to the BTC Seller's mobile phone number as provided in Bisq.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.satispay.info.seller=Please make sure your payment is received from the BTC Buyer's mobile phone number / name as provided in Bisq.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. + +payment.verse.info.account=Verse is a multiple currency payment method that can send and receive payment in EUR, SEK, HUF, DKK, PLN.\n\nWhen setting up your Verse account in Bisq please make sure to include the username that matches your username in your Verse account. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. +payment.verse.info.buyer=Please send payment only to the username provided by the BTC Seller in their Bisq account. Please leave the payment description blank.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. +payment.verse.info.seller=BTC Sellers should expect to receive payment from the username shown in the BTC Buyer's Bisq account.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. + +payment.strike.info.account=Please make sure to include your Strike username.\n\nIn Bisq, Strike is used for fiat to fiat payments only.\n\nPlease make sure you are aware of the Strike limits:\n\nUsers who have registered with only their email, name, and phone number have the following limits:\n\n● $100 maximum per deposit\n● $1,000 maximum total deposits per week\n● $100 maximum per payment\n\nUsers can increase their limits by providing Strike with more information. These users have the following limits:\n\n● $1,000 maximum per deposit\n● $1,000 maximum total deposits per week\n● $1,000 maximum per payment\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.strike.info.buyer=Please send payment only to the BTC Seller's Strike username as provided in Bisq.\n\nThe maximum trade size is $1,000 per payment.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.strike.info.seller=Please make sure your payment is received from the BTC Buyer's Strike username as provided in Bisq.\n\nThe maximum trade size is $1,000 per payment.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. payment.usPostalMoneyOrder.info=Trading using US Postal Money Orders (USPMO) on Bisq requires that you understand the following:\n\n- BTC buyers must write the BTC Seller’s name in both the Payer and the Payee’s fields & take a high-resolution photo of the USPMO and envelope with proof of tracking before sending.\n- BTC buyers must send the USPMO to the BTC seller with Delivery Confirmation.\n\nIn the event mediation is necessary, or if there is a trade dispute, you will be required to send the photos to the Bisq mediator or refund agent, together with the USPMO Serial Number, Post Office Number, and dollar amount, so they can verify the details on the US Post Office website.\n\nFailure to provide the required information to the Mediator or Arbitrator will result in losing the dispute case.\n\nIn all dispute cases, the USPMO sender bears 100% of the burden of responsibility in providing evidence/proof to the Mediator or Arbitrator.\n\nIf you do not understand these requirements, do not trade using USPMO on Bisq. @@ -2792,6 +2847,8 @@ payment.f2f.info='Face to Face' trades have different rules and come with differ payment.f2f.info.openURL=Abrir página web payment.f2f.offerbook.tooltip.countryAndCity=País e cidade: {0} / {1} payment.f2f.offerbook.tooltip.extra=Informação adicional: {0} +payment.ifsc=IFS Code +payment.ifsc.validation=IFSC format: XXXX0999999 payment.japan.bank=Banco payment.japan.branch=Agência @@ -2800,7 +2857,7 @@ payment.japan.recipient=Nome payment.australia.payid=PayID payment.payid=PayID linked to financial institution. Like email address or mobile phone. payment.payid.info=A PayID like a phone number, email address or an Australian Business Number (ABN), that you can securely link to your bank, credit union or building society account. You need to have already created a PayID with your Australian financial institution. Both sending and receiving financial institutions must support PayID. For more information please check [HYPERLINK:https://payid.com.au/faqs/] -payment.amazonGiftCard.info=To pay with Amazon eGift Card, you will need to send an Amazon eGift Card to the BTC seller via your Amazon account. \n\nBisq will show the BTC seller''s email address or phone number where the gift card should be sent, and you must include the trade ID in the gift card''s message field. Please see the wiki [HYPERLINK:https://bisq.wiki/Amazon_eGift_card] for further details and best practices. \n\nThree important notes:\n- try to send gift cards with amounts of 100 USD or smaller, as Amazon is known to flag larger gift cards as fraudulent\n- try to use creative, believable text for the gift card''s message (e.g., "Happy birthday Susan!") along with the trade ID (and use trader chat to tell your trading peer the reference text you picked so they can verify your payment)\n- Amazon eGift Cards can only be redeemed on the Amazon website they were purchased on (e.g., a gift card purchased on amazon.it can only be redeemed on amazon.it) +payment.amazonGiftCard.info=To pay with Amazon eGift Card, you will need to send an Amazon eGift Card to the BTC seller via your Amazon account. \n\nPlease see the wiki [HYPERLINK:https://bisq.wiki/Amazon_eGift_card] for further details and best practices. \n\nThree important notes:\n- try to send gift cards with amounts of 100 USD or smaller, as Amazon is known to flag larger gift cards as fraudulent\n- try to use creative, believable text for the gift card''s message (e.g., "Happy birthday Susan!") and use trader chat to tell your trading peer the reference text you picked so they can verify your payment\n- Amazon eGift Cards can only be redeemed on the Amazon website they were purchased on (e.g., a gift card purchased on amazon.it can only be redeemed on amazon.it) # We use constants from the code so we do not use our normal naming convention # dynamic values are not recognized by IntelliJ @@ -2885,12 +2942,38 @@ PAYSERA=Paysera # suppress inspection "UnusedProperty" PAXUM=Paxum # suppress inspection "UnusedProperty" +NEFT=India/NEFT +# suppress inspection "UnusedProperty" +RTGS=India/RTGS +# suppress inspection "UnusedProperty" +IMPS=India/IMPS +# suppress inspection "UnusedProperty" +UPI=India/UPI +# suppress inspection "UnusedProperty" +PAYTM=India/PayTM +# suppress inspection "UnusedProperty" +NEQUI=Nequi +# suppress inspection "UnusedProperty" +BIZUM=Bizum +# suppress inspection "UnusedProperty" +PIX=Pix +# suppress inspection "UnusedProperty" AMAZON_GIFT_CARD=Amazon eGift Card # suppress inspection "UnusedProperty" BLOCK_CHAINS_INSTANT=Altcoins Instantâneas # suppress inspection "UnusedProperty" CAPITUAL=Capitual # suppress inspection "UnusedProperty" +CELPAY=CelPay +# suppress inspection "UnusedProperty" +MONESE=Monese +# suppress inspection "UnusedProperty" +SATISPAY=Satispay +# suppress inspection "UnusedProperty" +VERSE=Verse +# suppress inspection "UnusedProperty" +STRIKE=Strike +# suppress inspection "UnusedProperty" SWIFT=SWIFT International Wire Transfer # Deprecated: Cannot be deleted as it would break old trade history entries @@ -2945,12 +3028,38 @@ PAYSERA_SHORT=Paysera # suppress inspection "UnusedProperty" PAXUM_SHORT=Paxum # suppress inspection "UnusedProperty" +NEFT_SHORT=NEFT +# suppress inspection "UnusedProperty" +RTGS_SHORT=RTGS +# suppress inspection "UnusedProperty" +IMPS_SHORT=IMPS +# suppress inspection "UnusedProperty" +UPI_SHORT=UPI +# suppress inspection "UnusedProperty" +PAYTM_SHORT=PayTM +# suppress inspection "UnusedProperty" +NEQUI_SHORT=Nequi +# suppress inspection "UnusedProperty" +BIZUM_SHORT=Bizum +# suppress inspection "UnusedProperty" +PIX_SHORT=Pix +# suppress inspection "UnusedProperty" AMAZON_GIFT_CARD_SHORT=Amazon eGift Card # suppress inspection "UnusedProperty" BLOCK_CHAINS_INSTANT_SHORT=Altcoins Instant # suppress inspection "UnusedProperty" CAPITUAL_SHORT=Capitual # suppress inspection "UnusedProperty" +CELPAY_SHORT=CelPay +# suppress inspection "UnusedProperty" +MONESE_SHORT=Monese +# suppress inspection "UnusedProperty" +SATISPAY_SHORT=Satispay +# suppress inspection "UnusedProperty" +VERSE_SHORT=Verse +# suppress inspection "UnusedProperty" +STRIKE_SHORT=Strike +# suppress inspection "UnusedProperty" SWIFT_SHORT=SWIFT # Deprecated: Cannot be deleted as it would break old trade history entries @@ -3040,5 +3149,5 @@ validation.phone.invalidCharacters=O número de telfone {0} contém carácteres validation.phone.insufficientDigits=There are not enough digits in {0} to be a valid phone number validation.phone.tooManyDigits=There are too many digits in {0} to be a valid phone number validation.phone.invalidDialingCode=Country dialing code for number {0} is invalid for country {1}. The correct dialing code is {2}. -validation.invalidAddressList=Deve ser um lista de endereços válidos separados por vírgulas +validation.invalidAddressList=Deve ser um lista de endereços válidos separados por vírgulas validation.capitual.invalidFormat=Must be a valid CAP code of format: CAP-XXXXXX (6 alphanumeric characters) diff --git a/core/src/main/resources/i18n/displayStrings_ru.properties b/core/src/main/resources/i18n/displayStrings_ru.properties index 8911b2cd077..4bc691b716e 100644 --- a/core/src/main/resources/i18n/displayStrings_ru.properties +++ b/core/src/main/resources/i18n/displayStrings_ru.properties @@ -284,6 +284,7 @@ mainView.walletServiceErrorMsg.rejectedTxException=A transaction was rejected fr mainView.networkWarning.allConnectionsLost=Сбой соединения со всеми {0} узлами сети.\nВозможно, вы отключились от интернета, или Ваш компьютер перешел в режим ожидания. mainView.networkWarning.localhostBitcoinLost=Сбой соединения с локальным узлом Биткойн.\nПерезапустите приложение для подключения к другим узлам Биткойн или перезапустите свой локальный узел Биткойн. +mainView.networkWarning.clockWatcher=Your computer was asleep for {0} seconds. Standby mode has been known to cause trades to fail. In order to operate correctly Bisq requires that standby mode be disabled in your computer's settings. mainView.version.update=(Имеется обновление) @@ -821,7 +822,7 @@ portfolio.pending.mediationResult.popup.alreadyAccepted=You've already accepted portfolio.pending.failedTrade.taker.missingTakerFeeTx=The taker fee transaction is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked and no trade fee has been paid. You can move this trade to failed trades. portfolio.pending.failedTrade.maker.missingTakerFeeTx=The peer's taker fee transaction is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked. Your offer is still available to other traders, so you have not lost the maker fee. You can move this trade to failed trades. portfolio.pending.failedTrade.missingDepositTx=The deposit transaction (the 2-of-2 multisig transaction) is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked but your trade fee has been paid. You can make a request to be reimbursed the trade fee here: [HYPERLINK:https://github.com/bisq-network/support/issues]\n\nFeel free to move this trade to failed trades. -portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the fiat or altcoin payment to the BTC seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] +portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the fiat or altcoin payment to the BTC seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing but funds have been locked in the deposit transaction.\n\nIf the buyer is also missing the delayed payout transaction, they will be instructed to NOT send the payment and open a mediation ticket instead. You should also open a mediation ticket with Cmd/Ctrl+o. \n\nIf the buyer has not sent payment yet, the mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). Otherwise the trade amount should go to the buyer. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.errorMsgSet=There was an error during trade protocol execution.\n\nError: {0}\n\nIt might be that this error is not critical, and the trade can be completed normally. If you are unsure, open a mediation ticket to get advice from Bisq mediators. \n\nIf the error was critical and the trade cannot be completed, you might have lost your trade fee. Request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.missingContract=The trade contract is not set.\n\nThe trade cannot be completed and you might have lost your trade fee. If so, you can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] @@ -996,7 +997,9 @@ support.buyerTaker=Покупатель ВТС/тейкер support.sellerTaker=Продавец BTC/тейкер support.backgroundInfo=Bisq is not a company, so it handles disputes differently.\n\nTraders can communicate within the application via secure chat on the open trades screen to try solving disputes on their own. If that is not sufficient, a mediator can step in to help. The mediator will evaluate the situation and suggest a payout of trade funds. If both traders accept this suggestion, the payout transaction is completed and the trade is closed. If one or both traders do not agree to the mediator's suggested payout, they can request arbitration.The arbitrator will re-evaluate the situation and, if warranted, personally pay the trader back and request reimbursement for this payment from the Bisq DAO. -support.initialInfo=Please enter a description of your problem in the text field below. Add as much information as possible to speed up dispute resolution time.\n\nHere is a check list for information you should provide:\n\t● If you are the BTC buyer: Did you make the Fiat or Altcoin transfer? If so, did you click the 'payment started' button in the application?\n\t● If you are the BTC seller: Did you receive the Fiat or Altcoin payment? If so, did you click the 'payment received' button in the application?\n\t● Which version of Bisq are you using?\n\t● Which operating system are you using?\n\t● If you encountered an issue with failed transactions please consider switching to a new data directory.\n\t Sometimes the data directory gets corrupted and leads to strange bugs. \n\t See: https://bisq.wiki/Switching_to_a_new_data_directory\n\nPlease make yourself familiar with the basic rules for the dispute process:\n\t● You need to respond to the {0}''s requests within 2 days.\n\t● Mediators respond in between 2 days. Arbitrators respond in between 5 business days.\n\t● The maximum period for a dispute is 14 days.\n\t● You need to cooperate with the {1} and provide the information they request to make your case.\n\t● You accepted the rules outlined in the dispute document in the user agreement when you first started the application.\n\nYou can read more about the dispute process at: {2} +support.initialInfo=Please enter a description of your problem in the text field below. Add as much information as possible to speed up dispute resolution time.\n\nHere is a check list for information you should provide:\n\t● If you are the BTC buyer: Did you make the Fiat or Altcoin transfer? If so, did you click the 'payment started' button in the application?\n\t● If you are the BTC seller: Did you receive the Fiat or Altcoin payment? If so, did you click the 'payment received' button in the application?\n\t● Which version of Bisq are you using?\n\t● Which operating system are you using?\n\t● If you encountered an issue with failed transactions please consider switching to a new data directory.\n\t Sometimes the data directory gets corrupted and leads to strange bugs. \n\t See: https://bisq.wiki/Switching_to_a_new_data_directory\n\nPlease make yourself familiar with the basic rules for the dispute process:\n\t● You need to respond to the {0}''s requests within 2 days.\n\t● {1}\n\t● The maximum period for a dispute is 14 days.\n\t● You need to cooperate with the {2} and provide the information they request to make your case.\n\t● You accepted the rules outlined in the dispute document in the user agreement when you first started the application.\n\nYou can read more about the dispute process at: {3} +support.initialMediatorMsg=Mediators will generally reply to you within 24 hours.\n\t If you have not had a reply after 48 hours please feel free to reach out to your mediator on Keybase.\n\t Mediators usernames on Keybase are the same as their usernames within the Bisq app.\n\t Your mediator is: {0} +support.initialArbitratorMsg=Arbitrators will generally reply to you within 5 days.\n\t If you have not had a reply after 7 days please feel free to reach out to your arbitrator on Keybase.\n\t Arbitrators usernames on Keybase are the same as their usernames within the Bisq app.\n\t Your arbitrator is: {0} support.systemMsg=Системное сообщение: {0} support.youOpenedTicket=Вы запросили поддержку.\n\n{0}\n\nВерсия Bisq: {1} support.youOpenedDispute=Вы начали спор.\n\n{0}\n\nВерсия Bisq: {1} @@ -1064,6 +1067,8 @@ setting.preferences.dao.resyncFromResources.popup=After an application restart t setting.preferences.dao.resyncFromGenesis.popup=A resync from genesis transaction can take considerable time and CPU resources. Are you sure you want to do that? Mostly a resync from latest resource files is sufficient and much faster.\n\nIf you proceed, after an application restart the Bisq network governance data will be reloaded from the seed nodes and the BSQ consensus state will be rebuilt from the genesis transaction. setting.preferences.dao.resyncFromGenesis.resync=Resync from genesis and shutdown setting.preferences.dao.isDaoFullNode=Запустить Bisq в режиме полного узла ДАО +setting.preferences.dao.activated=DAO activated +setting.preferences.dao.activated.popup=The change will be applied after a restart setting.preferences.dao.rpcUser=Логин RPC setting.preferences.dao.rpcPw=Пароль RPC setting.preferences.dao.blockNotifyPort=Блокировать сообщающий порт @@ -1922,15 +1927,10 @@ dao.news.bisqDAO.title=ДАО BISQ dao.news.bisqDAO.description=Модель управления Bisq так же децентрализована и защищена от цензуры, как и сама биржа Bisq. Это возможно благодаря ДАО Bisq и токену BSQ. dao.news.bisqDAO.readMoreLink=Подробнее о ДАО Bisq -dao.news.pastContribution.title=ПОМОГАЛИ НАМ В ПРОШЛОМ? ЗАПРОСИТЕ BSQ -dao.news.pastContribution.description=Если вы помогли Bisq в прошлом, используйте свой адрес BSQ ниже и запросите участие в первоначальном распределении BSQ. -dao.news.pastContribution.yourAddress=Адрес вашего кошелька BSQ -dao.news.pastContribution.requestNow=Запросить - -dao.news.daoInfo.title=ЗАПУСТИТЬ ДАО BISQ В НАШЕЙ ТЕСТОВОЙ СЕТИ -dao.news.daoInfo.description=Основная сеть ДАО Bisq еще не запущена, но вы можете узнать о ней подробнее, запустив ДАО в тестовой сети. -dao.news.daoInfo.firstSection.title=1. Переключиться в режим тестовой сети ДАО -dao.news.daoInfo.firstSection.content=Переключитесь на тестовую сеть ДАО в настройках. +dao.news.daoInfo.title=ENABLE THE BISQ DAO +dao.news.daoInfo.description=To participate in the Bisq DAO and to use BSQ for discounted trading fees, you need to enable the DAO. When the DAO is enabled, Bisq downloads all missing blocks and verifies BSQ transactions. This verification process requires time, during which you may see Bisq use a lot of memory and processing power. This is normal. +dao.news.daoInfo.firstSection.title=1. Enable DAO +dao.news.daoInfo.firstSection.content=Enable the Bisq DAO and restart. dao.news.DAOOnTestnet.secondSection.title=2. Приобрести BSQ dao.news.DAOOnTestnet.secondSection.content=Запросите BSQ в Slack или купите BSQ в Bisq. dao.news.DAOOnTestnet.thirdSection.title=3. Принять участие в цикле голосования @@ -2679,6 +2679,7 @@ payment.secret=Секретный вопрос payment.answer=Ответ payment.wallet=Идентификатор кошелька payment.capitual.cap=CAP Code +payment.upi.virtualPaymentAddress=Virtual Payment Address # suppress inspection "UnusedProperty" payment.swift.headline=International SWIFT Wire Transfer @@ -2767,11 +2768,65 @@ payment.amazonGiftCard.upgrade=Amazon gift cards payment method requires the cou payment.account.amazonGiftCard.addCountryInfo={0}\nYour existing Amazon Gift Card account ({1}) does not have a Country specified.\nPlease enter your Amazon Gift Card Country to update your account data.\nThis will not affect your account age status. payment.amazonGiftCard.upgrade.headLine=Update Amazon Gift Card account -payment.swift.info=Carefully review the core guidelines for using SWIFT on Bisq:\n\n- fill all fields completely and accurately \n- buyer must send payment in currency specified by the offer maker \n- buyer must use the shared fee model (SHA) \n- buyer and seller may incur fees, so they should check their bank's fee schedules beforehand \n\nSWIFT is more sophisticated than other payment methods, so please take a moment to review full guidance on the wiki [HYPERLINK:https://bisq.wiki/SWIFT]. +payment.swift.info.account=Carefully review the core guidelines for using SWIFT on Bisq:\n\n- fill all fields completely and accurately \n- buyer must send payment in currency specified by the offer maker \n- buyer must use the shared fee model (SHA) \n- buyer and seller may incur fees, so they should check their bank's fee schedules beforehand \n\nSWIFT is more sophisticated than other payment methods, so please take a moment to review full guidance on the wiki [HYPERLINK:https://bisq.wiki/SWIFT]. payment.swift.info.buyer=To buy bitcoin with SWIFT, you must:\n\n- send payment in the currency specified by the offer maker \n- use the shared fee model (SHA) to send payment\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. -payment.swift.info.seller=SWIFT senders are required to use the shared payment model (SHA) to send payments.\n\nIf you receive a SWIFT payment that does not use SHA, open a mediation ticket.\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. +payment.swift.info.seller=SWIFT senders are required to use the shared fee model (SHA) to send payments.\n\nIf you receive a SWIFT payment that does not use SHA, open a mediation ticket.\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. + +payment.imps.info.account=Please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a maximum of Rs. 200,000 that can be sent per transaction. If you are trading over this amount multiple transactions will be needed. However be aware their is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nSome banks have different limits for their customers. +payment.imps.info.buyer=Please send payment only to the account details provided in Bisq.\n\nThe maximum trade size is Rs. 200,000 per transaction.\n\nIf your trade is over Rs. 200,000 you will have to make multiple transfers. However be aware their is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nPlease note some banks have different limits for their customers. +payment.imps.info.seller=If you intend to receive over Rs. 200,000 per trade you should expect the buyer to have to make multiple transfers. However be aware there is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nPlease note some banks have different limits for their customers. + +payment.neft.info.account=Please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a maximum of Rs. 50,000 that can be sent per transaction. If you are trading over this amount multiple transactions will be needed.\n\nPlease note some banks have different limits for their customers. +payment.neft.info.buyer=Please send payment only to the account details provided in Bisq.\n\nThe maximum trade size is Rs. 50,000 per transaction.\n\nIf your trade is over Rs. 50,000 you will have to make multiple transfers.\n\nPlease note some banks have different limits for their customers. +payment.neft.info.seller=If you intend to receive over Rs. 50,000 per trade you should expect the buyer to have to make multiple transfers.\n\nPlease note some banks have different limits for their customers. + +payment.paytm.info.account=Please make sure to include your email or phone number that matches your email or phone number in your PayTM account. \n\nWhen users set up a PayTM account with No KYC users are limited to: \n\n● Maximum of Rs. 5,000 can be sent per transaction.\n● Maximum of Rs. 10,000 can be held in someone's PayTM wallet.\n\nIf you intend to trade amount of over 5,000 per trade you will need to complete KYC with PayTM. With KYC users are limited to:\n\n● Maximum of Rs. 100,000 can be sent per transaction.\n● Maximum of Rs. 100,000 can be held in someone's PayTM wallet.\n\nUsers should also be aware of account limits. Trades above PayTM account limits will likely have to take place over more than one day, or, be cancelled. +payment.paytm.info.buyer=Please send payment only to the email address or phone number provided.\n\nIf you intend to trade amount of over Rs. 5,000 per trade you will need to complete KYC with PayTM.\n\nWith No KYC Rs. 5,000 can be sent per transaction.\n\nWith KYC users Rs. 100,000 can be sent per transaction. +payment.paytm.info.seller=If you intend to trade amount of over Rs. 5,000 per trade you will need to complete KYC with PayTM. With KYC users are limited to:\n\n● Maximum of Rs. 100,000 can be sent per transaction.\n● Maximum of Rs. 100,000 can be held in your PayTM wallet .\n\nUsers should also be aware of account limits. As a maximum of Rs. 100,000 can be held in your PayTM wallet please make sure you transfer out your rupees regularly. + +payment.rtgs.info.account=RTGS is for payments of large trades of Rs. 200,000 or over.\n\nWhen setting up your RTGS payment account please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). +payment.rtgs.info.buyer=Please send payment only to the account details provided in Bisq.\n\nPlease be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). +payment.rtgs.info.seller=Please be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). + +payment.upi.info.account=Please make sure to include your Virtual Payment Address (VPA) also called your UPI ID. The format for this is like an email ID: with the sign “@” in the middle. For example, your UPI ID could be “receiver’s_name@bank_name” or “phone_number@bank_name.” \n\nFor UPI there is a maximum limit of Rs. 100,000 that can be sent per transaction. \n\nIf you intend to trade amount of over Rs. 100,000 per trade it is likely trades will have to take place over multiple transfers. \n\nPlease note some banks have different limits for their customers. +payment.upi.info.buyer=Please send payment only to the VPA / UPI ID provided in Bisq. \n\nThe maximum trade size is Rs. 100,000 per transaction. \n\nIf your trade is over Rs. 100,000 you will have to make multiple transfers. \n\nPlease note some banks have different limits for their customers. +payment.upi.info.seller=If you intend to receive over Rs. 100,000 per trade you should expect the buyer to have to make multiple transfers. \n\nPlease note some banks have different limits for their customers. + +payment.celpay.info.account=Please make sure to include the email your Celsius account is registered to. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account.\n\nCelPay users are limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Buyers can send any matching currency stablecoin to the BTC Seller. +payment.celpay.info.buyer=Please send payment only to the email address provided by the BTC Seller by sending a payment link.\n\nCelPay is limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Buyers can send any matching currency stablecoin to the BTC Seller. +payment.celpay.info.seller=BTC Sellers should expect to receive payment via a secure payment link. Please make sure the email payment link contains the email address provided by the BTC Buyer.\n\nCelPay users are limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Sellers should expect to receive any matching currency stablecoin from the BTC Buyer. It is possible for the BTC Buyer to send any matching currency stablecoin. +payment.celpay.supportedCurrenciesForReceiver=Supported currencies (please note: all the currencies below are supported stable coins within the Celcius app. Trades are for stable coins, not fiat.) + +payment.nequi.info.account=Please make sure to include your phone number that is associated with your Nequi account.\n\nWhen users set up a Nequi account payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. +payment.nequi.info.buyer=Please send payment only to the phone number provided in the BTC Seller's Bisq account.\n\nWhen users set up a Nequi account, payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. +payment.nequi.info.seller=Please check that the payment received matches the phone number provided in the BTC Buyer's Bisq account.\n\nWhen users set up a Nequi account, payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. + +payment.bizum.info.account=To use Bizum you need a bank account (IBAN) in Spain and to be registered for the service.\n\nBizum can be used for trades between €0.50 and €1,000.\n\nThe maximum amount of transactions you can send/receive using Bizum is €2,000 Euros per day.\n\nBizum users can have a maximum of 150 operations per month.\n\nEach bank however may establish its own limits, within the above limits, for its clients.\n\nTraders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.bizum.info.buyer=Please send payment only to the BTC Seller's mobile phone number as provided in Bisq.\n\nThe maximum trade size is €1,000 per payment. The maximum amount of transactions you can send using Bizum is €2,000 Euros per day.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.bizum.info.seller=Please make sure your payment is received from the BTC Buyer's mobile phone number as provided in Bisq.\n\nThe maximum trade size is €1,000 per payment. The maximum amount of transactions you can receive using Bizum is €2,000 Euros per day.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. + +payment.pix.info.account=Please make sure to include your chosen Pix Key. There are four types of keys: CPF (Natural Persons Register) or CNPJ (National Registry of Legal Entities), e-mail address, telephone number or a random key generated by the system called a universally unique identifier (UUID). A different key must be used for each Pix account you have. Individuals can create up to five keys for each account they own.\n\nWhen trading on Bisq, BTC Buyers should use their Pix Keys as the payment description so that it is easy for the BTC Sellers to identify the payment as coming from themselves. +payment.pix.info.buyer=Please send payment only the Pix Key provided in the BTC Seller's Bisq account.\n\nPlease use your Pix Key as the payment reference so that it is easy for the BTC Seller to identify the payment as coming from yourself. +payment.pix.info.seller=Please check that the payment received description matches the Pix Key provided in the BTC Buyer's Bisq account. +payment.pix.key=Pix Key (CPF, CNPJ, Email, Phone number or UUID) + +payment.monese.info.account=Monese is a bank app for users of GBP, EUR and RON*. Monese allows users to send money to other Monese accounts instantly and for free in any supported currency.\n\n*To open a RON account in Monese, you need to either live in Romania or have Romanian citizenship.\n\nWhen setting up your Monese account in Bisq please make sure to include your name and phone number that matches your Monese account. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account. +payment.monese.info.buyer=Please send payment only to the phone number provided by the BTC Seller in their Bisq account. Please leave the payment description blank. +payment.monese.info.seller=BTC Sellers should expect to receive payment from the phone number / name shown in the BTC Buyer's Bisq account. + +payment.satispay.info.account=To use Satispay you need a bank account (IBAN) in Italy and to be registered for the service.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.satispay.info.buyer=Please send payment only to the BTC Seller's mobile phone number as provided in Bisq.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.satispay.info.seller=Please make sure your payment is received from the BTC Buyer's mobile phone number / name as provided in Bisq.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. + +payment.verse.info.account=Verse is a multiple currency payment method that can send and receive payment in EUR, SEK, HUF, DKK, PLN.\n\nWhen setting up your Verse account in Bisq please make sure to include the username that matches your username in your Verse account. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. +payment.verse.info.buyer=Please send payment only to the username provided by the BTC Seller in their Bisq account. Please leave the payment description blank.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. +payment.verse.info.seller=BTC Sellers should expect to receive payment from the username shown in the BTC Buyer's Bisq account.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. + +payment.strike.info.account=Please make sure to include your Strike username.\n\nIn Bisq, Strike is used for fiat to fiat payments only.\n\nPlease make sure you are aware of the Strike limits:\n\nUsers who have registered with only their email, name, and phone number have the following limits:\n\n● $100 maximum per deposit\n● $1,000 maximum total deposits per week\n● $100 maximum per payment\n\nUsers can increase their limits by providing Strike with more information. These users have the following limits:\n\n● $1,000 maximum per deposit\n● $1,000 maximum total deposits per week\n● $1,000 maximum per payment\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.strike.info.buyer=Please send payment only to the BTC Seller's Strike username as provided in Bisq.\n\nThe maximum trade size is $1,000 per payment.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.strike.info.seller=Please make sure your payment is received from the BTC Buyer's Strike username as provided in Bisq.\n\nThe maximum trade size is $1,000 per payment.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. payment.usPostalMoneyOrder.info=Trading using US Postal Money Orders (USPMO) on Bisq requires that you understand the following:\n\n- BTC buyers must write the BTC Seller’s name in both the Payer and the Payee’s fields & take a high-resolution photo of the USPMO and envelope with proof of tracking before sending.\n- BTC buyers must send the USPMO to the BTC seller with Delivery Confirmation.\n\nIn the event mediation is necessary, or if there is a trade dispute, you will be required to send the photos to the Bisq mediator or refund agent, together with the USPMO Serial Number, Post Office Number, and dollar amount, so they can verify the details on the US Post Office website.\n\nFailure to provide the required information to the Mediator or Arbitrator will result in losing the dispute case.\n\nIn all dispute cases, the USPMO sender bears 100% of the burden of responsibility in providing evidence/proof to the Mediator or Arbitrator.\n\nIf you do not understand these requirements, do not trade using USPMO on Bisq. @@ -2792,6 +2847,8 @@ payment.f2f.info='Face to Face' trades have different rules and come with differ payment.f2f.info.openURL=Открыть веб-страницу payment.f2f.offerbook.tooltip.countryAndCity=Country and city: {0} / {1} payment.f2f.offerbook.tooltip.extra=Дополнительная информация: {0} +payment.ifsc=IFS Code +payment.ifsc.validation=IFSC format: XXXX0999999 payment.japan.bank=Банк payment.japan.branch=Branch @@ -2800,7 +2857,7 @@ payment.japan.recipient=Имя payment.australia.payid=PayID payment.payid=PayID linked to financial institution. Like email address or mobile phone. payment.payid.info=A PayID like a phone number, email address or an Australian Business Number (ABN), that you can securely link to your bank, credit union or building society account. You need to have already created a PayID with your Australian financial institution. Both sending and receiving financial institutions must support PayID. For more information please check [HYPERLINK:https://payid.com.au/faqs/] -payment.amazonGiftCard.info=To pay with Amazon eGift Card, you will need to send an Amazon eGift Card to the BTC seller via your Amazon account. \n\nBisq will show the BTC seller''s email address or phone number where the gift card should be sent, and you must include the trade ID in the gift card''s message field. Please see the wiki [HYPERLINK:https://bisq.wiki/Amazon_eGift_card] for further details and best practices. \n\nThree important notes:\n- try to send gift cards with amounts of 100 USD or smaller, as Amazon is known to flag larger gift cards as fraudulent\n- try to use creative, believable text for the gift card''s message (e.g., "Happy birthday Susan!") along with the trade ID (and use trader chat to tell your trading peer the reference text you picked so they can verify your payment)\n- Amazon eGift Cards can only be redeemed on the Amazon website they were purchased on (e.g., a gift card purchased on amazon.it can only be redeemed on amazon.it) +payment.amazonGiftCard.info=To pay with Amazon eGift Card, you will need to send an Amazon eGift Card to the BTC seller via your Amazon account. \n\nPlease see the wiki [HYPERLINK:https://bisq.wiki/Amazon_eGift_card] for further details and best practices. \n\nThree important notes:\n- try to send gift cards with amounts of 100 USD or smaller, as Amazon is known to flag larger gift cards as fraudulent\n- try to use creative, believable text for the gift card''s message (e.g., "Happy birthday Susan!") and use trader chat to tell your trading peer the reference text you picked so they can verify your payment\n- Amazon eGift Cards can only be redeemed on the Amazon website they were purchased on (e.g., a gift card purchased on amazon.it can only be redeemed on amazon.it) # We use constants from the code so we do not use our normal naming convention # dynamic values are not recognized by IntelliJ @@ -2885,12 +2942,38 @@ PAYSERA=Paysera # suppress inspection "UnusedProperty" PAXUM=Paxum # suppress inspection "UnusedProperty" +NEFT=India/NEFT +# suppress inspection "UnusedProperty" +RTGS=India/RTGS +# suppress inspection "UnusedProperty" +IMPS=India/IMPS +# suppress inspection "UnusedProperty" +UPI=India/UPI +# suppress inspection "UnusedProperty" +PAYTM=India/PayTM +# suppress inspection "UnusedProperty" +NEQUI=Nequi +# suppress inspection "UnusedProperty" +BIZUM=Bizum +# suppress inspection "UnusedProperty" +PIX=Pix +# suppress inspection "UnusedProperty" AMAZON_GIFT_CARD=Amazon eGift Card # suppress inspection "UnusedProperty" BLOCK_CHAINS_INSTANT=Альткойны (мгновенно) # suppress inspection "UnusedProperty" CAPITUAL=Capitual # suppress inspection "UnusedProperty" +CELPAY=CelPay +# suppress inspection "UnusedProperty" +MONESE=Monese +# suppress inspection "UnusedProperty" +SATISPAY=Satispay +# suppress inspection "UnusedProperty" +VERSE=Verse +# suppress inspection "UnusedProperty" +STRIKE=Strike +# suppress inspection "UnusedProperty" SWIFT=SWIFT International Wire Transfer # Deprecated: Cannot be deleted as it would break old trade history entries @@ -2945,12 +3028,38 @@ PAYSERA_SHORT=Paysera # suppress inspection "UnusedProperty" PAXUM_SHORT=Paxum # suppress inspection "UnusedProperty" +NEFT_SHORT=NEFT +# suppress inspection "UnusedProperty" +RTGS_SHORT=RTGS +# suppress inspection "UnusedProperty" +IMPS_SHORT=IMPS +# suppress inspection "UnusedProperty" +UPI_SHORT=UPI +# suppress inspection "UnusedProperty" +PAYTM_SHORT=PayTM +# suppress inspection "UnusedProperty" +NEQUI_SHORT=Nequi +# suppress inspection "UnusedProperty" +BIZUM_SHORT=Bizum +# suppress inspection "UnusedProperty" +PIX_SHORT=Pix +# suppress inspection "UnusedProperty" AMAZON_GIFT_CARD_SHORT=Amazon eGift Card # suppress inspection "UnusedProperty" BLOCK_CHAINS_INSTANT_SHORT=Альткойны (мгновенно) # suppress inspection "UnusedProperty" CAPITUAL_SHORT=Capitual # suppress inspection "UnusedProperty" +CELPAY_SHORT=CelPay +# suppress inspection "UnusedProperty" +MONESE_SHORT=Monese +# suppress inspection "UnusedProperty" +SATISPAY_SHORT=Satispay +# suppress inspection "UnusedProperty" +VERSE_SHORT=Verse +# suppress inspection "UnusedProperty" +STRIKE_SHORT=Strike +# suppress inspection "UnusedProperty" SWIFT_SHORT=SWIFT # Deprecated: Cannot be deleted as it would break old trade history entries diff --git a/core/src/main/resources/i18n/displayStrings_th.properties b/core/src/main/resources/i18n/displayStrings_th.properties index dd076c8a874..e590d5c11e1 100644 --- a/core/src/main/resources/i18n/displayStrings_th.properties +++ b/core/src/main/resources/i18n/displayStrings_th.properties @@ -284,6 +284,7 @@ mainView.walletServiceErrorMsg.rejectedTxException=A transaction was rejected fr mainView.networkWarning.allConnectionsLost=คุณสูญเสียการเชื่อมต่อกับ {0} เครือข่าย peers\nบางทีคุณอาจขาดการเชื่อมต่ออินเทอร์เน็ตหรืออาจเป็นเพราะคอมพิวเตอร์ของคุณอยู่ในโหมดสแตนด์บาย mainView.networkWarning.localhostBitcoinLost=คุณสูญเสียการเชื่อมต่อไปยังโหนดเครือข่าย Bitcoin localhost (แม่ข่ายเฉพาะที่)\nโปรดรีสตาร์ทแอ็พพลิเคชัน Bisq เพื่อเชื่อมต่อโหนด Bitcoin อื่นหรือรีสตาร์ทโหนด Bitcoin localhost +mainView.networkWarning.clockWatcher=Your computer was asleep for {0} seconds. Standby mode has been known to cause trades to fail. In order to operate correctly Bisq requires that standby mode be disabled in your computer's settings. mainView.version.update=(การอัพเดตพร้อมใช้งาน) @@ -821,7 +822,7 @@ portfolio.pending.mediationResult.popup.alreadyAccepted=You've already accepted portfolio.pending.failedTrade.taker.missingTakerFeeTx=The taker fee transaction is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked and no trade fee has been paid. You can move this trade to failed trades. portfolio.pending.failedTrade.maker.missingTakerFeeTx=The peer's taker fee transaction is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked. Your offer is still available to other traders, so you have not lost the maker fee. You can move this trade to failed trades. portfolio.pending.failedTrade.missingDepositTx=The deposit transaction (the 2-of-2 multisig transaction) is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked but your trade fee has been paid. You can make a request to be reimbursed the trade fee here: [HYPERLINK:https://github.com/bisq-network/support/issues]\n\nFeel free to move this trade to failed trades. -portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the fiat or altcoin payment to the BTC seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] +portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the fiat or altcoin payment to the BTC seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing but funds have been locked in the deposit transaction.\n\nIf the buyer is also missing the delayed payout transaction, they will be instructed to NOT send the payment and open a mediation ticket instead. You should also open a mediation ticket with Cmd/Ctrl+o. \n\nIf the buyer has not sent payment yet, the mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). Otherwise the trade amount should go to the buyer. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.errorMsgSet=There was an error during trade protocol execution.\n\nError: {0}\n\nIt might be that this error is not critical, and the trade can be completed normally. If you are unsure, open a mediation ticket to get advice from Bisq mediators. \n\nIf the error was critical and the trade cannot be completed, you might have lost your trade fee. Request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.missingContract=The trade contract is not set.\n\nThe trade cannot be completed and you might have lost your trade fee. If so, you can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] @@ -996,7 +997,9 @@ support.buyerTaker=BTC ผู้ซื้อ / ผู้รับ support.sellerTaker=BTC ผู้ขาย / ผู้รับ support.backgroundInfo=Bisq is not a company, so it handles disputes differently.\n\nTraders can communicate within the application via secure chat on the open trades screen to try solving disputes on their own. If that is not sufficient, a mediator can step in to help. The mediator will evaluate the situation and suggest a payout of trade funds. If both traders accept this suggestion, the payout transaction is completed and the trade is closed. If one or both traders do not agree to the mediator's suggested payout, they can request arbitration.The arbitrator will re-evaluate the situation and, if warranted, personally pay the trader back and request reimbursement for this payment from the Bisq DAO. -support.initialInfo=Please enter a description of your problem in the text field below. Add as much information as possible to speed up dispute resolution time.\n\nHere is a check list for information you should provide:\n\t● If you are the BTC buyer: Did you make the Fiat or Altcoin transfer? If so, did you click the 'payment started' button in the application?\n\t● If you are the BTC seller: Did you receive the Fiat or Altcoin payment? If so, did you click the 'payment received' button in the application?\n\t● Which version of Bisq are you using?\n\t● Which operating system are you using?\n\t● If you encountered an issue with failed transactions please consider switching to a new data directory.\n\t Sometimes the data directory gets corrupted and leads to strange bugs. \n\t See: https://bisq.wiki/Switching_to_a_new_data_directory\n\nPlease make yourself familiar with the basic rules for the dispute process:\n\t● You need to respond to the {0}''s requests within 2 days.\n\t● Mediators respond in between 2 days. Arbitrators respond in between 5 business days.\n\t● The maximum period for a dispute is 14 days.\n\t● You need to cooperate with the {1} and provide the information they request to make your case.\n\t● You accepted the rules outlined in the dispute document in the user agreement when you first started the application.\n\nYou can read more about the dispute process at: {2} +support.initialInfo=Please enter a description of your problem in the text field below. Add as much information as possible to speed up dispute resolution time.\n\nHere is a check list for information you should provide:\n\t● If you are the BTC buyer: Did you make the Fiat or Altcoin transfer? If so, did you click the 'payment started' button in the application?\n\t● If you are the BTC seller: Did you receive the Fiat or Altcoin payment? If so, did you click the 'payment received' button in the application?\n\t● Which version of Bisq are you using?\n\t● Which operating system are you using?\n\t● If you encountered an issue with failed transactions please consider switching to a new data directory.\n\t Sometimes the data directory gets corrupted and leads to strange bugs. \n\t See: https://bisq.wiki/Switching_to_a_new_data_directory\n\nPlease make yourself familiar with the basic rules for the dispute process:\n\t● You need to respond to the {0}''s requests within 2 days.\n\t● {1}\n\t● The maximum period for a dispute is 14 days.\n\t● You need to cooperate with the {2} and provide the information they request to make your case.\n\t● You accepted the rules outlined in the dispute document in the user agreement when you first started the application.\n\nYou can read more about the dispute process at: {3} +support.initialMediatorMsg=Mediators will generally reply to you within 24 hours.\n\t If you have not had a reply after 48 hours please feel free to reach out to your mediator on Keybase.\n\t Mediators usernames on Keybase are the same as their usernames within the Bisq app.\n\t Your mediator is: {0} +support.initialArbitratorMsg=Arbitrators will generally reply to you within 5 days.\n\t If you have not had a reply after 7 days please feel free to reach out to your arbitrator on Keybase.\n\t Arbitrators usernames on Keybase are the same as their usernames within the Bisq app.\n\t Your arbitrator is: {0} support.systemMsg=ระบบข้อความ: {0} support.youOpenedTicket=You opened a request for support.\n\n{0}\n\nBisq version: {1} support.youOpenedDispute=You opened a request for a dispute.\n\n{0}\n\nBisq version: {1} @@ -1064,6 +1067,8 @@ setting.preferences.dao.resyncFromResources.popup=After an application restart t setting.preferences.dao.resyncFromGenesis.popup=A resync from genesis transaction can take considerable time and CPU resources. Are you sure you want to do that? Mostly a resync from latest resource files is sufficient and much faster.\n\nIf you proceed, after an application restart the Bisq network governance data will be reloaded from the seed nodes and the BSQ consensus state will be rebuilt from the genesis transaction. setting.preferences.dao.resyncFromGenesis.resync=Resync from genesis and shutdown setting.preferences.dao.isDaoFullNode=ใช้งาน Bisq ในแบบโหนด DAO full node +setting.preferences.dao.activated=DAO activated +setting.preferences.dao.activated.popup=The change will be applied after a restart setting.preferences.dao.rpcUser=ชื่อผู้ใช้ RPC setting.preferences.dao.rpcPw=รหัส RPC setting.preferences.dao.blockNotifyPort=Block notify port @@ -1922,15 +1927,10 @@ dao.news.bisqDAO.title=THE BISQ DAO dao.news.bisqDAO.description=Just as the Bisq exchange is decentralized and censorship-resistant, so is its governance model - and the Bisq DAO and BSQ token are the tools that make it possible. dao.news.bisqDAO.readMoreLink=Learn More About the Bisq DAO -dao.news.pastContribution.title=MADE PAST CONTRIBUTIONS? REQUEST BSQ -dao.news.pastContribution.description=If you have contributed to Bisq please use the BSQ address below and make a request for taking part of the BSQ genesis distribution. -dao.news.pastContribution.yourAddress=Your BSQ Wallet Address -dao.news.pastContribution.requestNow=Request now - -dao.news.daoInfo.title=RUN THE BISQ DAO ON OUR TESTNET -dao.news.daoInfo.description=The mainnet Bisq DAO is not launched yet but you can learn about the Bisq DAO by running it on our testnet. -dao.news.daoInfo.firstSection.title=1. Switch to DAO Testnet Mode -dao.news.daoInfo.firstSection.content=Switch to DAO Testnet from the Settings screen. +dao.news.daoInfo.title=ENABLE THE BISQ DAO +dao.news.daoInfo.description=To participate in the Bisq DAO and to use BSQ for discounted trading fees, you need to enable the DAO. When the DAO is enabled, Bisq downloads all missing blocks and verifies BSQ transactions. This verification process requires time, during which you may see Bisq use a lot of memory and processing power. This is normal. +dao.news.daoInfo.firstSection.title=1. Enable DAO +dao.news.daoInfo.firstSection.content=Enable the Bisq DAO and restart. dao.news.DAOOnTestnet.secondSection.title=2. Acquire Some BSQ dao.news.DAOOnTestnet.secondSection.content=Request BSQ on Slack or Buy BSQ on Bisq. dao.news.DAOOnTestnet.thirdSection.title=3. Participate in a Voting Cycle @@ -2679,6 +2679,7 @@ payment.secret=คำถามลับ payment.answer=คำตอบ payment.wallet=ID กระเป๋าสตางค์ payment.capitual.cap=CAP Code +payment.upi.virtualPaymentAddress=Virtual Payment Address # suppress inspection "UnusedProperty" payment.swift.headline=International SWIFT Wire Transfer @@ -2767,11 +2768,65 @@ payment.amazonGiftCard.upgrade=Amazon gift cards payment method requires the cou payment.account.amazonGiftCard.addCountryInfo={0}\nYour existing Amazon Gift Card account ({1}) does not have a Country specified.\nPlease enter your Amazon Gift Card Country to update your account data.\nThis will not affect your account age status. payment.amazonGiftCard.upgrade.headLine=Update Amazon Gift Card account -payment.swift.info=Carefully review the core guidelines for using SWIFT on Bisq:\n\n- fill all fields completely and accurately \n- buyer must send payment in currency specified by the offer maker \n- buyer must use the shared fee model (SHA) \n- buyer and seller may incur fees, so they should check their bank's fee schedules beforehand \n\nSWIFT is more sophisticated than other payment methods, so please take a moment to review full guidance on the wiki [HYPERLINK:https://bisq.wiki/SWIFT]. +payment.swift.info.account=Carefully review the core guidelines for using SWIFT on Bisq:\n\n- fill all fields completely and accurately \n- buyer must send payment in currency specified by the offer maker \n- buyer must use the shared fee model (SHA) \n- buyer and seller may incur fees, so they should check their bank's fee schedules beforehand \n\nSWIFT is more sophisticated than other payment methods, so please take a moment to review full guidance on the wiki [HYPERLINK:https://bisq.wiki/SWIFT]. payment.swift.info.buyer=To buy bitcoin with SWIFT, you must:\n\n- send payment in the currency specified by the offer maker \n- use the shared fee model (SHA) to send payment\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. -payment.swift.info.seller=SWIFT senders are required to use the shared payment model (SHA) to send payments.\n\nIf you receive a SWIFT payment that does not use SHA, open a mediation ticket.\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. +payment.swift.info.seller=SWIFT senders are required to use the shared fee model (SHA) to send payments.\n\nIf you receive a SWIFT payment that does not use SHA, open a mediation ticket.\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. + +payment.imps.info.account=Please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a maximum of Rs. 200,000 that can be sent per transaction. If you are trading over this amount multiple transactions will be needed. However be aware their is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nSome banks have different limits for their customers. +payment.imps.info.buyer=Please send payment only to the account details provided in Bisq.\n\nThe maximum trade size is Rs. 200,000 per transaction.\n\nIf your trade is over Rs. 200,000 you will have to make multiple transfers. However be aware their is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nPlease note some banks have different limits for their customers. +payment.imps.info.seller=If you intend to receive over Rs. 200,000 per trade you should expect the buyer to have to make multiple transfers. However be aware there is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nPlease note some banks have different limits for their customers. + +payment.neft.info.account=Please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a maximum of Rs. 50,000 that can be sent per transaction. If you are trading over this amount multiple transactions will be needed.\n\nPlease note some banks have different limits for their customers. +payment.neft.info.buyer=Please send payment only to the account details provided in Bisq.\n\nThe maximum trade size is Rs. 50,000 per transaction.\n\nIf your trade is over Rs. 50,000 you will have to make multiple transfers.\n\nPlease note some banks have different limits for their customers. +payment.neft.info.seller=If you intend to receive over Rs. 50,000 per trade you should expect the buyer to have to make multiple transfers.\n\nPlease note some banks have different limits for their customers. + +payment.paytm.info.account=Please make sure to include your email or phone number that matches your email or phone number in your PayTM account. \n\nWhen users set up a PayTM account with No KYC users are limited to: \n\n● Maximum of Rs. 5,000 can be sent per transaction.\n● Maximum of Rs. 10,000 can be held in someone's PayTM wallet.\n\nIf you intend to trade amount of over 5,000 per trade you will need to complete KYC with PayTM. With KYC users are limited to:\n\n● Maximum of Rs. 100,000 can be sent per transaction.\n● Maximum of Rs. 100,000 can be held in someone's PayTM wallet.\n\nUsers should also be aware of account limits. Trades above PayTM account limits will likely have to take place over more than one day, or, be cancelled. +payment.paytm.info.buyer=Please send payment only to the email address or phone number provided.\n\nIf you intend to trade amount of over Rs. 5,000 per trade you will need to complete KYC with PayTM.\n\nWith No KYC Rs. 5,000 can be sent per transaction.\n\nWith KYC users Rs. 100,000 can be sent per transaction. +payment.paytm.info.seller=If you intend to trade amount of over Rs. 5,000 per trade you will need to complete KYC with PayTM. With KYC users are limited to:\n\n● Maximum of Rs. 100,000 can be sent per transaction.\n● Maximum of Rs. 100,000 can be held in your PayTM wallet .\n\nUsers should also be aware of account limits. As a maximum of Rs. 100,000 can be held in your PayTM wallet please make sure you transfer out your rupees regularly. + +payment.rtgs.info.account=RTGS is for payments of large trades of Rs. 200,000 or over.\n\nWhen setting up your RTGS payment account please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). +payment.rtgs.info.buyer=Please send payment only to the account details provided in Bisq.\n\nPlease be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). +payment.rtgs.info.seller=Please be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). + +payment.upi.info.account=Please make sure to include your Virtual Payment Address (VPA) also called your UPI ID. The format for this is like an email ID: with the sign “@” in the middle. For example, your UPI ID could be “receiver’s_name@bank_name” or “phone_number@bank_name.” \n\nFor UPI there is a maximum limit of Rs. 100,000 that can be sent per transaction. \n\nIf you intend to trade amount of over Rs. 100,000 per trade it is likely trades will have to take place over multiple transfers. \n\nPlease note some banks have different limits for their customers. +payment.upi.info.buyer=Please send payment only to the VPA / UPI ID provided in Bisq. \n\nThe maximum trade size is Rs. 100,000 per transaction. \n\nIf your trade is over Rs. 100,000 you will have to make multiple transfers. \n\nPlease note some banks have different limits for their customers. +payment.upi.info.seller=If you intend to receive over Rs. 100,000 per trade you should expect the buyer to have to make multiple transfers. \n\nPlease note some banks have different limits for their customers. + +payment.celpay.info.account=Please make sure to include the email your Celsius account is registered to. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account.\n\nCelPay users are limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Buyers can send any matching currency stablecoin to the BTC Seller. +payment.celpay.info.buyer=Please send payment only to the email address provided by the BTC Seller by sending a payment link.\n\nCelPay is limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Buyers can send any matching currency stablecoin to the BTC Seller. +payment.celpay.info.seller=BTC Sellers should expect to receive payment via a secure payment link. Please make sure the email payment link contains the email address provided by the BTC Buyer.\n\nCelPay users are limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Sellers should expect to receive any matching currency stablecoin from the BTC Buyer. It is possible for the BTC Buyer to send any matching currency stablecoin. +payment.celpay.supportedCurrenciesForReceiver=Supported currencies (please note: all the currencies below are supported stable coins within the Celcius app. Trades are for stable coins, not fiat.) + +payment.nequi.info.account=Please make sure to include your phone number that is associated with your Nequi account.\n\nWhen users set up a Nequi account payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. +payment.nequi.info.buyer=Please send payment only to the phone number provided in the BTC Seller's Bisq account.\n\nWhen users set up a Nequi account, payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. +payment.nequi.info.seller=Please check that the payment received matches the phone number provided in the BTC Buyer's Bisq account.\n\nWhen users set up a Nequi account, payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. + +payment.bizum.info.account=To use Bizum you need a bank account (IBAN) in Spain and to be registered for the service.\n\nBizum can be used for trades between €0.50 and €1,000.\n\nThe maximum amount of transactions you can send/receive using Bizum is €2,000 Euros per day.\n\nBizum users can have a maximum of 150 operations per month.\n\nEach bank however may establish its own limits, within the above limits, for its clients.\n\nTraders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.bizum.info.buyer=Please send payment only to the BTC Seller's mobile phone number as provided in Bisq.\n\nThe maximum trade size is €1,000 per payment. The maximum amount of transactions you can send using Bizum is €2,000 Euros per day.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.bizum.info.seller=Please make sure your payment is received from the BTC Buyer's mobile phone number as provided in Bisq.\n\nThe maximum trade size is €1,000 per payment. The maximum amount of transactions you can receive using Bizum is €2,000 Euros per day.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. + +payment.pix.info.account=Please make sure to include your chosen Pix Key. There are four types of keys: CPF (Natural Persons Register) or CNPJ (National Registry of Legal Entities), e-mail address, telephone number or a random key generated by the system called a universally unique identifier (UUID). A different key must be used for each Pix account you have. Individuals can create up to five keys for each account they own.\n\nWhen trading on Bisq, BTC Buyers should use their Pix Keys as the payment description so that it is easy for the BTC Sellers to identify the payment as coming from themselves. +payment.pix.info.buyer=Please send payment only the Pix Key provided in the BTC Seller's Bisq account.\n\nPlease use your Pix Key as the payment reference so that it is easy for the BTC Seller to identify the payment as coming from yourself. +payment.pix.info.seller=Please check that the payment received description matches the Pix Key provided in the BTC Buyer's Bisq account. +payment.pix.key=Pix Key (CPF, CNPJ, Email, Phone number or UUID) + +payment.monese.info.account=Monese is a bank app for users of GBP, EUR and RON*. Monese allows users to send money to other Monese accounts instantly and for free in any supported currency.\n\n*To open a RON account in Monese, you need to either live in Romania or have Romanian citizenship.\n\nWhen setting up your Monese account in Bisq please make sure to include your name and phone number that matches your Monese account. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account. +payment.monese.info.buyer=Please send payment only to the phone number provided by the BTC Seller in their Bisq account. Please leave the payment description blank. +payment.monese.info.seller=BTC Sellers should expect to receive payment from the phone number / name shown in the BTC Buyer's Bisq account. + +payment.satispay.info.account=To use Satispay you need a bank account (IBAN) in Italy and to be registered for the service.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.satispay.info.buyer=Please send payment only to the BTC Seller's mobile phone number as provided in Bisq.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.satispay.info.seller=Please make sure your payment is received from the BTC Buyer's mobile phone number / name as provided in Bisq.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. + +payment.verse.info.account=Verse is a multiple currency payment method that can send and receive payment in EUR, SEK, HUF, DKK, PLN.\n\nWhen setting up your Verse account in Bisq please make sure to include the username that matches your username in your Verse account. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. +payment.verse.info.buyer=Please send payment only to the username provided by the BTC Seller in their Bisq account. Please leave the payment description blank.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. +payment.verse.info.seller=BTC Sellers should expect to receive payment from the username shown in the BTC Buyer's Bisq account.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. + +payment.strike.info.account=Please make sure to include your Strike username.\n\nIn Bisq, Strike is used for fiat to fiat payments only.\n\nPlease make sure you are aware of the Strike limits:\n\nUsers who have registered with only their email, name, and phone number have the following limits:\n\n● $100 maximum per deposit\n● $1,000 maximum total deposits per week\n● $100 maximum per payment\n\nUsers can increase their limits by providing Strike with more information. These users have the following limits:\n\n● $1,000 maximum per deposit\n● $1,000 maximum total deposits per week\n● $1,000 maximum per payment\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.strike.info.buyer=Please send payment only to the BTC Seller's Strike username as provided in Bisq.\n\nThe maximum trade size is $1,000 per payment.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.strike.info.seller=Please make sure your payment is received from the BTC Buyer's Strike username as provided in Bisq.\n\nThe maximum trade size is $1,000 per payment.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. payment.usPostalMoneyOrder.info=Trading using US Postal Money Orders (USPMO) on Bisq requires that you understand the following:\n\n- BTC buyers must write the BTC Seller’s name in both the Payer and the Payee’s fields & take a high-resolution photo of the USPMO and envelope with proof of tracking before sending.\n- BTC buyers must send the USPMO to the BTC seller with Delivery Confirmation.\n\nIn the event mediation is necessary, or if there is a trade dispute, you will be required to send the photos to the Bisq mediator or refund agent, together with the USPMO Serial Number, Post Office Number, and dollar amount, so they can verify the details on the US Post Office website.\n\nFailure to provide the required information to the Mediator or Arbitrator will result in losing the dispute case.\n\nIn all dispute cases, the USPMO sender bears 100% of the burden of responsibility in providing evidence/proof to the Mediator or Arbitrator.\n\nIf you do not understand these requirements, do not trade using USPMO on Bisq. @@ -2792,6 +2847,8 @@ payment.f2f.info='Face to Face' trades have different rules and come with differ payment.f2f.info.openURL=เปิดหน้าเว็บ payment.f2f.offerbook.tooltip.countryAndCity=Country and city: {0} / {1} payment.f2f.offerbook.tooltip.extra=ข้อมูลเพิ่มเติม: {0} +payment.ifsc=IFS Code +payment.ifsc.validation=IFSC format: XXXX0999999 payment.japan.bank=ธนาคาร payment.japan.branch=Branch @@ -2800,7 +2857,7 @@ payment.japan.recipient=ชื่อ payment.australia.payid=PayID payment.payid=PayID linked to financial institution. Like email address or mobile phone. payment.payid.info=A PayID like a phone number, email address or an Australian Business Number (ABN), that you can securely link to your bank, credit union or building society account. You need to have already created a PayID with your Australian financial institution. Both sending and receiving financial institutions must support PayID. For more information please check [HYPERLINK:https://payid.com.au/faqs/] -payment.amazonGiftCard.info=To pay with Amazon eGift Card, you will need to send an Amazon eGift Card to the BTC seller via your Amazon account. \n\nBisq will show the BTC seller''s email address or phone number where the gift card should be sent, and you must include the trade ID in the gift card''s message field. Please see the wiki [HYPERLINK:https://bisq.wiki/Amazon_eGift_card] for further details and best practices. \n\nThree important notes:\n- try to send gift cards with amounts of 100 USD or smaller, as Amazon is known to flag larger gift cards as fraudulent\n- try to use creative, believable text for the gift card''s message (e.g., "Happy birthday Susan!") along with the trade ID (and use trader chat to tell your trading peer the reference text you picked so they can verify your payment)\n- Amazon eGift Cards can only be redeemed on the Amazon website they were purchased on (e.g., a gift card purchased on amazon.it can only be redeemed on amazon.it) +payment.amazonGiftCard.info=To pay with Amazon eGift Card, you will need to send an Amazon eGift Card to the BTC seller via your Amazon account. \n\nPlease see the wiki [HYPERLINK:https://bisq.wiki/Amazon_eGift_card] for further details and best practices. \n\nThree important notes:\n- try to send gift cards with amounts of 100 USD or smaller, as Amazon is known to flag larger gift cards as fraudulent\n- try to use creative, believable text for the gift card''s message (e.g., "Happy birthday Susan!") and use trader chat to tell your trading peer the reference text you picked so they can verify your payment\n- Amazon eGift Cards can only be redeemed on the Amazon website they were purchased on (e.g., a gift card purchased on amazon.it can only be redeemed on amazon.it) # We use constants from the code so we do not use our normal naming convention # dynamic values are not recognized by IntelliJ @@ -2885,12 +2942,38 @@ PAYSERA=Paysera # suppress inspection "UnusedProperty" PAXUM=Paxum # suppress inspection "UnusedProperty" +NEFT=India/NEFT +# suppress inspection "UnusedProperty" +RTGS=India/RTGS +# suppress inspection "UnusedProperty" +IMPS=India/IMPS +# suppress inspection "UnusedProperty" +UPI=India/UPI +# suppress inspection "UnusedProperty" +PAYTM=India/PayTM +# suppress inspection "UnusedProperty" +NEQUI=Nequi +# suppress inspection "UnusedProperty" +BIZUM=Bizum +# suppress inspection "UnusedProperty" +PIX=Pix +# suppress inspection "UnusedProperty" AMAZON_GIFT_CARD=Amazon eGift Card # suppress inspection "UnusedProperty" BLOCK_CHAINS_INSTANT=Altcoins Instant # suppress inspection "UnusedProperty" CAPITUAL=Capitual # suppress inspection "UnusedProperty" +CELPAY=CelPay +# suppress inspection "UnusedProperty" +MONESE=Monese +# suppress inspection "UnusedProperty" +SATISPAY=Satispay +# suppress inspection "UnusedProperty" +VERSE=Verse +# suppress inspection "UnusedProperty" +STRIKE=Strike +# suppress inspection "UnusedProperty" SWIFT=SWIFT International Wire Transfer # Deprecated: Cannot be deleted as it would break old trade history entries @@ -2945,12 +3028,38 @@ PAYSERA_SHORT=Paysera # suppress inspection "UnusedProperty" PAXUM_SHORT=Paxum # suppress inspection "UnusedProperty" +NEFT_SHORT=NEFT +# suppress inspection "UnusedProperty" +RTGS_SHORT=RTGS +# suppress inspection "UnusedProperty" +IMPS_SHORT=IMPS +# suppress inspection "UnusedProperty" +UPI_SHORT=UPI +# suppress inspection "UnusedProperty" +PAYTM_SHORT=PayTM +# suppress inspection "UnusedProperty" +NEQUI_SHORT=Nequi +# suppress inspection "UnusedProperty" +BIZUM_SHORT=Bizum +# suppress inspection "UnusedProperty" +PIX_SHORT=Pix +# suppress inspection "UnusedProperty" AMAZON_GIFT_CARD_SHORT=Amazon eGift Card # suppress inspection "UnusedProperty" BLOCK_CHAINS_INSTANT_SHORT=Altcoins Instant # suppress inspection "UnusedProperty" CAPITUAL_SHORT=Capitual # suppress inspection "UnusedProperty" +CELPAY_SHORT=CelPay +# suppress inspection "UnusedProperty" +MONESE_SHORT=Monese +# suppress inspection "UnusedProperty" +SATISPAY_SHORT=Satispay +# suppress inspection "UnusedProperty" +VERSE_SHORT=Verse +# suppress inspection "UnusedProperty" +STRIKE_SHORT=Strike +# suppress inspection "UnusedProperty" SWIFT_SHORT=SWIFT # Deprecated: Cannot be deleted as it would break old trade history entries diff --git a/core/src/main/resources/i18n/displayStrings_vi.properties b/core/src/main/resources/i18n/displayStrings_vi.properties index 5ac5afa933b..9ca0ef9ec90 100644 --- a/core/src/main/resources/i18n/displayStrings_vi.properties +++ b/core/src/main/resources/i18n/displayStrings_vi.properties @@ -284,6 +284,7 @@ mainView.walletServiceErrorMsg.rejectedTxException=A transaction was rejected fr mainView.networkWarning.allConnectionsLost=Mất kết nối tới tất cả mạng ngang hàng {0}.\nCó thể bạn mất kết nối internet hoặc máy tính đang ở chế độ standby. mainView.networkWarning.localhostBitcoinLost=Mất kết nối tới nút Bitcoin máy chủ nội bộ.\nVui lòng khởi động lại ứng dụng Bisq để nối với nút Bitcoin khác hoặc khởi động lại nút Bitcoin máy chủ nội bộ. +mainView.networkWarning.clockWatcher=Your computer was asleep for {0} seconds. Standby mode has been known to cause trades to fail. In order to operate correctly Bisq requires that standby mode be disabled in your computer's settings. mainView.version.update=(Có cập nhật) @@ -821,7 +822,7 @@ portfolio.pending.mediationResult.popup.alreadyAccepted=You've already accepted portfolio.pending.failedTrade.taker.missingTakerFeeTx=The taker fee transaction is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked and no trade fee has been paid. You can move this trade to failed trades. portfolio.pending.failedTrade.maker.missingTakerFeeTx=The peer's taker fee transaction is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked. Your offer is still available to other traders, so you have not lost the maker fee. You can move this trade to failed trades. portfolio.pending.failedTrade.missingDepositTx=The deposit transaction (the 2-of-2 multisig transaction) is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked but your trade fee has been paid. You can make a request to be reimbursed the trade fee here: [HYPERLINK:https://github.com/bisq-network/support/issues]\n\nFeel free to move this trade to failed trades. -portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the fiat or altcoin payment to the BTC seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] +portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the fiat or altcoin payment to the BTC seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing but funds have been locked in the deposit transaction.\n\nIf the buyer is also missing the delayed payout transaction, they will be instructed to NOT send the payment and open a mediation ticket instead. You should also open a mediation ticket with Cmd/Ctrl+o. \n\nIf the buyer has not sent payment yet, the mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). Otherwise the trade amount should go to the buyer. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.errorMsgSet=There was an error during trade protocol execution.\n\nError: {0}\n\nIt might be that this error is not critical, and the trade can be completed normally. If you are unsure, open a mediation ticket to get advice from Bisq mediators. \n\nIf the error was critical and the trade cannot be completed, you might have lost your trade fee. Request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.missingContract=The trade contract is not set.\n\nThe trade cannot be completed and you might have lost your trade fee. If so, you can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] @@ -928,7 +929,7 @@ funds.tx.daoTxFee=Phí đào cho giao dịch BSQ funds.tx.reimbursementRequestTxFee=Yêu cầu bồi hoàn funds.tx.compensationRequestTxFee=Yêu cầu bồi thường funds.tx.dustAttackTx=Số dư nhỏ đã nhận -funds.tx.dustAttackTx.popup=Giao dịch này đang gửi một lượng BTC rất nhỏ vào ví của bạn và có thể đây là cách các công ty phân tích chuỗi đang tìm cách theo dõi ví của bạn.\nNếu bạn sử dụng đầu ra giao dịch đó cho một giao dịch chi tiêu, họ sẽ phát hiện ra rằng rất có thể bạn cũng là người sở hửu cái ví kia (nhập coin). \n\nĐể bảo vệ quyền riêng tư của bạn, ví Bisq sẽ bỏ qua các đầu ra có số dư nhỏ dành cho mục đích chi tiêu cũng như hiển thị số dư. Bạn có thể thiết lập ngưỡng khi một đầu ra được cho là có số dư nhỏ trong phần cài đặt. +funds.tx.dustAttackTx.popup=Giao dịch này đang gửi một lượng BTC rất nhỏ vào ví của bạn và có thể đây là cách các công ty phân tích chuỗi đang tìm cách theo dõi ví của bạn.\nNếu bạn sử dụng đầu ra giao dịch đó cho một giao dịch chi tiêu, họ sẽ phát hiện ra rằng rất có thể bạn cũng là người sở hửu cái ví kia (nhập coin). \n\nĐể bảo vệ quyền riêng tư của bạn, ví Bisq sẽ bỏ qua các đầu ra có số dư nhỏ dành cho mục đích chi tiêu cũng như hiển thị số dư. Bạn có thể thiết lập ngưỡng khi một đầu ra được cho là có số dư nhỏ trong phần cài đặt. #################################################################### # Support @@ -996,7 +997,9 @@ support.buyerTaker=Người mua BTC/Người nhận support.sellerTaker=Người bán BTC/Người nhận support.backgroundInfo=Bisq is not a company, so it handles disputes differently.\n\nTraders can communicate within the application via secure chat on the open trades screen to try solving disputes on their own. If that is not sufficient, a mediator can step in to help. The mediator will evaluate the situation and suggest a payout of trade funds. If both traders accept this suggestion, the payout transaction is completed and the trade is closed. If one or both traders do not agree to the mediator's suggested payout, they can request arbitration.The arbitrator will re-evaluate the situation and, if warranted, personally pay the trader back and request reimbursement for this payment from the Bisq DAO. -support.initialInfo=Please enter a description of your problem in the text field below. Add as much information as possible to speed up dispute resolution time.\n\nHere is a check list for information you should provide:\n\t● If you are the BTC buyer: Did you make the Fiat or Altcoin transfer? If so, did you click the 'payment started' button in the application?\n\t● If you are the BTC seller: Did you receive the Fiat or Altcoin payment? If so, did you click the 'payment received' button in the application?\n\t● Which version of Bisq are you using?\n\t● Which operating system are you using?\n\t● If you encountered an issue with failed transactions please consider switching to a new data directory.\n\t Sometimes the data directory gets corrupted and leads to strange bugs. \n\t See: https://bisq.wiki/Switching_to_a_new_data_directory\n\nPlease make yourself familiar with the basic rules for the dispute process:\n\t● You need to respond to the {0}''s requests within 2 days.\n\t● Mediators respond in between 2 days. Arbitrators respond in between 5 business days.\n\t● The maximum period for a dispute is 14 days.\n\t● You need to cooperate with the {1} and provide the information they request to make your case.\n\t● You accepted the rules outlined in the dispute document in the user agreement when you first started the application.\n\nYou can read more about the dispute process at: {2} +support.initialInfo=Please enter a description of your problem in the text field below. Add as much information as possible to speed up dispute resolution time.\n\nHere is a check list for information you should provide:\n\t● If you are the BTC buyer: Did you make the Fiat or Altcoin transfer? If so, did you click the 'payment started' button in the application?\n\t● If you are the BTC seller: Did you receive the Fiat or Altcoin payment? If so, did you click the 'payment received' button in the application?\n\t● Which version of Bisq are you using?\n\t● Which operating system are you using?\n\t● If you encountered an issue with failed transactions please consider switching to a new data directory.\n\t Sometimes the data directory gets corrupted and leads to strange bugs. \n\t See: https://bisq.wiki/Switching_to_a_new_data_directory\n\nPlease make yourself familiar with the basic rules for the dispute process:\n\t● You need to respond to the {0}''s requests within 2 days.\n\t● {1}\n\t● The maximum period for a dispute is 14 days.\n\t● You need to cooperate with the {2} and provide the information they request to make your case.\n\t● You accepted the rules outlined in the dispute document in the user agreement when you first started the application.\n\nYou can read more about the dispute process at: {3} +support.initialMediatorMsg=Mediators will generally reply to you within 24 hours.\n\t If you have not had a reply after 48 hours please feel free to reach out to your mediator on Keybase.\n\t Mediators usernames on Keybase are the same as their usernames within the Bisq app.\n\t Your mediator is: {0} +support.initialArbitratorMsg=Arbitrators will generally reply to you within 5 days.\n\t If you have not had a reply after 7 days please feel free to reach out to your arbitrator on Keybase.\n\t Arbitrators usernames on Keybase are the same as their usernames within the Bisq app.\n\t Your arbitrator is: {0} support.systemMsg=Tin nhắn hệ thống: {0} support.youOpenedTicket=Bạn đã mở yêu cầu hỗ trợ.\n\n{0}\n\nPhiên bản Bisq: {1} support.youOpenedDispute=Bạn đã mở yêu cầu giải quyết tranh chấp.\n\n{0}\n\nPhiên bản Bisq: {1} @@ -1064,6 +1067,8 @@ setting.preferences.dao.resyncFromResources.popup=After an application restart t setting.preferences.dao.resyncFromGenesis.popup=A resync from genesis transaction can take considerable time and CPU resources. Are you sure you want to do that? Mostly a resync from latest resource files is sufficient and much faster.\n\nIf you proceed, after an application restart the Bisq network governance data will be reloaded from the seed nodes and the BSQ consensus state will be rebuilt from the genesis transaction. setting.preferences.dao.resyncFromGenesis.resync=Resync from genesis and shutdown setting.preferences.dao.isDaoFullNode=Chạy ứng dụng Bisq như một full node DAO +setting.preferences.dao.activated=DAO activated +setting.preferences.dao.activated.popup=The change will be applied after a restart setting.preferences.dao.rpcUser=Tên người dùng RPC setting.preferences.dao.rpcPw=Mật khẩu RPC setting.preferences.dao.blockNotifyPort=Cổng thông báo chặn @@ -1262,9 +1267,9 @@ account.altcoin.popup.ZEC.msg=When using Zcash you can only use the transparent # suppress inspection "UnusedProperty" account.altcoin.popup.XZC.msg=When using Zcoin you can only use the transparent (traceable) addresses, not the untraceable addresses, because the mediator or arbitrator would not be able to verify the transaction with untraceable addresses at a block explorer. # suppress inspection "UnusedProperty" -account.altcoin.popup.grin.msg=GRIN yêu cầu một quá trình tương tác giữa người gửi và người nhận để thực hiện một giao dịch. Vui lòng làm theo hướng dẫn từ trang web của dự án GRIN để gửi và nhận GRIN đúng cách. (người nhận cần phải trực tuyến hoặc ít nhất là trực tuyến trong một khung thời gian nhất định).\n\nBisq chỉ hỗ trợ ví Grinbox(wallet713) theo định dạng URL.\n\nNgười gửi GRIN phải cung cấp bằng chứng là họ đã gửi GRIN thành công. Nếu ví không thể cung cấp bằng chứng đó, nếu có tranh chấp thì sẽ được giải quyết theo hướng có lợi cho người nhận GRIN. Vui lòng đảm bảo rằng bạn sử dụng phần mềm Grinbox mới nhất có hỗ trợ bằng chứng giao dịch và bạn hiểu quy trình chuyển và nhận GRIN cũng như tạo bằng chứng. \n\nXem https://github.com/vault713/wallet713/blob/master/docs/usage.md#transaction-proofs-grinbox-only để biết thêm thông tin về công cụ bằng chứng Grinbox. +account.altcoin.popup.grin.msg=GRIN yêu cầu một quá trình tương tác giữa người gửi và người nhận để thực hiện một giao dịch. Vui lòng làm theo hướng dẫn từ trang web của dự án GRIN để gửi và nhận GRIN đúng cách. (người nhận cần phải trực tuyến hoặc ít nhất là trực tuyến trong một khung thời gian nhất định).\n\nBisq chỉ hỗ trợ ví Grinbox(wallet713) theo định dạng URL.\n\nNgười gửi GRIN phải cung cấp bằng chứng là họ đã gửi GRIN thành công. Nếu ví không thể cung cấp bằng chứng đó, nếu có tranh chấp thì sẽ được giải quyết theo hướng có lợi cho người nhận GRIN. Vui lòng đảm bảo rằng bạn sử dụng phần mềm Grinbox mới nhất có hỗ trợ bằng chứng giao dịch và bạn hiểu quy trình chuyển và nhận GRIN cũng như tạo bằng chứng. \n\nXem https://github.com/vault713/wallet713/blob/master/docs/usage.md#transaction-proofs-grinbox-only để biết thêm thông tin về công cụ bằng chứng Grinbox. # suppress inspection "UnusedProperty" -account.altcoin.popup.beam.msg=BEAM yêu cầu một quá trình tương tác giữa người gửi và người nhận để thực hiện một giao dịch. \n\nVui lòng làm theo hướng dẫn từ trang web của dự án BEAM để gửi và nhận BEAM đúng cách. (người nhận cần phải trực tuyến hoặc ít nhất là trực tuyến trong một khung thời gian nhất định).\n\nNgười gửi BEAM phải cung cấp bằng chứng là họ đã gửi BEAM thành công. Vui lòng đảm bảo là bạn sử dụng phần mềm ví có thể tạo ra một bằng chứng như vậy. Nếu ví không thể cung cấp bằng chứng đó, nếu có tranh chấp thì sẽ được giải quyết theo hướng có lợi cho người nhận BEAM. +account.altcoin.popup.beam.msg=BEAM yêu cầu một quá trình tương tác giữa người gửi và người nhận để thực hiện một giao dịch. \n\nVui lòng làm theo hướng dẫn từ trang web của dự án BEAM để gửi và nhận BEAM đúng cách. (người nhận cần phải trực tuyến hoặc ít nhất là trực tuyến trong một khung thời gian nhất định).\n\nNgười gửi BEAM phải cung cấp bằng chứng là họ đã gửi BEAM thành công. Vui lòng đảm bảo là bạn sử dụng phần mềm ví có thể tạo ra một bằng chứng như vậy. Nếu ví không thể cung cấp bằng chứng đó, nếu có tranh chấp thì sẽ được giải quyết theo hướng có lợi cho người nhận BEAM. # suppress inspection "UnusedProperty" account.altcoin.popup.pars.msg=Trading ParsiCoin on Bisq requires that you understand and fulfill the following requirements:\n\nTo send PARS you must use the official ParsiCoin Wallet version 3.0.0 or higher. \n\nYou can Check your Transaction Hash and Transaction Key on Transactions Section on your GUI Wallet (ParsiPay) You need to right Click on the Transaction and then click on show details. \n\nIn the event that arbitration is necessary, you must present the following to an mediator or arbitrator: 1) the Transaction Hash, 2) the Transaction Key, and 3) the recipient's PARS address. The mediator or arbitrator will then verify the PARS transfer using the ParsiCoin Block Explorer (http://explorer.parsicoin.net/#check_payment).\n\nFailure to provide the required information to the mediator or arbitrator will result in losing the dispute case. In all cases of dispute, the ParsiCoin sender bears 100% of the burden of responsibility in verifying transactions to an mediator or arbitrator. \n\nIf you do not understand these requirements, do not trade on Bisq. First, seek help at the ParsiCoin Discord (https://discord.gg/c7qmFNh). @@ -1298,7 +1303,7 @@ account.seed.backup.warning=Please note that the seed words are NOT a replacemen account.seed.warn.noPw.msg=Bạn đã tạo mật khẩu ví để bảo vệ tránh hiển thị Seed words.\n\nBạn có muốn hiển thị Seed words? account.seed.warn.noPw.yes=Có và không hỏi lại account.seed.enterPw=Nhập mật khẩu để xem seed words -account.seed.restore.info=Vui lòng tạo sao lưu dự phòng trước khi tiến hành khôi phục ví từ các từ khởi tạo. Phải hiểu rằng việc khôi phục ví chỉ nên thực hiện trong các trường hợp khẩn cấp và có thể gây sự cố với cơ sở dữ liệu ví bên trong.\nĐây không phải là một cách sao lưu dự phòng! Vui lòng sử dụng sao lưu dự phòng từ thư mục dữ liệu của ứng dụng để khôi phục trạng thái ban đầu của ứng dụng.\n\nSau khi khôi phục ứng dụng sẽ tự động tắt. Sau khi bạn khởi động lại, ứng dụng sẽ tái đồng bộ với mạng Bitcoin. Quá trình này có thể mất một lúc và tiêu tốn khá nhiều CPU, đặc biệt là khi ví đã cũ và có nhiều giao dịch. Vui lòng không làm gián đoạn quá trình này, nếu không bạn có thể sẽ phảỉ xóa file chuỗi SPV một lần nữa hoặc lặp lại quy trình khôi phục. +account.seed.restore.info=Vui lòng tạo sao lưu dự phòng trước khi tiến hành khôi phục ví từ các từ khởi tạo. Phải hiểu rằng việc khôi phục ví chỉ nên thực hiện trong các trường hợp khẩn cấp và có thể gây sự cố với cơ sở dữ liệu ví bên trong.\nĐây không phải là một cách sao lưu dự phòng! Vui lòng sử dụng sao lưu dự phòng từ thư mục dữ liệu của ứng dụng để khôi phục trạng thái ban đầu của ứng dụng.\n\nSau khi khôi phục ứng dụng sẽ tự động tắt. Sau khi bạn khởi động lại, ứng dụng sẽ tái đồng bộ với mạng Bitcoin. Quá trình này có thể mất một lúc và tiêu tốn khá nhiều CPU, đặc biệt là khi ví đã cũ và có nhiều giao dịch. Vui lòng không làm gián đoạn quá trình này, nếu không bạn có thể sẽ phảỉ xóa file chuỗi SPV một lần nữa hoặc lặp lại quy trình khôi phục. account.seed.restore.ok=Được, hãy thực hiện khôi phục và tắt ứng dụng Bisq @@ -1919,24 +1924,19 @@ dao.feeTx.confirm.details={0} fee: {1}\nMining fee: {2} ({3} Satoshis/vbyte)\nTr dao.feeTx.issuanceProposal.confirm.details={0} fee: {1}\nBTC needed for BSQ issuance: {2} ({3} Satoshis/BSQ)\nMining fee: {4} ({5} Satoshis/vbyte)\nTransaction vsize: {6} vKb\n\nIf your request is approved, you will receive the amount you requested net of the 2 BSQ proposal fee.\n\nAre you sure you want to publish the {7} transaction? dao.news.bisqDAO.title=DAO BISQ -dao.news.bisqDAO.description=Vì BIsq là sàn giao dịch phi tập trung và không bị kiểm duyệt, bởi vậy mô hình vận hành của nó, DAO Bisq và đồng BSQ là công cụ giúp điều này trở thành hiện thực. +dao.news.bisqDAO.description=Vì BIsq là sàn giao dịch phi tập trung và không bị kiểm duyệt, bởi vậy mô hình vận hành của nó, DAO Bisq và đồng BSQ là công cụ giúp điều này trở thành hiện thực. dao.news.bisqDAO.readMoreLink=Tìm hiểu thêm về DAO Bisq -dao.news.pastContribution.title=BẠN ĐÃ THAM GIA ĐÓNG GÓP? YÊU CẦU BSQ -dao.news.pastContribution.description=Nếu như bạn đã tham giao đóng góp cho Bisq, vui lòng sử dụng ví BSQ phía dưới và thực hiện một yêu cầu tham gia vào sự kiện phát hành BSQ genesis. -dao.news.pastContribution.yourAddress=Ví BSQ của bạn -dao.news.pastContribution.requestNow=Yêu cầu ngay - -dao.news.daoInfo.title=CHẠY DAO BISQ TRÊN TESTNET CỦA CHÚNG TÔI -dao.news.daoInfo.description=Mainnet DAO Bisq chưa ra mắt nhưng bạn vẫn có thể tìm hiểu về DAO Bisq bằng cách chạy nó trên testnet. -dao.news.daoInfo.firstSection.title=1. Chuyển qua chế độ Testnet DAO -dao.news.daoInfo.firstSection.content=1. Chuyển qua chế độ Testnet DAO từ màn hình cài đặt +dao.news.daoInfo.title=ENABLE THE BISQ DAO +dao.news.daoInfo.description=To participate in the Bisq DAO and to use BSQ for discounted trading fees, you need to enable the DAO. When the DAO is enabled, Bisq downloads all missing blocks and verifies BSQ transactions. This verification process requires time, during which you may see Bisq use a lot of memory and processing power. This is normal. +dao.news.daoInfo.firstSection.title=1. Enable DAO +dao.news.daoInfo.firstSection.content=Enable the Bisq DAO and restart. dao.news.DAOOnTestnet.secondSection.title=2. Kiếm BSQ dao.news.DAOOnTestnet.secondSection.content=Yêu cầu BSQ trên Slack hoặc Mua BSQ trên Bisq dao.news.DAOOnTestnet.thirdSection.title=3. Tham gia một vòng bỏ phiếu -dao.news.DAOOnTestnet.thirdSection.content=Tạo đề xuất và bỏ phiếu cho đề xuất để thanh đổi nhiều khía cạnh của Bisq. +dao.news.DAOOnTestnet.thirdSection.content=Tạo đề xuất và bỏ phiếu cho đề xuất để thanh đổi nhiều khía cạnh của Bisq. dao.news.DAOOnTestnet.fourthSection.title=4. Tìm hiểu về BSQ Block Explorer -dao.news.DAOOnTestnet.fourthSection.content=Vì BSQ chỉa là bitcoin, bạn có thể thấy các giao dịch BSQ trên trình duyện bitcoin Block Explorer của chúng tôi. +dao.news.DAOOnTestnet.fourthSection.content=Vì BSQ chỉa là bitcoin, bạn có thể thấy các giao dịch BSQ trên trình duyện bitcoin Block Explorer của chúng tôi. dao.news.DAOOnTestnet.readMoreLink=Đọc tài liệu đầy đủ dao.monitor.daoState=Trạng thái DAO @@ -1954,7 +1954,7 @@ dao.monitor.table.seedPeers=Seed node: {0} dao.monitor.daoState.headline=Trạng thái DAO dao.monitor.daoState.table.headline=Chuỗi Hash trạng thái DAO -dao.monitor.daoState.table.blockHeight=Chiều cao khối +dao.monitor.daoState.table.blockHeight=Chiều cao khối dao.monitor.daoState.table.hash=Hash của trạng thái DAO dao.monitor.daoState.table.prev=Hash trước đó dao.monitor.daoState.conflictTable.headline=Hash trạng thái DAO từ đối tác đang trong xung dột @@ -1972,7 +1972,7 @@ dao.monitor.proposal.table.hash=Hash trạng thái đề xuất dao.monitor.proposal.table.prev=Hash trước đó dao.monitor.proposal.table.numProposals=Số đề xuất -dao.monitor.isInConflictWithSeedNode=Dữ liệu trên máy bạn không đồng bộ với ít nhất một seed node. Vui lòng đồng bộ lại trạng thái DAO. +dao.monitor.isInConflictWithSeedNode=Dữ liệu trên máy bạn không đồng bộ với ít nhất một seed node. Vui lòng đồng bộ lại trạng thái DAO. dao.monitor.isInConflictWithNonSeedNode=Một trong các đối tác của bạn không đồng bộ với mạng nhưng node của bạn vẫn đang đồng bộ với các seed node. dao.monitor.daoStateInSync=Node trên máy tính của bạn đang dồng bộ với mạng @@ -2363,7 +2363,7 @@ popup.warning.mandatoryUpdate.trading=Please update to the latest Bisq version. popup.warning.mandatoryUpdate.dao=Please update to the latest Bisq version. A mandatory update was released which disables the Bisq DAO and BSQ for old versions. Please check out the Bisq Forum for more information. popup.warning.disable.dao=The Bisq DAO and BSQ are temporary disabled. Please check out the Bisq Forum for more information. popup.warning.noFilter=We did not receive a filter object from the seed nodes. This is a not expected situation. Please inform the Bisq developers. -popup.warning.burnBTC=Không thể thực hiện giao dịch, vì phí đào {0} vượt quá số lượng {1} cần chuyển. Vui lòng chờ tới khi phí đào thấp xuống hoặc khi bạn tích lũy đủ BTC để chuyển. +popup.warning.burnBTC=Không thể thực hiện giao dịch, vì phí đào {0} vượt quá số lượng {1} cần chuyển. Vui lòng chờ tới khi phí đào thấp xuống hoặc khi bạn tích lũy đủ BTC để chuyển. popup.warning.openOffer.makerFeeTxRejected=The maker fee transaction for offer with ID {0} was rejected by the Bitcoin network.\nTransaction ID={1}.\nThe offer has been removed to avoid further problems.\nPlease go to \"Settings/Network info\" and do a SPV resync.\nFor further help please contact the Bisq support channel at the Bisq Keybase team. @@ -2679,6 +2679,7 @@ payment.secret=Câu hỏi bí mật payment.answer=Trả lời payment.wallet=ID ví payment.capitual.cap=CAP Code +payment.upi.virtualPaymentAddress=Virtual Payment Address # suppress inspection "UnusedProperty" payment.swift.headline=International SWIFT Wire Transfer @@ -2767,11 +2768,65 @@ payment.amazonGiftCard.upgrade=Amazon gift cards payment method requires the cou payment.account.amazonGiftCard.addCountryInfo={0}\nYour existing Amazon Gift Card account ({1}) does not have a Country specified.\nPlease enter your Amazon Gift Card Country to update your account data.\nThis will not affect your account age status. payment.amazonGiftCard.upgrade.headLine=Update Amazon Gift Card account -payment.swift.info=Carefully review the core guidelines for using SWIFT on Bisq:\n\n- fill all fields completely and accurately \n- buyer must send payment in currency specified by the offer maker \n- buyer must use the shared fee model (SHA) \n- buyer and seller may incur fees, so they should check their bank's fee schedules beforehand \n\nSWIFT is more sophisticated than other payment methods, so please take a moment to review full guidance on the wiki [HYPERLINK:https://bisq.wiki/SWIFT]. +payment.swift.info.account=Carefully review the core guidelines for using SWIFT on Bisq:\n\n- fill all fields completely and accurately \n- buyer must send payment in currency specified by the offer maker \n- buyer must use the shared fee model (SHA) \n- buyer and seller may incur fees, so they should check their bank's fee schedules beforehand \n\nSWIFT is more sophisticated than other payment methods, so please take a moment to review full guidance on the wiki [HYPERLINK:https://bisq.wiki/SWIFT]. payment.swift.info.buyer=To buy bitcoin with SWIFT, you must:\n\n- send payment in the currency specified by the offer maker \n- use the shared fee model (SHA) to send payment\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. -payment.swift.info.seller=SWIFT senders are required to use the shared payment model (SHA) to send payments.\n\nIf you receive a SWIFT payment that does not use SHA, open a mediation ticket.\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. +payment.swift.info.seller=SWIFT senders are required to use the shared fee model (SHA) to send payments.\n\nIf you receive a SWIFT payment that does not use SHA, open a mediation ticket.\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. + +payment.imps.info.account=Please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a maximum of Rs. 200,000 that can be sent per transaction. If you are trading over this amount multiple transactions will be needed. However be aware their is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nSome banks have different limits for their customers. +payment.imps.info.buyer=Please send payment only to the account details provided in Bisq.\n\nThe maximum trade size is Rs. 200,000 per transaction.\n\nIf your trade is over Rs. 200,000 you will have to make multiple transfers. However be aware their is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nPlease note some banks have different limits for their customers. +payment.imps.info.seller=If you intend to receive over Rs. 200,000 per trade you should expect the buyer to have to make multiple transfers. However be aware there is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nPlease note some banks have different limits for their customers. + +payment.neft.info.account=Please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a maximum of Rs. 50,000 that can be sent per transaction. If you are trading over this amount multiple transactions will be needed.\n\nPlease note some banks have different limits for their customers. +payment.neft.info.buyer=Please send payment only to the account details provided in Bisq.\n\nThe maximum trade size is Rs. 50,000 per transaction.\n\nIf your trade is over Rs. 50,000 you will have to make multiple transfers.\n\nPlease note some banks have different limits for their customers. +payment.neft.info.seller=If you intend to receive over Rs. 50,000 per trade you should expect the buyer to have to make multiple transfers.\n\nPlease note some banks have different limits for their customers. + +payment.paytm.info.account=Please make sure to include your email or phone number that matches your email or phone number in your PayTM account. \n\nWhen users set up a PayTM account with No KYC users are limited to: \n\n● Maximum of Rs. 5,000 can be sent per transaction.\n● Maximum of Rs. 10,000 can be held in someone's PayTM wallet.\n\nIf you intend to trade amount of over 5,000 per trade you will need to complete KYC with PayTM. With KYC users are limited to:\n\n● Maximum of Rs. 100,000 can be sent per transaction.\n● Maximum of Rs. 100,000 can be held in someone's PayTM wallet.\n\nUsers should also be aware of account limits. Trades above PayTM account limits will likely have to take place over more than one day, or, be cancelled. +payment.paytm.info.buyer=Please send payment only to the email address or phone number provided.\n\nIf you intend to trade amount of over Rs. 5,000 per trade you will need to complete KYC with PayTM.\n\nWith No KYC Rs. 5,000 can be sent per transaction.\n\nWith KYC users Rs. 100,000 can be sent per transaction. +payment.paytm.info.seller=If you intend to trade amount of over Rs. 5,000 per trade you will need to complete KYC with PayTM. With KYC users are limited to:\n\n● Maximum of Rs. 100,000 can be sent per transaction.\n● Maximum of Rs. 100,000 can be held in your PayTM wallet .\n\nUsers should also be aware of account limits. As a maximum of Rs. 100,000 can be held in your PayTM wallet please make sure you transfer out your rupees regularly. + +payment.rtgs.info.account=RTGS is for payments of large trades of Rs. 200,000 or over.\n\nWhen setting up your RTGS payment account please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). +payment.rtgs.info.buyer=Please send payment only to the account details provided in Bisq.\n\nPlease be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). +payment.rtgs.info.seller=Please be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). + +payment.upi.info.account=Please make sure to include your Virtual Payment Address (VPA) also called your UPI ID. The format for this is like an email ID: with the sign “@” in the middle. For example, your UPI ID could be “receiver’s_name@bank_name” or “phone_number@bank_name.” \n\nFor UPI there is a maximum limit of Rs. 100,000 that can be sent per transaction. \n\nIf you intend to trade amount of over Rs. 100,000 per trade it is likely trades will have to take place over multiple transfers. \n\nPlease note some banks have different limits for their customers. +payment.upi.info.buyer=Please send payment only to the VPA / UPI ID provided in Bisq. \n\nThe maximum trade size is Rs. 100,000 per transaction. \n\nIf your trade is over Rs. 100,000 you will have to make multiple transfers. \n\nPlease note some banks have different limits for their customers. +payment.upi.info.seller=If you intend to receive over Rs. 100,000 per trade you should expect the buyer to have to make multiple transfers. \n\nPlease note some banks have different limits for their customers. + +payment.celpay.info.account=Please make sure to include the email your Celsius account is registered to. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account.\n\nCelPay users are limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Buyers can send any matching currency stablecoin to the BTC Seller. +payment.celpay.info.buyer=Please send payment only to the email address provided by the BTC Seller by sending a payment link.\n\nCelPay is limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Buyers can send any matching currency stablecoin to the BTC Seller. +payment.celpay.info.seller=BTC Sellers should expect to receive payment via a secure payment link. Please make sure the email payment link contains the email address provided by the BTC Buyer.\n\nCelPay users are limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Sellers should expect to receive any matching currency stablecoin from the BTC Buyer. It is possible for the BTC Buyer to send any matching currency stablecoin. +payment.celpay.supportedCurrenciesForReceiver=Supported currencies (please note: all the currencies below are supported stable coins within the Celcius app. Trades are for stable coins, not fiat.) + +payment.nequi.info.account=Please make sure to include your phone number that is associated with your Nequi account.\n\nWhen users set up a Nequi account payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. +payment.nequi.info.buyer=Please send payment only to the phone number provided in the BTC Seller's Bisq account.\n\nWhen users set up a Nequi account, payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. +payment.nequi.info.seller=Please check that the payment received matches the phone number provided in the BTC Buyer's Bisq account.\n\nWhen users set up a Nequi account, payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. + +payment.bizum.info.account=To use Bizum you need a bank account (IBAN) in Spain and to be registered for the service.\n\nBizum can be used for trades between €0.50 and €1,000.\n\nThe maximum amount of transactions you can send/receive using Bizum is €2,000 Euros per day.\n\nBizum users can have a maximum of 150 operations per month.\n\nEach bank however may establish its own limits, within the above limits, for its clients.\n\nTraders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.bizum.info.buyer=Please send payment only to the BTC Seller's mobile phone number as provided in Bisq.\n\nThe maximum trade size is €1,000 per payment. The maximum amount of transactions you can send using Bizum is €2,000 Euros per day.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.bizum.info.seller=Please make sure your payment is received from the BTC Buyer's mobile phone number as provided in Bisq.\n\nThe maximum trade size is €1,000 per payment. The maximum amount of transactions you can receive using Bizum is €2,000 Euros per day.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. + +payment.pix.info.account=Please make sure to include your chosen Pix Key. There are four types of keys: CPF (Natural Persons Register) or CNPJ (National Registry of Legal Entities), e-mail address, telephone number or a random key generated by the system called a universally unique identifier (UUID). A different key must be used for each Pix account you have. Individuals can create up to five keys for each account they own.\n\nWhen trading on Bisq, BTC Buyers should use their Pix Keys as the payment description so that it is easy for the BTC Sellers to identify the payment as coming from themselves. +payment.pix.info.buyer=Please send payment only the Pix Key provided in the BTC Seller's Bisq account.\n\nPlease use your Pix Key as the payment reference so that it is easy for the BTC Seller to identify the payment as coming from yourself. +payment.pix.info.seller=Please check that the payment received description matches the Pix Key provided in the BTC Buyer's Bisq account. +payment.pix.key=Pix Key (CPF, CNPJ, Email, Phone number or UUID) + +payment.monese.info.account=Monese is a bank app for users of GBP, EUR and RON*. Monese allows users to send money to other Monese accounts instantly and for free in any supported currency.\n\n*To open a RON account in Monese, you need to either live in Romania or have Romanian citizenship.\n\nWhen setting up your Monese account in Bisq please make sure to include your name and phone number that matches your Monese account. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account. +payment.monese.info.buyer=Please send payment only to the phone number provided by the BTC Seller in their Bisq account. Please leave the payment description blank. +payment.monese.info.seller=BTC Sellers should expect to receive payment from the phone number / name shown in the BTC Buyer's Bisq account. + +payment.satispay.info.account=To use Satispay you need a bank account (IBAN) in Italy and to be registered for the service.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.satispay.info.buyer=Please send payment only to the BTC Seller's mobile phone number as provided in Bisq.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.satispay.info.seller=Please make sure your payment is received from the BTC Buyer's mobile phone number / name as provided in Bisq.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. + +payment.verse.info.account=Verse is a multiple currency payment method that can send and receive payment in EUR, SEK, HUF, DKK, PLN.\n\nWhen setting up your Verse account in Bisq please make sure to include the username that matches your username in your Verse account. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. +payment.verse.info.buyer=Please send payment only to the username provided by the BTC Seller in their Bisq account. Please leave the payment description blank.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. +payment.verse.info.seller=BTC Sellers should expect to receive payment from the username shown in the BTC Buyer's Bisq account.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. + +payment.strike.info.account=Please make sure to include your Strike username.\n\nIn Bisq, Strike is used for fiat to fiat payments only.\n\nPlease make sure you are aware of the Strike limits:\n\nUsers who have registered with only their email, name, and phone number have the following limits:\n\n● $100 maximum per deposit\n● $1,000 maximum total deposits per week\n● $100 maximum per payment\n\nUsers can increase their limits by providing Strike with more information. These users have the following limits:\n\n● $1,000 maximum per deposit\n● $1,000 maximum total deposits per week\n● $1,000 maximum per payment\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.strike.info.buyer=Please send payment only to the BTC Seller's Strike username as provided in Bisq.\n\nThe maximum trade size is $1,000 per payment.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.strike.info.seller=Please make sure your payment is received from the BTC Buyer's Strike username as provided in Bisq.\n\nThe maximum trade size is $1,000 per payment.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. payment.usPostalMoneyOrder.info=Trading using US Postal Money Orders (USPMO) on Bisq requires that you understand the following:\n\n- BTC buyers must write the BTC Seller’s name in both the Payer and the Payee’s fields & take a high-resolution photo of the USPMO and envelope with proof of tracking before sending.\n- BTC buyers must send the USPMO to the BTC seller with Delivery Confirmation.\n\nIn the event mediation is necessary, or if there is a trade dispute, you will be required to send the photos to the Bisq mediator or refund agent, together with the USPMO Serial Number, Post Office Number, and dollar amount, so they can verify the details on the US Post Office website.\n\nFailure to provide the required information to the Mediator or Arbitrator will result in losing the dispute case.\n\nIn all dispute cases, the USPMO sender bears 100% of the burden of responsibility in providing evidence/proof to the Mediator or Arbitrator.\n\nIf you do not understand these requirements, do not trade using USPMO on Bisq. @@ -2792,6 +2847,8 @@ payment.f2f.info='Face to Face' trades have different rules and come with differ payment.f2f.info.openURL=Mở trang web payment.f2f.offerbook.tooltip.countryAndCity=Country and city: {0} / {1} payment.f2f.offerbook.tooltip.extra=Thông tin thêm: {0} +payment.ifsc=IFS Code +payment.ifsc.validation=IFSC format: XXXX0999999 payment.japan.bank=Ngân hàng payment.japan.branch=Branch @@ -2800,7 +2857,7 @@ payment.japan.recipient=Tên payment.australia.payid=PayID payment.payid=PayID linked to financial institution. Like email address or mobile phone. payment.payid.info=A PayID like a phone number, email address or an Australian Business Number (ABN), that you can securely link to your bank, credit union or building society account. You need to have already created a PayID with your Australian financial institution. Both sending and receiving financial institutions must support PayID. For more information please check [HYPERLINK:https://payid.com.au/faqs/] -payment.amazonGiftCard.info=To pay with Amazon eGift Card, you will need to send an Amazon eGift Card to the BTC seller via your Amazon account. \n\nBisq will show the BTC seller''s email address or phone number where the gift card should be sent, and you must include the trade ID in the gift card''s message field. Please see the wiki [HYPERLINK:https://bisq.wiki/Amazon_eGift_card] for further details and best practices. \n\nThree important notes:\n- try to send gift cards with amounts of 100 USD or smaller, as Amazon is known to flag larger gift cards as fraudulent\n- try to use creative, believable text for the gift card''s message (e.g., "Happy birthday Susan!") along with the trade ID (and use trader chat to tell your trading peer the reference text you picked so they can verify your payment)\n- Amazon eGift Cards can only be redeemed on the Amazon website they were purchased on (e.g., a gift card purchased on amazon.it can only be redeemed on amazon.it) +payment.amazonGiftCard.info=To pay with Amazon eGift Card, you will need to send an Amazon eGift Card to the BTC seller via your Amazon account. \n\nPlease see the wiki [HYPERLINK:https://bisq.wiki/Amazon_eGift_card] for further details and best practices. \n\nThree important notes:\n- try to send gift cards with amounts of 100 USD or smaller, as Amazon is known to flag larger gift cards as fraudulent\n- try to use creative, believable text for the gift card''s message (e.g., "Happy birthday Susan!") and use trader chat to tell your trading peer the reference text you picked so they can verify your payment\n- Amazon eGift Cards can only be redeemed on the Amazon website they were purchased on (e.g., a gift card purchased on amazon.it can only be redeemed on amazon.it) # We use constants from the code so we do not use our normal naming convention # dynamic values are not recognized by IntelliJ @@ -2885,12 +2942,38 @@ PAYSERA=Paysera # suppress inspection "UnusedProperty" PAXUM=Paxum # suppress inspection "UnusedProperty" +NEFT=India/NEFT +# suppress inspection "UnusedProperty" +RTGS=India/RTGS +# suppress inspection "UnusedProperty" +IMPS=India/IMPS +# suppress inspection "UnusedProperty" +UPI=India/UPI +# suppress inspection "UnusedProperty" +PAYTM=India/PayTM +# suppress inspection "UnusedProperty" +NEQUI=Nequi +# suppress inspection "UnusedProperty" +BIZUM=Bizum +# suppress inspection "UnusedProperty" +PIX=Pix +# suppress inspection "UnusedProperty" AMAZON_GIFT_CARD=Amazon eGift Card # suppress inspection "UnusedProperty" BLOCK_CHAINS_INSTANT=Altcoin ngay tức thì # suppress inspection "UnusedProperty" CAPITUAL=Capitual # suppress inspection "UnusedProperty" +CELPAY=CelPay +# suppress inspection "UnusedProperty" +MONESE=Monese +# suppress inspection "UnusedProperty" +SATISPAY=Satispay +# suppress inspection "UnusedProperty" +VERSE=Verse +# suppress inspection "UnusedProperty" +STRIKE=Strike +# suppress inspection "UnusedProperty" SWIFT=SWIFT International Wire Transfer # Deprecated: Cannot be deleted as it would break old trade history entries @@ -2945,12 +3028,38 @@ PAYSERA_SHORT=Paysera # suppress inspection "UnusedProperty" PAXUM_SHORT=Paxum # suppress inspection "UnusedProperty" +NEFT_SHORT=NEFT +# suppress inspection "UnusedProperty" +RTGS_SHORT=RTGS +# suppress inspection "UnusedProperty" +IMPS_SHORT=IMPS +# suppress inspection "UnusedProperty" +UPI_SHORT=UPI +# suppress inspection "UnusedProperty" +PAYTM_SHORT=PayTM +# suppress inspection "UnusedProperty" +NEQUI_SHORT=Nequi +# suppress inspection "UnusedProperty" +BIZUM_SHORT=Bizum +# suppress inspection "UnusedProperty" +PIX_SHORT=Pix +# suppress inspection "UnusedProperty" AMAZON_GIFT_CARD_SHORT=Amazon eGift Card # suppress inspection "UnusedProperty" BLOCK_CHAINS_INSTANT_SHORT=Altcoin ngay tức thì # suppress inspection "UnusedProperty" CAPITUAL_SHORT=Capitual # suppress inspection "UnusedProperty" +CELPAY_SHORT=CelPay +# suppress inspection "UnusedProperty" +MONESE_SHORT=Monese +# suppress inspection "UnusedProperty" +SATISPAY_SHORT=Satispay +# suppress inspection "UnusedProperty" +VERSE_SHORT=Verse +# suppress inspection "UnusedProperty" +STRIKE_SHORT=Strike +# suppress inspection "UnusedProperty" SWIFT_SHORT=SWIFT # Deprecated: Cannot be deleted as it would break old trade history entries diff --git a/core/src/main/resources/i18n/displayStrings_zh-hans.properties b/core/src/main/resources/i18n/displayStrings_zh-hans.properties index 32822c053c6..e629e913ede 100644 --- a/core/src/main/resources/i18n/displayStrings_zh-hans.properties +++ b/core/src/main/resources/i18n/displayStrings_zh-hans.properties @@ -284,6 +284,7 @@ mainView.walletServiceErrorMsg.rejectedTxException=交易被网络拒绝。\n\n{ mainView.networkWarning.allConnectionsLost=您失去了所有与 {0} 网络节点的连接。\n您失去了互联网连接或您的计算机处于待机状态。 mainView.networkWarning.localhostBitcoinLost=您丢失了与本地主机比特币节点的连接。\n请重启 Bisq 应用程序连接到其他比特币节点或重新启动主机比特币节点。 +mainView.networkWarning.clockWatcher=Your computer was asleep for {0} seconds. Standby mode has been known to cause trades to fail. In order to operate correctly Bisq requires that standby mode be disabled in your computer's settings. mainView.version.update=(有更新可用) @@ -377,10 +378,10 @@ offerbook.volume={0}(最小 - 最大) offerbook.deposit=BTC 保证金(%) offerbook.deposit.help=交易双方均已支付保证金确保这个交易正常进行。这会在交易完成时退还。 -offerbook.createOfferToBuy=创建新的报价来买入 {0} -offerbook.createOfferToSell=创建新的报价来卖出 {0} +offerbook.createOfferToBuy=创建新的报价来买入 {0} +offerbook.createOfferToSell=创建新的报价来卖出 {0} offerbook.createOfferToBuy.withFiat=创建新的报价用 {1} 购买 {0} -offerbook.createOfferToSell.forFiat=创建新的报价以 {1} 出售 {0} +offerbook.createOfferToSell.forFiat=创建新的报价以 {1} 出售 {0} offerbook.createOfferToBuy.withCrypto=创建新的卖出报价 {0} (买入 {1}) offerbook.createOfferToSell.forCrypto=创建新的买入报价 {0}(卖出 {1}) @@ -660,7 +661,7 @@ portfolio.pending.step2_buyer.moneyGramMTCNInfo.headline=发送授权编号和 portfolio.pending.step2_buyer.moneyGramMTCNInfo.msg=请通过电邮发送授权编号和照片给 BTC 卖家。\n收据必须清楚地向卖家写明您的全名、城市、国家或地区、数量。卖方的电子邮件是:{0}。\n\n您把授权编号和合同发给卖方了吗? portfolio.pending.step2_buyer.westernUnionMTCNInfo.headline=发送 MTCN 和收据 portfolio.pending.step2_buyer.westernUnionMTCNInfo.msg=请通过电邮发送 MTCN(追踪号码)和照片给 BTC 卖家。\n收据必须清楚地向卖家写明您的全名、城市、国家或地区、数量。卖方的电子邮件是:{0}。\n\n您把 MTCN 和合同发给卖方了吗? -portfolio.pending.step2_buyer.halCashInfo.headline=请发送 HalCash 代码 +portfolio.pending.step2_buyer.halCashInfo.headline=请发送 HalCash 代码 portfolio.pending.step2_buyer.halCashInfo.msg=您需要向 BTC 卖家发送带有 HalCash 代码和交易 ID({0})的文本消息。\n\n卖方的手机号码是 {1} 。\n\n您是否已经将代码发送至卖家? portfolio.pending.step2_buyer.fasterPaymentsHolderNameInfo=有些银行可能会要求接收方的姓名。在较旧的 Bisq 客户端创建的快速支付帐户没有提供收款人的姓名,所以请使用交易聊天来获得收款人姓名(如果需要)。 portfolio.pending.step2_buyer.confirmStart.headline=确定您已经付款 @@ -821,7 +822,7 @@ portfolio.pending.mediationResult.popup.alreadyAccepted=您已经接受了。 portfolio.pending.failedTrade.taker.missingTakerFeeTx=吃单交易费未找到。\n\n如果没有 tx,交易不能完成。没有资金被锁定以及没有支付交易费用。你可以将交易移至失败的交易。 portfolio.pending.failedTrade.maker.missingTakerFeeTx=挂单费交易未找到。\n\n如果没有 tx,交易不能完成。没有资金被锁定以及没有支付交易费用。你可以将交易移至失败的交易。 portfolio.pending.failedTrade.missingDepositTx=这个保证金交易(2 对 2 多重签名交易)缺失\n\n没有该 tx,交易不能完成。没有资金被锁定但是您的交易手续费仍然已支出。您可以发起一个请求去赔偿改交易手续费在这里:https://github.com/bisq-network/support/issues\n\n请随意的将该交易移至失败交易 -portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=延迟支付交易缺失,但是资金仍然被锁定在保证金交易中。\n\n请不要给比特币卖家发送法币或数字货币,因为没有延迟交易 tx,不能开启仲裁。使用 Cmd/Ctrl+o开启调解协助。调解员应该建议交易双方分别退回全部的保证金(卖方支付的交易金额也会全数返还)。这样的话不会有任何的安全问题只会损失交易手续费。\n\n你可以在这里为失败的交易提出赔偿要求:https://github.com/bisq-network/support/issues +portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the fiat or altcoin payment to the BTC seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=延迟支付交易确实但是资金仍然被锁定在保证金交易中。\n\n如果卖家仍然缺失延迟支付交易,他会接到请勿付款的指示并开启一个调节帮助。你也应该使用 Cmd/Ctrl+O 去打开一个调节协助\n\n如果买家还没有发送付款,调解员应该会建议交易双方分别退回全部的保证金(卖方支付的交易金额也会全数返还)。否则交易额应该判给买方。\n\n你可以在这里为失败的交易提出赔偿要求:https://github.com/bisq-network/support/issues portfolio.pending.failedTrade.errorMsgSet=在处理交易协议是发生了一个错误\n\n错误:{0}\n\n这应该不是致命错误,您可以正常的完成交易。如果你仍担忧,打开一个调解协助并从 Bisq 调解员处得到建议。\n\n如果这个错误是致命的那么这个交易就无法完成,你可能会损失交易费。可以在这里为失败的交易提出赔偿要求:https://github.com/bisq-network/support/issues portfolio.pending.failedTrade.missingContract=没有设置交易合同。\n\n这个交易无法完成,你可能会损失交易手续费。可以在这里为失败的交易提出赔偿要求:https://github.com/bisq-network/support/issues @@ -996,7 +997,9 @@ support.buyerTaker=BTC 买家/买单者 support.sellerTaker=BTC 卖家/买单者 support.backgroundInfo=Bisq 不是一家公司,所以它处理纠纷的方式不同。\n\n交易双方可以在应用程序中通过未完成交易页面上的安全聊天进行通信,以尝试自行解决争端。如果这还不够,调解员可以介入帮助。调解员将对情况进行评估,并对交易资金的支出提出建议。如果两个交易者都接受这个建议,那么支付交易就完成了,交易也结束了。如果一方或双方不同意调解员的建议,他们可以要求仲裁。仲裁员将重新评估情况,如果有必要,将亲自向交易员付款,并要求 Bisq DAO 对这笔付款进行补偿。 -support.initialInfo=Please enter a description of your problem in the text field below. Add as much information as possible to speed up dispute resolution time.\n\nHere is a check list for information you should provide:\n\t● If you are the BTC buyer: Did you make the Fiat or Altcoin transfer? If so, did you click the 'payment started' button in the application?\n\t● If you are the BTC seller: Did you receive the Fiat or Altcoin payment? If so, did you click the 'payment received' button in the application?\n\t● Which version of Bisq are you using?\n\t● Which operating system are you using?\n\t● If you encountered an issue with failed transactions please consider switching to a new data directory.\n\t Sometimes the data directory gets corrupted and leads to strange bugs. \n\t See: https://bisq.wiki/Switching_to_a_new_data_directory\n\nPlease make yourself familiar with the basic rules for the dispute process:\n\t● You need to respond to the {0}''s requests within 2 days.\n\t● Mediators respond in between 2 days. Arbitrators respond in between 5 business days.\n\t● The maximum period for a dispute is 14 days.\n\t● You need to cooperate with the {1} and provide the information they request to make your case.\n\t● You accepted the rules outlined in the dispute document in the user agreement when you first started the application.\n\nYou can read more about the dispute process at: {2} +support.initialInfo=Please enter a description of your problem in the text field below. Add as much information as possible to speed up dispute resolution time.\n\nHere is a check list for information you should provide:\n\t● If you are the BTC buyer: Did you make the Fiat or Altcoin transfer? If so, did you click the 'payment started' button in the application?\n\t● If you are the BTC seller: Did you receive the Fiat or Altcoin payment? If so, did you click the 'payment received' button in the application?\n\t● Which version of Bisq are you using?\n\t● Which operating system are you using?\n\t● If you encountered an issue with failed transactions please consider switching to a new data directory.\n\t Sometimes the data directory gets corrupted and leads to strange bugs. \n\t See: https://bisq.wiki/Switching_to_a_new_data_directory\n\nPlease make yourself familiar with the basic rules for the dispute process:\n\t● You need to respond to the {0}''s requests within 2 days.\n\t● {1}\n\t● The maximum period for a dispute is 14 days.\n\t● You need to cooperate with the {2} and provide the information they request to make your case.\n\t● You accepted the rules outlined in the dispute document in the user agreement when you first started the application.\n\nYou can read more about the dispute process at: {3} +support.initialMediatorMsg=Mediators will generally reply to you within 24 hours.\n\t If you have not had a reply after 48 hours please feel free to reach out to your mediator on Keybase.\n\t Mediators usernames on Keybase are the same as their usernames within the Bisq app.\n\t Your mediator is: {0} +support.initialArbitratorMsg=Arbitrators will generally reply to you within 5 days.\n\t If you have not had a reply after 7 days please feel free to reach out to your arbitrator on Keybase.\n\t Arbitrators usernames on Keybase are the same as their usernames within the Bisq app.\n\t Your arbitrator is: {0} support.systemMsg=系统消息:{0} support.youOpenedTicket=您创建了帮助请求。\n\n{0}\n\nBisq 版本:{1} support.youOpenedDispute=您创建了一个纠纷请求。\n\n{0}\n\nBisq 版本:{1} @@ -1064,6 +1067,8 @@ setting.preferences.dao.resyncFromResources.popup=应用程序重新启动后, setting.preferences.dao.resyncFromGenesis.popup=从创始交易中出现同步会消耗大量时间以及 CPU 资源。您确定要重新同步吗?通常,从最新资源文件进行重新同步就足够了,而且速度更快。\n\n应用程序重新启动后,Bisq 网络治理数据将从种子节点重新加载,而 BSQ 同步状态将从初始交易中重新构建。 setting.preferences.dao.resyncFromGenesis.resync=从创始区块重新同步并关闭 setting.preferences.dao.isDaoFullNode=以 DAO 全节点运行 Bisq +setting.preferences.dao.activated=DAO activated +setting.preferences.dao.activated.popup=The change will be applied after a restart setting.preferences.dao.rpcUser=RPC 用户名 setting.preferences.dao.rpcPw=PRC 密码 setting.preferences.dao.blockNotifyPort=区块通知端口 @@ -1922,15 +1927,10 @@ dao.news.bisqDAO.title=Bisq DAO dao.news.bisqDAO.description=正如 Bisq交易是分散的,并且不受审查,它的治理模型也是如此—— Bisq DAO 和 BSQ 是使其成为可能的工具。 dao.news.bisqDAO.readMoreLink=了解有关 Bisq DAO 的更多信息 -dao.news.pastContribution.title=过去有所贡献?申请 BSQ -dao.news.pastContribution.description=如果您对 Bisq 有贡献,请使用下面的 BSQ 地址,并申请参与 BSQ 初始分发。 -dao.news.pastContribution.yourAddress=你的 BSQ 钱包地址 -dao.news.pastContribution.requestNow=现在申请 - -dao.news.daoInfo.title=在我们的测试网络上运行 BISQ DAO -dao.news.daoInfo.description=核心网络 Bisq DAO 还没有启动,但是您可以通过在我们的测试网络上运行它来了解 Bisq DAO 。 -dao.news.daoInfo.firstSection.title=1.切换至 DAO 测试网络模式 -dao.news.daoInfo.firstSection.content=从设置页面切换到 DAO 测试网络。 +dao.news.daoInfo.title=ENABLE THE BISQ DAO +dao.news.daoInfo.description=To participate in the Bisq DAO and to use BSQ for discounted trading fees, you need to enable the DAO. When the DAO is enabled, Bisq downloads all missing blocks and verifies BSQ transactions. This verification process requires time, during which you may see Bisq use a lot of memory and processing power. This is normal. +dao.news.daoInfo.firstSection.title=1. Enable DAO +dao.news.daoInfo.firstSection.content=Enable the Bisq DAO and restart. dao.news.DAOOnTestnet.secondSection.title=2.获得一些 BSQ dao.news.DAOOnTestnet.secondSection.content=在 Slack 上申请 BSQ 或在 Bisq 上购买 BSQ 。 dao.news.DAOOnTestnet.thirdSection.title=3.参与投票周期 @@ -1991,7 +1991,7 @@ dao.factsAndFigures.dashboard.avgPrice30=30天平均 BSQ/BTC 交易价格 dao.factsAndFigures.dashboard.avgUSDPrice90=90 days volume weighted average BSQ/USD price dao.factsAndFigures.dashboard.avgUSDPrice30=30 days volume weighted average BSQ/USD price dao.factsAndFigures.dashboard.marketCap=Market capitalisation (based on 30 days average BSQ/USD price) -dao.factsAndFigures.dashboard.availableAmount=总共可用的 BSQ +dao.factsAndFigures.dashboard.availableAmount=总共可用的 BSQ dao.factsAndFigures.dashboard.volumeUsd=Total trade volume in USD dao.factsAndFigures.dashboard.volumeBtc=Total trade volume in BTC dao.factsAndFigures.dashboard.averageBsqUsdPriceFromSelection=Average BSQ/USD trade price from selected time period in chart @@ -2003,8 +2003,8 @@ dao.factsAndFigures.supply.issued=已发放的 BSQ dao.factsAndFigures.supply.compReq=赔偿要求 dao.factsAndFigures.supply.reimbursement=Reimbursement requests dao.factsAndFigures.supply.genesisIssueAmount=在初始交易中心有问题的 BSQ -dao.factsAndFigures.supply.compRequestIssueAmount=报偿申请发放的 BSQ -dao.factsAndFigures.supply.reimbursementAmount=退还申请发放的 BSQ +dao.factsAndFigures.supply.compRequestIssueAmount=报偿申请发放的 BSQ +dao.factsAndFigures.supply.reimbursementAmount=退还申请发放的 BSQ dao.factsAndFigures.supply.totalIssued=Total issued BSQ dao.factsAndFigures.supply.totalBurned=Total burned BSQ dao.factsAndFigures.supply.chart.tradeFee.toolTip={0}\n{1} @@ -2679,6 +2679,7 @@ payment.secret=密保问题 payment.answer=答案 payment.wallet=钱包 ID payment.capitual.cap=CAP Code +payment.upi.virtualPaymentAddress=Virtual Payment Address # suppress inspection "UnusedProperty" payment.swift.headline=International SWIFT Wire Transfer @@ -2767,11 +2768,65 @@ payment.amazonGiftCard.upgrade=Amazon gift cards payment method requires the cou payment.account.amazonGiftCard.addCountryInfo={0}\nYour existing Amazon Gift Card account ({1}) does not have a Country specified.\nPlease enter your Amazon Gift Card Country to update your account data.\nThis will not affect your account age status. payment.amazonGiftCard.upgrade.headLine=Update Amazon Gift Card account -payment.swift.info=Carefully review the core guidelines for using SWIFT on Bisq:\n\n- fill all fields completely and accurately \n- buyer must send payment in currency specified by the offer maker \n- buyer must use the shared fee model (SHA) \n- buyer and seller may incur fees, so they should check their bank's fee schedules beforehand \n\nSWIFT is more sophisticated than other payment methods, so please take a moment to review full guidance on the wiki [HYPERLINK:https://bisq.wiki/SWIFT]. +payment.swift.info.account=Carefully review the core guidelines for using SWIFT on Bisq:\n\n- fill all fields completely and accurately \n- buyer must send payment in currency specified by the offer maker \n- buyer must use the shared fee model (SHA) \n- buyer and seller may incur fees, so they should check their bank's fee schedules beforehand \n\nSWIFT is more sophisticated than other payment methods, so please take a moment to review full guidance on the wiki [HYPERLINK:https://bisq.wiki/SWIFT]. payment.swift.info.buyer=To buy bitcoin with SWIFT, you must:\n\n- send payment in the currency specified by the offer maker \n- use the shared fee model (SHA) to send payment\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. -payment.swift.info.seller=SWIFT senders are required to use the shared payment model (SHA) to send payments.\n\nIf you receive a SWIFT payment that does not use SHA, open a mediation ticket.\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. +payment.swift.info.seller=SWIFT senders are required to use the shared fee model (SHA) to send payments.\n\nIf you receive a SWIFT payment that does not use SHA, open a mediation ticket.\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. + +payment.imps.info.account=Please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a maximum of Rs. 200,000 that can be sent per transaction. If you are trading over this amount multiple transactions will be needed. However be aware their is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nSome banks have different limits for their customers. +payment.imps.info.buyer=Please send payment only to the account details provided in Bisq.\n\nThe maximum trade size is Rs. 200,000 per transaction.\n\nIf your trade is over Rs. 200,000 you will have to make multiple transfers. However be aware their is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nPlease note some banks have different limits for their customers. +payment.imps.info.seller=If you intend to receive over Rs. 200,000 per trade you should expect the buyer to have to make multiple transfers. However be aware there is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nPlease note some banks have different limits for their customers. + +payment.neft.info.account=Please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a maximum of Rs. 50,000 that can be sent per transaction. If you are trading over this amount multiple transactions will be needed.\n\nPlease note some banks have different limits for their customers. +payment.neft.info.buyer=Please send payment only to the account details provided in Bisq.\n\nThe maximum trade size is Rs. 50,000 per transaction.\n\nIf your trade is over Rs. 50,000 you will have to make multiple transfers.\n\nPlease note some banks have different limits for their customers. +payment.neft.info.seller=If you intend to receive over Rs. 50,000 per trade you should expect the buyer to have to make multiple transfers.\n\nPlease note some banks have different limits for their customers. + +payment.paytm.info.account=Please make sure to include your email or phone number that matches your email or phone number in your PayTM account. \n\nWhen users set up a PayTM account with No KYC users are limited to: \n\n● Maximum of Rs. 5,000 can be sent per transaction.\n● Maximum of Rs. 10,000 can be held in someone's PayTM wallet.\n\nIf you intend to trade amount of over 5,000 per trade you will need to complete KYC with PayTM. With KYC users are limited to:\n\n● Maximum of Rs. 100,000 can be sent per transaction.\n● Maximum of Rs. 100,000 can be held in someone's PayTM wallet.\n\nUsers should also be aware of account limits. Trades above PayTM account limits will likely have to take place over more than one day, or, be cancelled. +payment.paytm.info.buyer=Please send payment only to the email address or phone number provided.\n\nIf you intend to trade amount of over Rs. 5,000 per trade you will need to complete KYC with PayTM.\n\nWith No KYC Rs. 5,000 can be sent per transaction.\n\nWith KYC users Rs. 100,000 can be sent per transaction. +payment.paytm.info.seller=If you intend to trade amount of over Rs. 5,000 per trade you will need to complete KYC with PayTM. With KYC users are limited to:\n\n● Maximum of Rs. 100,000 can be sent per transaction.\n● Maximum of Rs. 100,000 can be held in your PayTM wallet .\n\nUsers should also be aware of account limits. As a maximum of Rs. 100,000 can be held in your PayTM wallet please make sure you transfer out your rupees regularly. + +payment.rtgs.info.account=RTGS is for payments of large trades of Rs. 200,000 or over.\n\nWhen setting up your RTGS payment account please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). +payment.rtgs.info.buyer=Please send payment only to the account details provided in Bisq.\n\nPlease be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). +payment.rtgs.info.seller=Please be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). + +payment.upi.info.account=Please make sure to include your Virtual Payment Address (VPA) also called your UPI ID. The format for this is like an email ID: with the sign “@” in the middle. For example, your UPI ID could be “receiver’s_name@bank_name” or “phone_number@bank_name.” \n\nFor UPI there is a maximum limit of Rs. 100,000 that can be sent per transaction. \n\nIf you intend to trade amount of over Rs. 100,000 per trade it is likely trades will have to take place over multiple transfers. \n\nPlease note some banks have different limits for their customers. +payment.upi.info.buyer=Please send payment only to the VPA / UPI ID provided in Bisq. \n\nThe maximum trade size is Rs. 100,000 per transaction. \n\nIf your trade is over Rs. 100,000 you will have to make multiple transfers. \n\nPlease note some banks have different limits for their customers. +payment.upi.info.seller=If you intend to receive over Rs. 100,000 per trade you should expect the buyer to have to make multiple transfers. \n\nPlease note some banks have different limits for their customers. + +payment.celpay.info.account=Please make sure to include the email your Celsius account is registered to. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account.\n\nCelPay users are limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Buyers can send any matching currency stablecoin to the BTC Seller. +payment.celpay.info.buyer=Please send payment only to the email address provided by the BTC Seller by sending a payment link.\n\nCelPay is limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Buyers can send any matching currency stablecoin to the BTC Seller. +payment.celpay.info.seller=BTC Sellers should expect to receive payment via a secure payment link. Please make sure the email payment link contains the email address provided by the BTC Buyer.\n\nCelPay users are limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Sellers should expect to receive any matching currency stablecoin from the BTC Buyer. It is possible for the BTC Buyer to send any matching currency stablecoin. +payment.celpay.supportedCurrenciesForReceiver=Supported currencies (please note: all the currencies below are supported stable coins within the Celcius app. Trades are for stable coins, not fiat.) + +payment.nequi.info.account=Please make sure to include your phone number that is associated with your Nequi account.\n\nWhen users set up a Nequi account payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. +payment.nequi.info.buyer=Please send payment only to the phone number provided in the BTC Seller's Bisq account.\n\nWhen users set up a Nequi account, payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. +payment.nequi.info.seller=Please check that the payment received matches the phone number provided in the BTC Buyer's Bisq account.\n\nWhen users set up a Nequi account, payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. + +payment.bizum.info.account=To use Bizum you need a bank account (IBAN) in Spain and to be registered for the service.\n\nBizum can be used for trades between €0.50 and €1,000.\n\nThe maximum amount of transactions you can send/receive using Bizum is €2,000 Euros per day.\n\nBizum users can have a maximum of 150 operations per month.\n\nEach bank however may establish its own limits, within the above limits, for its clients.\n\nTraders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.bizum.info.buyer=Please send payment only to the BTC Seller's mobile phone number as provided in Bisq.\n\nThe maximum trade size is €1,000 per payment. The maximum amount of transactions you can send using Bizum is €2,000 Euros per day.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.bizum.info.seller=Please make sure your payment is received from the BTC Buyer's mobile phone number as provided in Bisq.\n\nThe maximum trade size is €1,000 per payment. The maximum amount of transactions you can receive using Bizum is €2,000 Euros per day.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. + +payment.pix.info.account=Please make sure to include your chosen Pix Key. There are four types of keys: CPF (Natural Persons Register) or CNPJ (National Registry of Legal Entities), e-mail address, telephone number or a random key generated by the system called a universally unique identifier (UUID). A different key must be used for each Pix account you have. Individuals can create up to five keys for each account they own.\n\nWhen trading on Bisq, BTC Buyers should use their Pix Keys as the payment description so that it is easy for the BTC Sellers to identify the payment as coming from themselves. +payment.pix.info.buyer=Please send payment only the Pix Key provided in the BTC Seller's Bisq account.\n\nPlease use your Pix Key as the payment reference so that it is easy for the BTC Seller to identify the payment as coming from yourself. +payment.pix.info.seller=Please check that the payment received description matches the Pix Key provided in the BTC Buyer's Bisq account. +payment.pix.key=Pix Key (CPF, CNPJ, Email, Phone number or UUID) + +payment.monese.info.account=Monese is a bank app for users of GBP, EUR and RON*. Monese allows users to send money to other Monese accounts instantly and for free in any supported currency.\n\n*To open a RON account in Monese, you need to either live in Romania or have Romanian citizenship.\n\nWhen setting up your Monese account in Bisq please make sure to include your name and phone number that matches your Monese account. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account. +payment.monese.info.buyer=Please send payment only to the phone number provided by the BTC Seller in their Bisq account. Please leave the payment description blank. +payment.monese.info.seller=BTC Sellers should expect to receive payment from the phone number / name shown in the BTC Buyer's Bisq account. + +payment.satispay.info.account=To use Satispay you need a bank account (IBAN) in Italy and to be registered for the service.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.satispay.info.buyer=Please send payment only to the BTC Seller's mobile phone number as provided in Bisq.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.satispay.info.seller=Please make sure your payment is received from the BTC Buyer's mobile phone number / name as provided in Bisq.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. + +payment.verse.info.account=Verse is a multiple currency payment method that can send and receive payment in EUR, SEK, HUF, DKK, PLN.\n\nWhen setting up your Verse account in Bisq please make sure to include the username that matches your username in your Verse account. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. +payment.verse.info.buyer=Please send payment only to the username provided by the BTC Seller in their Bisq account. Please leave the payment description blank.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. +payment.verse.info.seller=BTC Sellers should expect to receive payment from the username shown in the BTC Buyer's Bisq account.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. + +payment.strike.info.account=Please make sure to include your Strike username.\n\nIn Bisq, Strike is used for fiat to fiat payments only.\n\nPlease make sure you are aware of the Strike limits:\n\nUsers who have registered with only their email, name, and phone number have the following limits:\n\n● $100 maximum per deposit\n● $1,000 maximum total deposits per week\n● $100 maximum per payment\n\nUsers can increase their limits by providing Strike with more information. These users have the following limits:\n\n● $1,000 maximum per deposit\n● $1,000 maximum total deposits per week\n● $1,000 maximum per payment\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.strike.info.buyer=Please send payment only to the BTC Seller's Strike username as provided in Bisq.\n\nThe maximum trade size is $1,000 per payment.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.strike.info.seller=Please make sure your payment is received from the BTC Buyer's Strike username as provided in Bisq.\n\nThe maximum trade size is $1,000 per payment.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. payment.usPostalMoneyOrder.info=在 Bisq 上交易 US Postal Money Orders (USPMO)您必须理解下述条款:\n\n- BTC 买方必须在发送方和收款人字段中都写上 BTC 卖方的名称,并在发送之前对 USPMO 和信封进行高分辨率照片拍照,并带有跟踪证明。\n- BTC 买方必须将 USPMO 连同交货确认书一起发送给 BTC 卖方。\n\n如果需要调解,或有交易纠纷,您将需要将照片连同 USPMO 编号,邮局编号和交易金额一起发送给 Bisq 调解员或退款代理,以便他们进行验证美国邮局网站上的详细信息。\n\n如未能提供要求的交易数据将在纠纷中直接判负\n\n在所有争议案件中,USPMO 发送方在向调解人或仲裁员提供证据/证明时承担 100% 的责任。\n\n如果您不理解这些要求,请不要在 Bisq 上使用 USPMO 进行交易。 @@ -2792,6 +2847,8 @@ payment.f2f.info='Face to Face' trades have different rules and come with differ payment.f2f.info.openURL=打开网页 payment.f2f.offerbook.tooltip.countryAndCity=国家或地区及城市:{0} / {1} payment.f2f.offerbook.tooltip.extra=附加信息:{0} +payment.ifsc=IFS Code +payment.ifsc.validation=IFSC format: XXXX0999999 payment.japan.bank=银行 payment.japan.branch=分行 @@ -2800,7 +2857,7 @@ payment.japan.recipient=名称 payment.australia.payid=PayID payment.payid=PayID 需链接至金融机构。例如电子邮件地址或手机。 payment.payid.info=PayID,如电话号码、电子邮件地址或澳大利亚商业号码(ABN),您可以安全地连接到您的银行、信用合作社或建立社会帐户。你需要在你的澳大利亚金融机构创建一个 PayID。发送和接收金融机构都必须支持 PayID。更多信息请查看[HYPERLINK:https://payid.com.au/faqs/] -payment.amazonGiftCard.info=To pay with Amazon eGift Card, you will need to send an Amazon eGift Card to the BTC seller via your Amazon account. \n\nBisq will show the BTC seller''s email address or phone number where the gift card should be sent, and you must include the trade ID in the gift card''s message field. Please see the wiki [HYPERLINK:https://bisq.wiki/Amazon_eGift_card] for further details and best practices. \n\nThree important notes:\n- try to send gift cards with amounts of 100 USD or smaller, as Amazon is known to flag larger gift cards as fraudulent\n- try to use creative, believable text for the gift card''s message (e.g., "Happy birthday Susan!") along with the trade ID (and use trader chat to tell your trading peer the reference text you picked so they can verify your payment)\n- Amazon eGift Cards can only be redeemed on the Amazon website they were purchased on (e.g., a gift card purchased on amazon.it can only be redeemed on amazon.it) +payment.amazonGiftCard.info=To pay with Amazon eGift Card, you will need to send an Amazon eGift Card to the BTC seller via your Amazon account. \n\nPlease see the wiki [HYPERLINK:https://bisq.wiki/Amazon_eGift_card] for further details and best practices. \n\nThree important notes:\n- try to send gift cards with amounts of 100 USD or smaller, as Amazon is known to flag larger gift cards as fraudulent\n- try to use creative, believable text for the gift card''s message (e.g., "Happy birthday Susan!") and use trader chat to tell your trading peer the reference text you picked so they can verify your payment\n- Amazon eGift Cards can only be redeemed on the Amazon website they were purchased on (e.g., a gift card purchased on amazon.it can only be redeemed on amazon.it) # We use constants from the code so we do not use our normal naming convention # dynamic values are not recognized by IntelliJ @@ -2885,12 +2942,38 @@ PAYSERA=Paysera # suppress inspection "UnusedProperty" PAXUM=Paxum # suppress inspection "UnusedProperty" +NEFT=India/NEFT +# suppress inspection "UnusedProperty" +RTGS=India/RTGS +# suppress inspection "UnusedProperty" +IMPS=India/IMPS +# suppress inspection "UnusedProperty" +UPI=India/UPI +# suppress inspection "UnusedProperty" +PAYTM=India/PayTM +# suppress inspection "UnusedProperty" +NEQUI=Nequi +# suppress inspection "UnusedProperty" +BIZUM=Bizum +# suppress inspection "UnusedProperty" +PIX=Pix +# suppress inspection "UnusedProperty" AMAZON_GIFT_CARD=亚马逊电子礼品卡 # suppress inspection "UnusedProperty" BLOCK_CHAINS_INSTANT=Altcoins Instant # suppress inspection "UnusedProperty" CAPITUAL=Capitual # suppress inspection "UnusedProperty" +CELPAY=CelPay +# suppress inspection "UnusedProperty" +MONESE=Monese +# suppress inspection "UnusedProperty" +SATISPAY=Satispay +# suppress inspection "UnusedProperty" +VERSE=Verse +# suppress inspection "UnusedProperty" +STRIKE=Strike +# suppress inspection "UnusedProperty" SWIFT=SWIFT International Wire Transfer # Deprecated: Cannot be deleted as it would break old trade history entries @@ -2945,12 +3028,38 @@ PAYSERA_SHORT=Paysera # suppress inspection "UnusedProperty" PAXUM_SHORT=Paxum # suppress inspection "UnusedProperty" +NEFT_SHORT=NEFT +# suppress inspection "UnusedProperty" +RTGS_SHORT=RTGS +# suppress inspection "UnusedProperty" +IMPS_SHORT=IMPS +# suppress inspection "UnusedProperty" +UPI_SHORT=UPI +# suppress inspection "UnusedProperty" +PAYTM_SHORT=PayTM +# suppress inspection "UnusedProperty" +NEQUI_SHORT=Nequi +# suppress inspection "UnusedProperty" +BIZUM_SHORT=Bizum +# suppress inspection "UnusedProperty" +PIX_SHORT=Pix +# suppress inspection "UnusedProperty" AMAZON_GIFT_CARD_SHORT=亚马逊电子礼品卡 # suppress inspection "UnusedProperty" BLOCK_CHAINS_INSTANT_SHORT=Altcoins Instant # suppress inspection "UnusedProperty" CAPITUAL_SHORT=Capitual # suppress inspection "UnusedProperty" +CELPAY_SHORT=CelPay +# suppress inspection "UnusedProperty" +MONESE_SHORT=Monese +# suppress inspection "UnusedProperty" +SATISPAY_SHORT=Satispay +# suppress inspection "UnusedProperty" +VERSE_SHORT=Verse +# suppress inspection "UnusedProperty" +STRIKE_SHORT=Strike +# suppress inspection "UnusedProperty" SWIFT_SHORT=SWIFT # Deprecated: Cannot be deleted as it would break old trade history entries diff --git a/core/src/main/resources/i18n/displayStrings_zh-hant.properties b/core/src/main/resources/i18n/displayStrings_zh-hant.properties index c9032f0b013..960e2368e04 100644 --- a/core/src/main/resources/i18n/displayStrings_zh-hant.properties +++ b/core/src/main/resources/i18n/displayStrings_zh-hant.properties @@ -284,6 +284,7 @@ mainView.walletServiceErrorMsg.rejectedTxException=交易被網絡拒絕。\n\n{ mainView.networkWarning.allConnectionsLost=您失去了所有與 {0} 網絡節點的連接。\n您失去了互聯網連接或您的計算機處於待機狀態。 mainView.networkWarning.localhostBitcoinLost=您丟失了與本地主機比特幣節點的連接。\n請重啟 Bisq 應用程序連接到其他比特幣節點或重新啟動主機比特幣節點。 +mainView.networkWarning.clockWatcher=Your computer was asleep for {0} seconds. Standby mode has been known to cause trades to fail. In order to operate correctly Bisq requires that standby mode be disabled in your computer's settings. mainView.version.update=(有更新可用) @@ -821,7 +822,7 @@ portfolio.pending.mediationResult.popup.alreadyAccepted=您已經接受了。 portfolio.pending.failedTrade.taker.missingTakerFeeTx=吃單交易費未找到。\n\n如果沒有 tx,交易不能完成。沒有資金被鎖定以及沒有支付交易費用。你可以將交易移至失敗的交易。 portfolio.pending.failedTrade.maker.missingTakerFeeTx=掛單費交易未找到。\n\n如果沒有 tx,交易不能完成。沒有資金被鎖定以及沒有支付交易費用。你可以將交易移至失敗的交易。 portfolio.pending.failedTrade.missingDepositTx=這個保證金交易(2 對 2 多重簽名交易)缺失\n\n沒有該 tx,交易不能完成。沒有資金被鎖定但是您的交易手續費仍然已支出。您可以發起一個請求去賠償改交易手續費在這裏:https://github.com/bisq-network/support/issues\n\n請隨意的將該交易移至失敗交易 -portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=延遲支付交易缺失,但是資金仍然被鎖定在保證金交易中。\n\n請不要給比特幣賣家發送法幣或數字貨幣,因為沒有延遲交易 tx,不能開啟仲裁。使用 Cmd/Ctrl+o開啟調解協助。調解員應該建議交易雙方分別退回全部的保證金(賣方支付的交易金額也會全數返還)。這樣的話不會有任何的安全問題只會損失交易手續費。\n\n你可以在這裏為失敗的交易提出賠償要求:https://github.com/bisq-network/support/issues +portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the fiat or altcoin payment to the BTC seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues] portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=延遲支付交易確實但是資金仍然被鎖定在保證金交易中。\n\n如果賣家仍然缺失延遲支付交易,他會接到請勿付款的指示並開啟一個調節幫助。你也應該使用 Cmd/Ctrl+O 去打開一個調節協助\n\n如果買家還沒有發送付款,調解員應該會建議交易雙方分別退回全部的保證金(賣方支付的交易金額也會全數返還)。否則交易額應該判給買方。\n\n你可以在這裏為失敗的交易提出賠償要求:https://github.com/bisq-network/support/issues portfolio.pending.failedTrade.errorMsgSet=在處理交易協議是發生了一個錯誤\n\n錯誤:{0}\n\n這應該不是致命錯誤,您可以正常的完成交易。如果你仍擔憂,打開一個調解協助並從 Bisq 調解員處得到建議。\n\n如果這個錯誤是致命的那麼這個交易就無法完成,你可能會損失交易費。可以在這裏為失敗的交易提出賠償要求:https://github.com/bisq-network/support/issues portfolio.pending.failedTrade.missingContract=沒有設置交易合同。\n\n這個交易無法完成,你可能會損失交易手續費。可以在這裏為失敗的交易提出賠償要求:https://github.com/bisq-network/support/issues @@ -996,7 +997,9 @@ support.buyerTaker=BTC 買家/買單者 support.sellerTaker=BTC 賣家/買單者 support.backgroundInfo=Bisq 不是一家公司,所以它處理糾紛的方式不同。\n\n交易雙方可以在應用程序中通過未完成交易頁面上的安全聊天進行通信,以嘗試自行解決爭端。如果這還不夠,調解員可以介入幫助。調解員將對情況進行評估,並對交易資金的支出提出建議。如果兩個交易者都接受這個建議,那麼支付交易就完成了,交易也結束了。如果一方或雙方不同意調解員的建議,他們可以要求仲裁。仲裁員將重新評估情況,如果有必要,將親自向交易員付款,並要求 Bisq DAO 對這筆付款進行補償。 -support.initialInfo=Please enter a description of your problem in the text field below. Add as much information as possible to speed up dispute resolution time.\n\nHere is a check list for information you should provide:\n\t● If you are the BTC buyer: Did you make the Fiat or Altcoin transfer? If so, did you click the 'payment started' button in the application?\n\t● If you are the BTC seller: Did you receive the Fiat or Altcoin payment? If so, did you click the 'payment received' button in the application?\n\t● Which version of Bisq are you using?\n\t● Which operating system are you using?\n\t● If you encountered an issue with failed transactions please consider switching to a new data directory.\n\t Sometimes the data directory gets corrupted and leads to strange bugs. \n\t See: https://bisq.wiki/Switching_to_a_new_data_directory\n\nPlease make yourself familiar with the basic rules for the dispute process:\n\t● You need to respond to the {0}''s requests within 2 days.\n\t● Mediators respond in between 2 days. Arbitrators respond in between 5 business days.\n\t● The maximum period for a dispute is 14 days.\n\t● You need to cooperate with the {1} and provide the information they request to make your case.\n\t● You accepted the rules outlined in the dispute document in the user agreement when you first started the application.\n\nYou can read more about the dispute process at: {2} +support.initialInfo=Please enter a description of your problem in the text field below. Add as much information as possible to speed up dispute resolution time.\n\nHere is a check list for information you should provide:\n\t● If you are the BTC buyer: Did you make the Fiat or Altcoin transfer? If so, did you click the 'payment started' button in the application?\n\t● If you are the BTC seller: Did you receive the Fiat or Altcoin payment? If so, did you click the 'payment received' button in the application?\n\t● Which version of Bisq are you using?\n\t● Which operating system are you using?\n\t● If you encountered an issue with failed transactions please consider switching to a new data directory.\n\t Sometimes the data directory gets corrupted and leads to strange bugs. \n\t See: https://bisq.wiki/Switching_to_a_new_data_directory\n\nPlease make yourself familiar with the basic rules for the dispute process:\n\t● You need to respond to the {0}''s requests within 2 days.\n\t● {1}\n\t● The maximum period for a dispute is 14 days.\n\t● You need to cooperate with the {2} and provide the information they request to make your case.\n\t● You accepted the rules outlined in the dispute document in the user agreement when you first started the application.\n\nYou can read more about the dispute process at: {3} +support.initialMediatorMsg=Mediators will generally reply to you within 24 hours.\n\t If you have not had a reply after 48 hours please feel free to reach out to your mediator on Keybase.\n\t Mediators usernames on Keybase are the same as their usernames within the Bisq app.\n\t Your mediator is: {0} +support.initialArbitratorMsg=Arbitrators will generally reply to you within 5 days.\n\t If you have not had a reply after 7 days please feel free to reach out to your arbitrator on Keybase.\n\t Arbitrators usernames on Keybase are the same as their usernames within the Bisq app.\n\t Your arbitrator is: {0} support.systemMsg=系統消息:{0} support.youOpenedTicket=您創建了幫助請求。\n\n{0}\n\nBisq 版本:{1} support.youOpenedDispute=您創建了一個糾紛請求。\n\n{0}\n\nBisq 版本:{1} @@ -1064,6 +1067,8 @@ setting.preferences.dao.resyncFromResources.popup=應用程序重新啟動後, setting.preferences.dao.resyncFromGenesis.popup=從創始交易中出現同步會消耗大量時間以及 CPU 資源。您確定要重新同步嗎?通常,從最新資源文件進行重新同步就足夠了,而且速度更快。\n\n應用程序重新啟動後,Bisq 網絡治理數據將從種子節點重新加載,而 BSQ 同步狀態將從初始交易中重新構建。 setting.preferences.dao.resyncFromGenesis.resync=從創始區塊重新同步並關閉 setting.preferences.dao.isDaoFullNode=以 DAO 全節點運行 Bisq +setting.preferences.dao.activated=DAO activated +setting.preferences.dao.activated.popup=The change will be applied after a restart setting.preferences.dao.rpcUser=RPC 用户名 setting.preferences.dao.rpcPw=PRC 密碼 setting.preferences.dao.blockNotifyPort=區塊通知端口 @@ -1922,15 +1927,10 @@ dao.news.bisqDAO.title=Bisq DAO dao.news.bisqDAO.description=正如 Bisq交易是分散的,並且不受審查,它的治理模型也是如此—— Bisq DAO 和 BSQ 是使其成為可能的工具。 dao.news.bisqDAO.readMoreLink=瞭解有關 Bisq DAO 的更多信息 -dao.news.pastContribution.title=過去有所貢獻?申請 BSQ -dao.news.pastContribution.description=如果您對 Bisq 有貢獻,請使用下面的 BSQ 地址,並申請參與 BSQ 初始分發。 -dao.news.pastContribution.yourAddress=你的 BSQ 錢包地址 -dao.news.pastContribution.requestNow=現在申請 - -dao.news.daoInfo.title=在我們的測試網絡上運行 BISQ DAO -dao.news.daoInfo.description=核心網絡 Bisq DAO 還沒有啟動,但是您可以通過在我們的測試網絡上運行它來了解 Bisq DAO 。 -dao.news.daoInfo.firstSection.title=1.切換至 DAO 測試網絡模式 -dao.news.daoInfo.firstSection.content=從設置頁面切換到 DAO 測試網絡。 +dao.news.daoInfo.title=ENABLE THE BISQ DAO +dao.news.daoInfo.description=To participate in the Bisq DAO and to use BSQ for discounted trading fees, you need to enable the DAO. When the DAO is enabled, Bisq downloads all missing blocks and verifies BSQ transactions. This verification process requires time, during which you may see Bisq use a lot of memory and processing power. This is normal. +dao.news.daoInfo.firstSection.title=1. Enable DAO +dao.news.daoInfo.firstSection.content=Enable the Bisq DAO and restart. dao.news.DAOOnTestnet.secondSection.title=2.獲得一些 BSQ dao.news.DAOOnTestnet.secondSection.content=在 Slack 上申請 BSQ 或在 Bisq 上購買 BSQ 。 dao.news.DAOOnTestnet.thirdSection.title=3.參與投票週期 @@ -2679,6 +2679,7 @@ payment.secret=密保問題 payment.answer=答案 payment.wallet=錢包 ID payment.capitual.cap=CAP Code +payment.upi.virtualPaymentAddress=Virtual Payment Address # suppress inspection "UnusedProperty" payment.swift.headline=International SWIFT Wire Transfer @@ -2767,11 +2768,65 @@ payment.amazonGiftCard.upgrade=Amazon gift cards payment method requires the cou payment.account.amazonGiftCard.addCountryInfo={0}\nYour existing Amazon Gift Card account ({1}) does not have a Country specified.\nPlease enter your Amazon Gift Card Country to update your account data.\nThis will not affect your account age status. payment.amazonGiftCard.upgrade.headLine=Update Amazon Gift Card account -payment.swift.info=Carefully review the core guidelines for using SWIFT on Bisq:\n\n- fill all fields completely and accurately \n- buyer must send payment in currency specified by the offer maker \n- buyer must use the shared fee model (SHA) \n- buyer and seller may incur fees, so they should check their bank's fee schedules beforehand \n\nSWIFT is more sophisticated than other payment methods, so please take a moment to review full guidance on the wiki [HYPERLINK:https://bisq.wiki/SWIFT]. +payment.swift.info.account=Carefully review the core guidelines for using SWIFT on Bisq:\n\n- fill all fields completely and accurately \n- buyer must send payment in currency specified by the offer maker \n- buyer must use the shared fee model (SHA) \n- buyer and seller may incur fees, so they should check their bank's fee schedules beforehand \n\nSWIFT is more sophisticated than other payment methods, so please take a moment to review full guidance on the wiki [HYPERLINK:https://bisq.wiki/SWIFT]. payment.swift.info.buyer=To buy bitcoin with SWIFT, you must:\n\n- send payment in the currency specified by the offer maker \n- use the shared fee model (SHA) to send payment\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. -payment.swift.info.seller=SWIFT senders are required to use the shared payment model (SHA) to send payments.\n\nIf you receive a SWIFT payment that does not use SHA, open a mediation ticket.\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. +payment.swift.info.seller=SWIFT senders are required to use the shared fee model (SHA) to send payments.\n\nIf you receive a SWIFT payment that does not use SHA, open a mediation ticket.\n\nPlease review further guidance on the wiki to avoid penalties and ensure smooth trades [HYPERLINK:https://bisq.wiki/SWIFT]. + +payment.imps.info.account=Please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a maximum of Rs. 200,000 that can be sent per transaction. If you are trading over this amount multiple transactions will be needed. However be aware their is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nSome banks have different limits for their customers. +payment.imps.info.buyer=Please send payment only to the account details provided in Bisq.\n\nThe maximum trade size is Rs. 200,000 per transaction.\n\nIf your trade is over Rs. 200,000 you will have to make multiple transfers. However be aware their is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nPlease note some banks have different limits for their customers. +payment.imps.info.seller=If you intend to receive over Rs. 200,000 per trade you should expect the buyer to have to make multiple transfers. However be aware there is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\nPlease note some banks have different limits for their customers. + +payment.neft.info.account=Please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a maximum of Rs. 50,000 that can be sent per transaction. If you are trading over this amount multiple transactions will be needed.\n\nPlease note some banks have different limits for their customers. +payment.neft.info.buyer=Please send payment only to the account details provided in Bisq.\n\nThe maximum trade size is Rs. 50,000 per transaction.\n\nIf your trade is over Rs. 50,000 you will have to make multiple transfers.\n\nPlease note some banks have different limits for their customers. +payment.neft.info.seller=If you intend to receive over Rs. 50,000 per trade you should expect the buyer to have to make multiple transfers.\n\nPlease note some banks have different limits for their customers. + +payment.paytm.info.account=Please make sure to include your email or phone number that matches your email or phone number in your PayTM account. \n\nWhen users set up a PayTM account with No KYC users are limited to: \n\n● Maximum of Rs. 5,000 can be sent per transaction.\n● Maximum of Rs. 10,000 can be held in someone's PayTM wallet.\n\nIf you intend to trade amount of over 5,000 per trade you will need to complete KYC with PayTM. With KYC users are limited to:\n\n● Maximum of Rs. 100,000 can be sent per transaction.\n● Maximum of Rs. 100,000 can be held in someone's PayTM wallet.\n\nUsers should also be aware of account limits. Trades above PayTM account limits will likely have to take place over more than one day, or, be cancelled. +payment.paytm.info.buyer=Please send payment only to the email address or phone number provided.\n\nIf you intend to trade amount of over Rs. 5,000 per trade you will need to complete KYC with PayTM.\n\nWith No KYC Rs. 5,000 can be sent per transaction.\n\nWith KYC users Rs. 100,000 can be sent per transaction. +payment.paytm.info.seller=If you intend to trade amount of over Rs. 5,000 per trade you will need to complete KYC with PayTM. With KYC users are limited to:\n\n● Maximum of Rs. 100,000 can be sent per transaction.\n● Maximum of Rs. 100,000 can be held in your PayTM wallet .\n\nUsers should also be aware of account limits. As a maximum of Rs. 100,000 can be held in your PayTM wallet please make sure you transfer out your rupees regularly. + +payment.rtgs.info.account=RTGS is for payments of large trades of Rs. 200,000 or over.\n\nWhen setting up your RTGS payment account please make sure to include your:\n\n● Account owner full name\n● Account number\n● IFSC number\n\nThese details should match your bank account that you will use for sending / receiving payments.\n\nPlease be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). +payment.rtgs.info.buyer=Please send payment only to the account details provided in Bisq.\n\nPlease be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). +payment.rtgs.info.seller=Please be aware there is a minimum trade amount of Rs. 200,000 that can be sent per transaction. If you are trading under this amount either the trade would get cancelled or both traders would have to agree on another payment method (eg IMPS or UPI). + +payment.upi.info.account=Please make sure to include your Virtual Payment Address (VPA) also called your UPI ID. The format for this is like an email ID: with the sign “@” in the middle. For example, your UPI ID could be “receiver’s_name@bank_name” or “phone_number@bank_name.” \n\nFor UPI there is a maximum limit of Rs. 100,000 that can be sent per transaction. \n\nIf you intend to trade amount of over Rs. 100,000 per trade it is likely trades will have to take place over multiple transfers. \n\nPlease note some banks have different limits for their customers. +payment.upi.info.buyer=Please send payment only to the VPA / UPI ID provided in Bisq. \n\nThe maximum trade size is Rs. 100,000 per transaction. \n\nIf your trade is over Rs. 100,000 you will have to make multiple transfers. \n\nPlease note some banks have different limits for their customers. +payment.upi.info.seller=If you intend to receive over Rs. 100,000 per trade you should expect the buyer to have to make multiple transfers. \n\nPlease note some banks have different limits for their customers. + +payment.celpay.info.account=Please make sure to include the email your Celsius account is registered to. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account.\n\nCelPay users are limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Buyers can send any matching currency stablecoin to the BTC Seller. +payment.celpay.info.buyer=Please send payment only to the email address provided by the BTC Seller by sending a payment link.\n\nCelPay is limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Buyers can send any matching currency stablecoin to the BTC Seller. +payment.celpay.info.seller=BTC Sellers should expect to receive payment via a secure payment link. Please make sure the email payment link contains the email address provided by the BTC Buyer.\n\nCelPay users are limited to sending $2,500 (or other currency/crypto equivalent) in 24 hours.\n\nTrades above CelPay account limits will likely have to take place over more than one day, or, be cancelled.\n\nCelPay supports multiple stablecoins:\n\n● USD Stablecoins; DAI, TrueUSD, USDC, ZUSD, BUSD, GUSD, PAX, USDT (ERC20)\n● CAD Stablecoins; TrueCAD\n● GBP Stablecoins; TrueGBP\n● HKD Stablecoins; TrueHKD\n● AUD Stablecoins; TrueAUD\n\nBTC Sellers should expect to receive any matching currency stablecoin from the BTC Buyer. It is possible for the BTC Buyer to send any matching currency stablecoin. +payment.celpay.supportedCurrenciesForReceiver=Supported currencies (please note: all the currencies below are supported stable coins within the Celcius app. Trades are for stable coins, not fiat.) + +payment.nequi.info.account=Please make sure to include your phone number that is associated with your Nequi account.\n\nWhen users set up a Nequi account payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. +payment.nequi.info.buyer=Please send payment only to the phone number provided in the BTC Seller's Bisq account.\n\nWhen users set up a Nequi account, payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. +payment.nequi.info.seller=Please check that the payment received matches the phone number provided in the BTC Buyer's Bisq account.\n\nWhen users set up a Nequi account, payment limits are set to a maximum of ~ 7,000,000 COP that can be sent per month.\n\nIf you intend to trade amount of over 7,000,000 COP per trade you will need to complete KYC with Bancolombia and pay a fee of around 15,000 COP. After this all transactions will incur a 0.4% of tax. Please ensure you are aware of the latest taxes.\n\nUsers should also be aware of account limits. Trades above Nequi account limits will likely have to be cancelled. + +payment.bizum.info.account=To use Bizum you need a bank account (IBAN) in Spain and to be registered for the service.\n\nBizum can be used for trades between €0.50 and €1,000.\n\nThe maximum amount of transactions you can send/receive using Bizum is €2,000 Euros per day.\n\nBizum users can have a maximum of 150 operations per month.\n\nEach bank however may establish its own limits, within the above limits, for its clients.\n\nTraders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.bizum.info.buyer=Please send payment only to the BTC Seller's mobile phone number as provided in Bisq.\n\nThe maximum trade size is €1,000 per payment. The maximum amount of transactions you can send using Bizum is €2,000 Euros per day.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.bizum.info.seller=Please make sure your payment is received from the BTC Buyer's mobile phone number as provided in Bisq.\n\nThe maximum trade size is €1,000 per payment. The maximum amount of transactions you can receive using Bizum is €2,000 Euros per day.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. + +payment.pix.info.account=Please make sure to include your chosen Pix Key. There are four types of keys: CPF (Natural Persons Register) or CNPJ (National Registry of Legal Entities), e-mail address, telephone number or a random key generated by the system called a universally unique identifier (UUID). A different key must be used for each Pix account you have. Individuals can create up to five keys for each account they own.\n\nWhen trading on Bisq, BTC Buyers should use their Pix Keys as the payment description so that it is easy for the BTC Sellers to identify the payment as coming from themselves. +payment.pix.info.buyer=Please send payment only the Pix Key provided in the BTC Seller's Bisq account.\n\nPlease use your Pix Key as the payment reference so that it is easy for the BTC Seller to identify the payment as coming from yourself. +payment.pix.info.seller=Please check that the payment received description matches the Pix Key provided in the BTC Buyer's Bisq account. +payment.pix.key=Pix Key (CPF, CNPJ, Email, Phone number or UUID) + +payment.monese.info.account=Monese is a bank app for users of GBP, EUR and RON*. Monese allows users to send money to other Monese accounts instantly and for free in any supported currency.\n\n*To open a RON account in Monese, you need to either live in Romania or have Romanian citizenship.\n\nWhen setting up your Monese account in Bisq please make sure to include your name and phone number that matches your Monese account. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account. +payment.monese.info.buyer=Please send payment only to the phone number provided by the BTC Seller in their Bisq account. Please leave the payment description blank. +payment.monese.info.seller=BTC Sellers should expect to receive payment from the phone number / name shown in the BTC Buyer's Bisq account. + +payment.satispay.info.account=To use Satispay you need a bank account (IBAN) in Italy and to be registered for the service.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.satispay.info.buyer=Please send payment only to the BTC Seller's mobile phone number as provided in Bisq.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.satispay.info.seller=Please make sure your payment is received from the BTC Buyer's mobile phone number / name as provided in Bisq.\n\nSatispay account limits are individually set. If you want to trade increased amounts you will need to contact Satispay support to increase your limits. Traders on Bisq should be aware of their limits. If you trade over the above limits your trade might be cancelled and there could be a penalty. + +payment.verse.info.account=Verse is a multiple currency payment method that can send and receive payment in EUR, SEK, HUF, DKK, PLN.\n\nWhen setting up your Verse account in Bisq please make sure to include the username that matches your username in your Verse account. This will ensure that when you send funds they show from the correct account and when you receive funds they will be credited to your account.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. +payment.verse.info.buyer=Please send payment only to the username provided by the BTC Seller in their Bisq account. Please leave the payment description blank.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. +payment.verse.info.seller=BTC Sellers should expect to receive payment from the username shown in the BTC Buyer's Bisq account.\n\nVerse users are limited to sending or receiving €10,000 per year (or equivalent foreign currency amount) for accumulated payments made from or received into their payment account. This can be increased by Verse on request. + +payment.strike.info.account=Please make sure to include your Strike username.\n\nIn Bisq, Strike is used for fiat to fiat payments only.\n\nPlease make sure you are aware of the Strike limits:\n\nUsers who have registered with only their email, name, and phone number have the following limits:\n\n● $100 maximum per deposit\n● $1,000 maximum total deposits per week\n● $100 maximum per payment\n\nUsers can increase their limits by providing Strike with more information. These users have the following limits:\n\n● $1,000 maximum per deposit\n● $1,000 maximum total deposits per week\n● $1,000 maximum per payment\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.strike.info.buyer=Please send payment only to the BTC Seller's Strike username as provided in Bisq.\n\nThe maximum trade size is $1,000 per payment.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. +payment.strike.info.seller=Please make sure your payment is received from the BTC Buyer's Strike username as provided in Bisq.\n\nThe maximum trade size is $1,000 per payment.\n\nIf you trade over the above limits your trade might be cancelled and there could be a penalty. payment.usPostalMoneyOrder.info=在 Bisq 上交易 US Postal Money Orders (USPMO)您必須理解下述條款:\n\n- BTC 買方必須在發送方和收款人字段中都寫上 BTC 賣方的名稱,並在發送之前對 USPMO 和信封進行高分辨率照片拍照,並帶有跟蹤證明。\n- BTC 買方必須將 USPMO 連同交貨確認書一起發送給 BTC 賣方。\n\n如果需要調解,或有交易糾紛,您將需要將照片連同 USPMO 編號,郵局編號和交易金額一起發送給 Bisq 調解員或退款代理,以便他們進行驗證美國郵局網站上的詳細信息。\n\n如未能提供要求的交易數據將在糾紛中直接判負\n\n在所有爭議案件中,USPMO 發送方在向調解人或仲裁員提供證據/證明時承擔 100% 的責任。\n\n如果您不理解這些要求,請不要在 Bisq 上使用 USPMO 進行交易。 @@ -2792,6 +2847,8 @@ payment.f2f.info='Face to Face' trades have different rules and come with differ payment.f2f.info.openURL=打開網頁 payment.f2f.offerbook.tooltip.countryAndCity=國家或地區及城市:{0} / {1} payment.f2f.offerbook.tooltip.extra=附加信息:{0} +payment.ifsc=IFS Code +payment.ifsc.validation=IFSC format: XXXX0999999 payment.japan.bank=銀行 payment.japan.branch=分行 @@ -2800,7 +2857,7 @@ payment.japan.recipient=名稱 payment.australia.payid=PayID payment.payid=PayID 需鏈接至金融機構。例如電子郵件地址或手機。 payment.payid.info=PayID,如電話號碼、電子郵件地址或澳大利亞商業號碼(ABN),您可以安全地連接到您的銀行、信用合作社或建立社會帳户。你需要在你的澳大利亞金融機構創建一個 PayID。發送和接收金融機構都必須支持 PayID。更多信息請查看[HYPERLINK:https://payid.com.au/faqs/] -payment.amazonGiftCard.info=To pay with Amazon eGift Card, you will need to send an Amazon eGift Card to the BTC seller via your Amazon account. \n\nBisq will show the BTC seller''s email address or phone number where the gift card should be sent, and you must include the trade ID in the gift card''s message field. Please see the wiki [HYPERLINK:https://bisq.wiki/Amazon_eGift_card] for further details and best practices. \n\nThree important notes:\n- try to send gift cards with amounts of 100 USD or smaller, as Amazon is known to flag larger gift cards as fraudulent\n- try to use creative, believable text for the gift card''s message (e.g., "Happy birthday Susan!") along with the trade ID (and use trader chat to tell your trading peer the reference text you picked so they can verify your payment)\n- Amazon eGift Cards can only be redeemed on the Amazon website they were purchased on (e.g., a gift card purchased on amazon.it can only be redeemed on amazon.it) +payment.amazonGiftCard.info=To pay with Amazon eGift Card, you will need to send an Amazon eGift Card to the BTC seller via your Amazon account. \n\nPlease see the wiki [HYPERLINK:https://bisq.wiki/Amazon_eGift_card] for further details and best practices. \n\nThree important notes:\n- try to send gift cards with amounts of 100 USD or smaller, as Amazon is known to flag larger gift cards as fraudulent\n- try to use creative, believable text for the gift card''s message (e.g., "Happy birthday Susan!") and use trader chat to tell your trading peer the reference text you picked so they can verify your payment\n- Amazon eGift Cards can only be redeemed on the Amazon website they were purchased on (e.g., a gift card purchased on amazon.it can only be redeemed on amazon.it) # We use constants from the code so we do not use our normal naming convention # dynamic values are not recognized by IntelliJ @@ -2885,12 +2942,38 @@ PAYSERA=Paysera # suppress inspection "UnusedProperty" PAXUM=Paxum # suppress inspection "UnusedProperty" +NEFT=India/NEFT +# suppress inspection "UnusedProperty" +RTGS=India/RTGS +# suppress inspection "UnusedProperty" +IMPS=India/IMPS +# suppress inspection "UnusedProperty" +UPI=India/UPI +# suppress inspection "UnusedProperty" +PAYTM=India/PayTM +# suppress inspection "UnusedProperty" +NEQUI=Nequi +# suppress inspection "UnusedProperty" +BIZUM=Bizum +# suppress inspection "UnusedProperty" +PIX=Pix +# suppress inspection "UnusedProperty" AMAZON_GIFT_CARD=亞馬遜電子禮品卡 # suppress inspection "UnusedProperty" BLOCK_CHAINS_INSTANT=Altcoins Instant # suppress inspection "UnusedProperty" CAPITUAL=Capitual # suppress inspection "UnusedProperty" +CELPAY=CelPay +# suppress inspection "UnusedProperty" +MONESE=Monese +# suppress inspection "UnusedProperty" +SATISPAY=Satispay +# suppress inspection "UnusedProperty" +VERSE=Verse +# suppress inspection "UnusedProperty" +STRIKE=Strike +# suppress inspection "UnusedProperty" SWIFT=SWIFT International Wire Transfer # Deprecated: Cannot be deleted as it would break old trade history entries @@ -2945,12 +3028,38 @@ PAYSERA_SHORT=Paysera # suppress inspection "UnusedProperty" PAXUM_SHORT=Paxum # suppress inspection "UnusedProperty" +NEFT_SHORT=NEFT +# suppress inspection "UnusedProperty" +RTGS_SHORT=RTGS +# suppress inspection "UnusedProperty" +IMPS_SHORT=IMPS +# suppress inspection "UnusedProperty" +UPI_SHORT=UPI +# suppress inspection "UnusedProperty" +PAYTM_SHORT=PayTM +# suppress inspection "UnusedProperty" +NEQUI_SHORT=Nequi +# suppress inspection "UnusedProperty" +BIZUM_SHORT=Bizum +# suppress inspection "UnusedProperty" +PIX_SHORT=Pix +# suppress inspection "UnusedProperty" AMAZON_GIFT_CARD_SHORT=亞馬遜電子禮品卡 # suppress inspection "UnusedProperty" BLOCK_CHAINS_INSTANT_SHORT=Altcoins Instant # suppress inspection "UnusedProperty" CAPITUAL_SHORT=Capitual # suppress inspection "UnusedProperty" +CELPAY_SHORT=CelPay +# suppress inspection "UnusedProperty" +MONESE_SHORT=Monese +# suppress inspection "UnusedProperty" +SATISPAY_SHORT=Satispay +# suppress inspection "UnusedProperty" +VERSE_SHORT=Verse +# suppress inspection "UnusedProperty" +STRIKE_SHORT=Strike +# suppress inspection "UnusedProperty" SWIFT_SHORT=SWIFT # Deprecated: Cannot be deleted as it would break old trade history entries 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 cf47c712d4b..a3df97d74c1 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.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.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/dao/state/DaoStateSnapshotServiceTest.java b/core/src/test/java/bisq/core/dao/state/DaoStateSnapshotServiceTest.java index a9e773878ae..5e8ccefe05b 100644 --- a/core/src/test/java/bisq/core/dao/state/DaoStateSnapshotServiceTest.java +++ b/core/src/test/java/bisq/core/dao/state/DaoStateSnapshotServiceTest.java @@ -38,6 +38,7 @@ public void setup() { mock(GenesisTxInfo.class), mock(DaoStateStorageService.class), mock(DaoStateMonitoringService.class), + null, null); } diff --git a/core/src/test/java/bisq/core/offer/OfferMaker.java b/core/src/test/java/bisq/core/offer/OfferMaker.java index aa9d294c2e2..2e89ec24fb1 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 9300c01574e..4d2b03ff7b4 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 63b4e406bb6..d961f6c256a 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 void tearDown() { 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 void testStartEditOfferForActiveOffer() { null, null, null, - persistenceManager); + persistenceManager + ); AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false); @@ -95,8 +95,7 @@ public void testStartEditOfferForDeactivatedOffer() { 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 void testStartEditOfferForDeactivatedOffer() { null, null, null, - persistenceManager); + persistenceManager + ); AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false); @@ -133,10 +133,8 @@ public void testStartEditOfferForDeactivatedOffer() { 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 void testStartEditOfferForOfferThatIsCurrentlyEdited() { 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 e6a41bca6ae..5933fc0faf0 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 7d98e959238..e6340678eae 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 void testRoundtripFull() { 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 fc6a1f534db..a5632e7207e 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 @@ private static Filter filterWithReceivers(List btcFeeReceiverAddresses) 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 dee3b4a2490..1f11899bde5 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 void testUnknownEnumFix() { } } - 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 4c139e17094..d5e33036eac 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 @@ private AvailabilityResult getAvailabilityResult(String errorMessage) { } 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 e31828bbfb8..3aa453c202a 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 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 @@ public GrpcOffersService(CoreApi coreApi, GrpcExceptionHandler exceptionHandler) 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 @@ public void getOffer(GetOfferRequest req, } } + @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 @@ public void getMyOffer(GetMyOfferRequest req, } } + @Override + public void getBsqSwapOffers(GetOffersRequest req, + StreamObserver responseObserver) { + try { + List result = coreApi.getBsqSwapOffers(req.getDirection(), req.getCurrencyCode()) + .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 @@ public void getOffers(GetOffersRequest req, } } + @Override + public void getMyBsqSwapOffers(GetMyOffersRequest req, + StreamObserver responseObserver) { + try { + List result = coreApi.getMyBsqSwapOffers(req.getDirection(), req.getCurrencyCode()) + .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 @@ public void getMyOffers(GetMyOffersRequest req, } } + @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 9ac400d1008..1473f30fedf 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 @@ public void createCryptoCurrencyPaymentAccount(CreateCryptoCurrencyPaymentAccoun 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 7ffea95e6a5..5e712e253e1 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 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 @@ public GrpcTradesService(CoreApi coreApi, GrpcExceptionHandler exceptionHandler) 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/package/linux/Dockerfile b/desktop/package/linux/Dockerfile index 90e590f8c2d..ed17887ad91 100644 --- a/desktop/package/linux/Dockerfile +++ b/desktop/package/linux/Dockerfile @@ -8,7 +8,7 @@ # pull base image FROM openjdk:8-jdk -ENV version 1.7.4-SNAPSHOT +ENV version 1.7.5 RUN apt-get update && apt-get install -y --no-install-recommends openjfx && rm -rf /var/lib/apt/lists/* && apt-get install -y vim fakeroot diff --git a/desktop/package/macosx/Info.plist b/desktop/package/macosx/Info.plist index fef0b6fde5c..5cd8063e584 100644 --- a/desktop/package/macosx/Info.plist +++ b/desktop/package/macosx/Info.plist @@ -5,10 +5,10 @@ CFBundleVersion - 1.7.4 + 1.7.5 CFBundleShortVersionString - 1.7.4 + 1.7.5 CFBundleExecutable Bisq diff --git a/desktop/package/macosx/copy_dbs.sh b/desktop/package/macosx/copy_dbs.sh index 869b2b4a1cc..4f30ce639b3 100755 --- a/desktop/package/macosx/copy_dbs.sh +++ b/desktop/package/macosx/copy_dbs.sh @@ -2,7 +2,7 @@ cd $(dirname $0)/../../../ -version="1.7.4" +version="1.7.5" # Set BISQ_DIR as environment var to the path of your locally synced Bisq data directory e.g. BISQ_DIR=~/Library/Application\ Support/Bisq diff --git a/desktop/package/macosx/finalize.sh b/desktop/package/macosx/finalize.sh index 205547b0491..b1a73d3a5d2 100755 --- a/desktop/package/macosx/finalize.sh +++ b/desktop/package/macosx/finalize.sh @@ -2,7 +2,7 @@ cd ../../ -version="1.7.4-SNAPSHOT" +version="1.7.5" target_dir="releases/$version" diff --git a/desktop/package/macosx/replace_version_number.sh b/desktop/package/macosx/replace_version_number.sh index 8d22d65ad5b..06a9681d7ea 100755 --- a/desktop/package/macosx/replace_version_number.sh +++ b/desktop/package/macosx/replace_version_number.sh @@ -2,8 +2,8 @@ cd $(dirname $0)/../../../. -oldVersion=1.7.3 -newVersion=1.7.4 +oldVersion=1.7.4 +newVersion=1.7.5 find . -type f \( -name "finalize.sh" \ -o -name "create_app.sh" \ diff --git a/desktop/src/main/java/bisq/desktop/bisq.css b/desktop/src/main/java/bisq/desktop/bisq.css index 8b0b830f901..d8aac0472d6 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 d3ae4b4fba4..19fbbf08655 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.core.alert.PrivateNotificationManager; import bisq.core.locale.Res; import bisq.core.offer.Offer; -import bisq.core.trade.Trade; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.user.Preferences; import bisq.network.p2p.NodeAddress; diff --git a/desktop/src/main/java/bisq/desktop/components/PeerInfoIconSmall.java b/desktop/src/main/java/bisq/desktop/components/PeerInfoIconSmall.java index 4fa2a2b2a65..943312bec2c 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 @@ 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.bisq_v1.Trade; import bisq.core.user.Preferences; import bisq.network.p2p.NodeAddress; diff --git a/desktop/src/main/java/bisq/desktop/components/PeerInfoIconTrading.java b/desktop/src/main/java/bisq/desktop/components/PeerInfoIconTrading.java index fa063a2bf60..796f7144b79 100644 --- a/desktop/src/main/java/bisq/desktop/components/PeerInfoIconTrading.java +++ b/desktop/src/main/java/bisq/desktop/components/PeerInfoIconTrading.java @@ -23,7 +23,7 @@ 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.bisq_v1.Trade; import bisq.core.user.Preferences; import bisq.network.p2p.NodeAddress; @@ -40,7 +40,10 @@ 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 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 2933099ee19..2a72811dead 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 TradeCurrency fromString(String s) { ((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 3aa4ba93633..df2a8fd2d33 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.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 @@ else if (!paymentAccount.getTradeCurrencies().isEmpty() && paymentAccount.getTra 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 769cf10dd15..13c6bcd2641 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 @@ 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.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 9b3f23ab903..72d6a362633 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 @@ protected void initialize() { 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 @@ protected Tooltip computeValue() { 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 @@ protected Tooltip computeValue() { 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) @@ -410,7 +416,7 @@ protected Tooltip computeValue() { /////////////////////////////////////////////////////////////////////////////////////////// @Override - public void onChangeAfterBatchProcessing() { + public void onDaoStateHashesChanged() { } @Override diff --git a/desktop/src/main/java/bisq/desktop/main/MainViewModel.java b/desktop/src/main/java/bisq/desktop/main/MainViewModel.java index d495c7f3410..4b88da60a7b 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.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,8 @@ 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.CompletedBsqSwapsView; import bisq.desktop.main.presentation.AccountPresentation; import bisq.desktop.main.presentation.DaoPresentation; import bisq.desktop.main.presentation.MarketPricePresentation; @@ -64,6 +67,7 @@ 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; @@ -125,6 +129,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; @@ -173,6 +178,7 @@ public MainViewModel(BisqSetup bisqSetup, SettingsPresentation settingsPresentation, P2PService p2PService, TradeManager tradeManager, + BsqSwapTradeManager bsqSwapTradeManager, OpenOfferManager openOfferManager, Preferences preferences, PrivateNotificationManager privateNotificationManager, @@ -200,6 +206,7 @@ public MainViewModel(BisqSetup bisqSetup, this.settingsPresentation = settingsPresentation; this.p2PService = p2PService; this.tradeManager = tradeManager; + this.bsqSwapTradeManager = bsqSwapTradeManager; this.openOfferManager = openOfferManager; this.preferences = preferences; this.privateNotificationManager = privateNotificationManager; @@ -241,32 +248,48 @@ public void onSetupComplete() { 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, CompletedBsqSwapsView.class)) + .show(); + bsqSwapTradeManager.resetCompletedBsqSwapTrade(); } }); } @@ -801,9 +824,6 @@ public ObservableList getPriceFeedComboBoxItems() { 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/PriceUtil.java b/desktop/src/main/java/bisq/desktop/main/PriceUtil.java index 7901c4689cc..f28ed99f015 100644 --- a/desktop/src/main/java/bisq/desktop/main/PriceUtil.java +++ b/desktop/src/main/java/bisq/desktop/main/PriceUtil.java @@ -26,7 +26,7 @@ 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; @@ -139,7 +139,7 @@ public boolean hasMarketPrice(Offer offer) { } public Optional getMarketBasedPrice(Offer offer, - OfferPayload.Direction direction) { + OfferDirection direction) { if (offer.isUseMarketBasedPrice()) { return Optional.of(offer.getMarketPriceMargin()); } @@ -169,7 +169,7 @@ public Optional getMarketBasedPrice(Offer offer, public Optional calculatePercentage(Offer offer, double marketPrice, - OfferPayload.Direction direction) { + 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 +179,7 @@ public Optional calculatePercentage(Offer offer, 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/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 04883b53aca..da91488357a 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 @@ protected void activate() { 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 @@ public void onSaveNewAccount(PaymentAccount paymentAccount) { } 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 0a45a883847..f3c30abc368 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 @@ protected void activate() { 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 @@ public void onSaveNewAccount(PaymentAccount paymentAccount) { } 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 3e6d3a61805..baf3dd625f5 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.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.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.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.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 @@ protected void addNewAccount() { paymentMethodComboBox.setVisibleRowCount(11); paymentMethodComboBox.setPrefWidth(250); List 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/dao/DaoView.java b/desktop/src/main/java/bisq/desktop/main/dao/DaoView.java index 40b5f8082a5..b9f33a5e389 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 void initialize() { 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 764d073abb3..787a31cf0e8 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.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 @@ protected void deactivate() { /////////////////////////////////////////////////////////////////////////////////////////// @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 0d95572770b..e0c4ec30be5 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 @@ protected void activate() { assetService.updateAssetStates(); updateList(); - onUpdateAvailableConfirmedBalance(bsqWalletService.getAvailableConfirmedBalance()); + onUpdateAvailableBalance(bsqWalletService.getAvailableBalance()); payFeeButton.setOnAction((event) -> { Coin listingFee = getListingFee(); @@ -235,7 +235,7 @@ protected void deactivate() { /////////////////////////////////////////////////////////////////////////////////////////// @Override - public void onUpdateBalances(Coin availableConfirmedBalance, + public void onUpdateBalances(Coin availableBalance, Coin availableNonBsqBalance, Coin unverifiedBalance, Coin unconfirmedChangeBalance, @@ -243,7 +243,7 @@ public void onUpdateBalances(Coin availableConfirmedBalance, Coin lockupBondsBalance, Coin unlockingBondsBalance) { - onUpdateAvailableConfirmedBalance(availableConfirmedBalance); + onUpdateAvailableBalance(availableBalance); } @@ -278,8 +278,8 @@ private void createListeners() { }; } - 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 d1dbc15d75d..3a0de6d79b2 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 @@ protected void activate() { proofOfBurnService.getUpdateFlag().addListener(updateListener); bsqWalletService.addBsqBalanceListener(this); - onUpdateAvailableConfirmedBalance(bsqWalletService.getAvailableConfirmedBalance()); + onUpdateAvailableBalance(bsqWalletService.getAvailableBalance()); burnButton.setOnAction((event) -> { Coin amount = getAmountFee(); @@ -216,14 +216,14 @@ protected void deactivate() { /////////////////////////////////////////////////////////////////////////////////////////// @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 @@ private void createListeners() { 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 5b1a467968e..e82b6c22a53 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.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 @@ private void publishMyProposal(ProposalType type) { 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 d8763a94c68..1a53aa4090c 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 @@ protected void activate() { stakeInputTextField.textProperty().addListener(stakeListener); voteButton.setOnAction(e -> onVote()); - onUpdateBalances(bsqWalletService.getAvailableConfirmedBalance(), + onUpdateBalances(bsqWalletService.getAvailableBalance(), bsqWalletService.getAvailableNonBsqBalance(), bsqWalletService.getUnverifiedBalance(), bsqWalletService.getUnconfirmedChangeBalance(), @@ -272,7 +272,7 @@ protected void deactivate() { /////////////////////////////////////////////////////////////////////////////////////////// @Override - public void onUpdateBalances(Coin availableConfirmedBalance, + public void onUpdateBalances(Coin availableBalance, Coin availableNonBsqBalance, Coin unverifiedBalance, Coin unconfirmedChangeBalance, @@ -281,7 +281,7 @@ public void onUpdateBalances(Coin availableConfirmedBalance, 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/monitor/StateBlockListItem.java b/desktop/src/main/java/bisq/desktop/main/dao/monitor/StateBlockListItem.java index eff55bb1868..b7093b8cea4 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/monitor/StateBlockListItem.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/monitor/StateBlockListItem.java @@ -36,10 +36,9 @@ @Getter @EqualsAndHashCode public abstract class StateBlockListItem> { - private final StateBlock stateBlock; + protected final StateBlock stateBlock; private final Supplier height; private final String hash; - private final String prevHash; private final String numNetworkMessages; private final String numMisMatches; private final boolean isInSync; @@ -58,7 +57,6 @@ protected StateBlockListItem(StB stateBlock, IntSupplier cycleIndexSupplier) { Res.get("dao.monitor.table.cycleBlockHeight", cycleIndexSupplier.getAsInt() + 1, String.valueOf(stateBlock.getHeight())))::get; hash = Utilities.bytesAsHexString(stateBlock.getHash()); - prevHash = stateBlock.getPrevHash().length > 0 ? Utilities.bytesAsHexString(stateBlock.getPrevHash()) : "-"; numNetworkMessages = String.valueOf(stateBlock.getPeersMap().size()); int size = stateBlock.getInConflictMap().size(); numMisMatches = String.valueOf(size); diff --git a/desktop/src/main/java/bisq/desktop/main/dao/monitor/StateInConflictListItem.java b/desktop/src/main/java/bisq/desktop/main/dao/monitor/StateInConflictListItem.java index cf51c261c2a..3ca42f1474e 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/monitor/StateInConflictListItem.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/monitor/StateInConflictListItem.java @@ -38,7 +38,6 @@ public abstract class StateInConflictListItem { private final String peerAddressString; private final String height; private final String hash; - private final String prevHash; private final T stateHash; protected StateInConflictListItem(String peerAddress, T stateHash, int cycleIndex, @@ -52,7 +51,5 @@ protected StateInConflictListItem(String peerAddress, T stateHash, int cycleInde cycleIndex + 1, String.valueOf(stateHash.getHeight())); hash = Utilities.bytesAsHexString(stateHash.getHash()); - prevHash = stateHash.getPrevHash().length > 0 ? - Utilities.bytesAsHexString(stateHash.getPrevHash()) : "-"; } } diff --git a/desktop/src/main/java/bisq/desktop/main/dao/monitor/StateMonitorView.java b/desktop/src/main/java/bisq/desktop/main/dao/monitor/StateMonitorView.java index 962ca6eaf20..c85f6c997e6 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/monitor/StateMonitorView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/monitor/StateMonitorView.java @@ -99,6 +99,7 @@ public abstract class StateMonitorView resyncDaoState()); @@ -162,6 +162,13 @@ protected void deactivate() { resyncButton.setOnAction(null); } + protected void setResyncButtonBindings() { + resyncButton.visibleProperty().bind(isInConflictWithSeedNode + .or(isDaoStateBlockChainNotConnecting)); + resyncButton.managedProperty().bind(isInConflictWithSeedNode + .or(isDaoStateBlockChainNotConnecting)); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Abstract @@ -177,8 +184,6 @@ protected void deactivate() { protected abstract String getPeersTableHeader(); - protected abstract String getPrevHashTableHeader(); - protected abstract String getHashTableHeader(); protected abstract String getBlockHeightTableHeader(); @@ -272,6 +277,9 @@ protected void onDataUpdate() { } else if (isInConflictWithNonSeedNode.get()) { statusTextField.setText(Res.get("dao.monitor.isInConflictWithNonSeedNode")); statusTextField.getStyleClass().remove("dao-inConflict"); + } else if (isDaoStateBlockChainNotConnecting.get()) { + statusTextField.setText(Res.get("dao.monitor.isDaoStateBlockChainNotConnecting")); + statusTextField.getStyleClass().add("dao-inConflict"); } else { statusTextField.setText(Res.get("dao.monitor.daoStateInSync")); statusTextField.getStyleClass().remove("dao-inConflict"); @@ -353,31 +361,6 @@ public void updateItem(final BLI item, boolean empty) { tableView.getColumns().add(column); - column = new AutoTooltipTableColumn<>(getPrevHashTableHeader()); - column.setMinWidth(120); - column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue())); - column.setCellFactory( - new Callback<>() { - - @Override - public TableCell call(TableColumn column) { - return new TableCell<>() { - @Override - public void updateItem(final BLI item, boolean empty) { - super.updateItem(item, empty); - if (item != null) - setText(item.getPrevHash()); - else - setText(""); - } - }; - } - }); - column.setComparator(Comparator.comparing(BLI::getPrevHash)); - tableView.getColumns().add(column); - - column = new AutoTooltipTableColumn<>(getPeersTableHeader()); column.setMinWidth(80); column.setMaxWidth(column.getMinWidth()); @@ -464,7 +447,6 @@ public void updateItem(final BLI item, boolean empty) { tableView.getColumns().add(column); } - protected void createConflictColumns() { TableColumn column; @@ -543,30 +525,6 @@ public void updateItem(final CLI item, boolean empty) { conflictTableView.getColumns().add(column); - column = new AutoTooltipTableColumn<>(getPrevHashTableHeader()); - column.setMinWidth(120); - column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue())); - column.setCellFactory( - new Callback<>() { - @Override - public TableCell call( - TableColumn column) { - return new TableCell<>() { - @Override - public void updateItem(final CLI item, boolean empty) { - super.updateItem(item, empty); - if (item != null) - setText(item.getPrevHash()); - else - setText(""); - } - }; - } - }); - column.setComparator(Comparator.comparing(CLI::getPrevHash)); - conflictTableView.getColumns().add(column); - - column = new AutoTooltipTableColumn<>(""); column.setMinWidth(120); column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue())); diff --git a/desktop/src/main/java/bisq/desktop/main/dao/monitor/blindvotes/BlindVoteStateMonitorView.java b/desktop/src/main/java/bisq/desktop/main/dao/monitor/blindvotes/BlindVoteStateMonitorView.java index 6949fa8b020..03b3e59bd9b 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/monitor/blindvotes/BlindVoteStateMonitorView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/monitor/blindvotes/BlindVoteStateMonitorView.java @@ -149,11 +149,6 @@ protected String getPeersTableHeader() { return Res.get("dao.monitor.table.peers"); } - @Override - protected String getPrevHashTableHeader() { - return Res.get("dao.monitor.blindVote.table.prev"); - } - @Override protected String getHashTableHeader() { return Res.get("dao.monitor.blindVote.table.hash"); diff --git a/desktop/src/main/java/bisq/desktop/main/dao/monitor/daostate/DaoStateBlockListItem.java b/desktop/src/main/java/bisq/desktop/main/dao/monitor/daostate/DaoStateBlockListItem.java index 18636794709..1c9f3f949ea 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/monitor/daostate/DaoStateBlockListItem.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/monitor/daostate/DaoStateBlockListItem.java @@ -21,6 +21,7 @@ import bisq.core.dao.monitoring.model.DaoStateBlock; import bisq.core.dao.monitoring.model.DaoStateHash; +import bisq.core.locale.Res; import java.util.function.IntSupplier; @@ -35,4 +36,8 @@ class DaoStateBlockListItem extends StateBlockListItem implements DaoStateMonitoringService.Listener { private final DaoStateMonitoringService daoStateMonitoringService; + private final Preferences preferences; private ListChangeListener utxoMismatchListChangeListener; private Popup warningPopup; + private ToggleButton activationsToggle; + private boolean hasBeenDeactivated; + private final BooleanProperty useDaoStateMonitoring = new SimpleBooleanProperty(); + private final BooleanProperty reactivatedDaoStateMonitoring = new SimpleBooleanProperty(); /////////////////////////////////////////////////////////////////////////////////////////// // Constructor, lifecycle /////////////////////////////////////////////////////////////////////////////////////////// - @Inject private DaoStateMonitorView(DaoStateService daoStateService, DaoFacade daoFacade, @@ -68,20 +86,22 @@ private DaoStateMonitorView(DaoStateService daoStateService, CycleService cycleService, PeriodService periodService, SeedNodeRepository seedNodeRepository, + Preferences preferences, @Named(Config.STORAGE_DIR) File storageDir) { super(daoStateService, daoFacade, cycleService, periodService, seedNodeRepository, storageDir); this.daoStateMonitoringService = daoStateMonitoringService; + this.preferences = preferences; } @Override public void initialize() { utxoMismatchListChangeListener = c -> updateUtxoMismatches(); - FormBuilder.addTitledGroupBg(root, gridRow, 3, Res.get("dao.monitor.daoState.headline")); - + FormBuilder.addTitledGroupBg(root, gridRow, 4, Res.get("dao.monitor.daoState.headline")); statusTextField = FormBuilder.addTopLabelTextField(root, ++gridRow, Res.get("dao.monitor.state")).second; + activationsToggle = FormBuilder.addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.dao.useDaoStateMonitoring")); resyncButton = FormBuilder.addButton(root, ++gridRow, Res.get("dao.monitor.resync"), 10); super.initialize(); @@ -93,7 +113,9 @@ protected void activate() { daoStateMonitoringService.addListener(this); daoStateMonitoringService.getUtxoMismatches().addListener(utxoMismatchListChangeListener); + activationsToggle.setOnAction(ev -> onToggleActivateButton()); + updateActivationState(); updateUtxoMismatches(); } @@ -111,7 +133,7 @@ protected void deactivate() { /////////////////////////////////////////////////////////////////////////////////////////// @Override - public void onChangeAfterBatchProcessing() { + public void onDaoStateHashesChanged() { if (daoStateService.isParseBlockChainComplete()) { onDataUpdate(); } @@ -160,11 +182,6 @@ protected String getPeersTableHeader() { return Res.get("dao.monitor.table.peers"); } - @Override - protected String getPrevHashTableHeader() { - return Res.get("dao.monitor.daoState.table.prev"); - } - @Override protected String getHashTableHeader() { return Res.get("dao.monitor.daoState.table.hash"); @@ -185,16 +202,37 @@ protected String getRequestHashes() { // Override /////////////////////////////////////////////////////////////////////////////////////////// + @Override + protected void setResyncButtonBindings() { + resyncButton.visibleProperty().bind(isInConflictWithSeedNode + .or(isDaoStateBlockChainNotConnecting) + .or(reactivatedDaoStateMonitoring)); + resyncButton.managedProperty().bind(isInConflictWithSeedNode + .or(isDaoStateBlockChainNotConnecting) + .or(reactivatedDaoStateMonitoring)); + } + @Override protected void onDataUpdate() { isInConflictWithSeedNode.set(daoStateMonitoringService.isInConflictWithSeedNode()); isInConflictWithNonSeedNode.set(daoStateMonitoringService.isInConflictWithNonSeedNode()); + isDaoStateBlockChainNotConnecting.set(daoStateMonitoringService.isDaoStateBlockChainNotConnecting()); listItems.setAll(daoStateMonitoringService.getDaoStateBlockChain().stream() .map(this::getStateBlockListItem) .collect(Collectors.toList())); super.onDataUpdate(); + + if (reactivatedDaoStateMonitoring.get()) { + statusTextField.setText(Res.get("dao.monitor.reactivatedDaoStateMonitoring")); + statusTextField.getStyleClass().add("dao-inConflict"); + } + + if (!useDaoStateMonitoring.get()) { + statusTextField.setText(Res.get("dao.monitor.deactivatedDaoStateMonitoring")); + statusTextField.getStyleClass().add("dao-inConflict"); + } } @Override @@ -202,6 +240,35 @@ protected void requestHashesFromGenesisBlockHeight(String peerAddress) { daoStateMonitoringService.requestHashesFromGenesisBlockHeight(peerAddress); } + @Override + protected void createColumns() { + super.createColumns(); + + TableColumn column = new AutoTooltipTableColumn<>(Res.get("dao.monitor.table.isSelfConstructed")); + column.setMinWidth(50); + column.setMaxWidth(column.getMinWidth()); + column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue())); + column.setCellFactory( + new Callback<>() { + @Override + public TableCell call( + TableColumn column) { + return new TableCell<>() { + @Override + public void updateItem(final DaoStateBlockListItem item, boolean empty) { + super.updateItem(item, empty); + if (item != null) + setText(item.isSelfConstructed()); + else + setText(""); + } + }; + } + }); + column.setComparator(Comparator.comparing(e -> e.getStateBlock().getPeersMap().size())); + tableView.getColumns().add(2, column); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Private @@ -224,4 +291,32 @@ private void updateUtxoMismatches() { } } } + + private void onToggleActivateButton() { + boolean useDaoMonitor = preferences.isUseDaoMonitor(); + preferences.setUseDaoMonitor(!useDaoMonitor); + updateActivationState(); + } + + private void updateActivationState() { + boolean useDaoMonitor = preferences.isUseDaoMonitor(); + activationsToggle.setSelected(useDaoMonitor); + useDaoStateMonitoring.set(useDaoMonitor); + + if (useDaoMonitor) { + String key = "DaoMonitorInfo"; + if (DontShowAgainLookup.showAgain(key)) { + new Popup().information(Res.get("dao.monitor.activate.popup.info")) + .dontShowAgainId(key) + .closeButtonText(Res.get("shared.iUnderstand")) + .show(); + } + if (hasBeenDeactivated) { + reactivatedDaoStateMonitoring.set(true); + } + } else { + hasBeenDeactivated = true; + } + onDataUpdate(); + } } diff --git a/desktop/src/main/java/bisq/desktop/main/dao/monitor/proposals/ProposalStateMonitorView.java b/desktop/src/main/java/bisq/desktop/main/dao/monitor/proposals/ProposalStateMonitorView.java index f12e574694f..29cc78eb8ae 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/monitor/proposals/ProposalStateMonitorView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/monitor/proposals/ProposalStateMonitorView.java @@ -147,11 +147,6 @@ protected String getPeersTableHeader() { return Res.get("dao.monitor.table.peers"); } - @Override - protected String getPrevHashTableHeader() { - return Res.get("dao.monitor.proposal.table.prev"); - } - @Override protected String getHashTableHeader() { return Res.get("dao.monitor.proposal.table.hash"); 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 5c0ce109ae7..db95d762e22 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 void onUpdateBalances(Coin availableBalance, 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 15c52cde891..1f4f51eb335 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 @@ protected void deactivate() { } @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 void fillFromTradeData(Tuple2 tuple) { 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 @@ private void onSendBsq() { 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 @@ private void setBsqUtxoCandidates(Set candidates) { 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 @@ private void onSendBtc() { 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 a3d2bdd7834..7f52b5b8292 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.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.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 22c6f5ef92b..60b7462378b 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.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.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 @@ private BsqTxView(DaoFacade daoFacade, 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 @@ private BsqTxView(DaoFacade daoFacade, this.preferences = preferences; this.btcWalletService = btcWalletService; this.bsqBalanceUtil = bsqBalanceUtil; + this.tradableRepository = tradableRepository; + this.bsqTradeDetailsWindow = bsqTradeDetailsWindow; } @Override @@ -268,7 +277,7 @@ protected void deactivate() { /////////////////////////////////////////////////////////////////////////////////////////// @Override - public void onUpdateBalances(Coin availableConfirmedBalance, + public void onUpdateBalances(Coin availableBalance, Coin availableNonBsqBalance, Coin unverifiedBalance, Coin unconfirmedChangeBalance, @@ -359,7 +368,8 @@ private void updateList() { 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 void updateItem(final BsqTxListItem item, boolean empty) { } 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 TableCell call(TableColumn 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 void updateItem(final BsqTxListItem item, boolean empty) { setGraphic(field); } } else { - if (item.isWithdrawalToBTCWallet()) labelString = Res.get("dao.tx.withdrawnFromWallet"); @@ -539,7 +558,6 @@ public void updateItem(final BsqTxListItem item, boolean empty) { }; } }); - tableView.getColumns().add(column); } @@ -664,8 +682,19 @@ public void updateItem(final BsqTxListItem item, boolean empty) { } 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 89d0acbf090..6e5613df315 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.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 a2d80382dc2..e7f483078dd 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.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 db459d0515b..45544d09e48 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.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 4d5db041feb..ab42a4fd745 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.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 0606299547d..a94466f9cc4 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 74b24037abc..a5ee768e53f 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 e58221dac11..633e3005a32 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.TradeManager; -import bisq.core.trade.closed.ClosedTradableManager; -import bisq.core.trade.failed.FailedTradesManager; +import bisq.core.trade.bisq_v1.ClosedTradableManager; +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 fd59a7dc1ae..4a4368b01a8 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 @@ 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 @@ public boolean isRelatedToTransaction(Transaction transaction) { 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 199e01590f4..b00c283d96e 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 86b7699e2e1..3c699d9a0f0 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.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 9d0ab09ef28..d940ad5df7c 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.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 @@ @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 @@ public boolean isRelatedToTransaction(Transaction transaction) { 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 @@ private boolean isPayoutTx(Sha256Hash txId) { } 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 @@ private boolean isDepositTx(Sha256Hash txId) { } 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 @@ private boolean isDisputedPayoutTx(String txId) { } boolean isDelayedPayoutTx(String txId) { + if (isBsqSwapTrade()) + return false; + Transaction transaction = btcWalletService.getTransaction(txId); if (transaction == null) return false; @@ -142,8 +172,11 @@ boolean isDelayedPayoutTx(String txId) { }); } - 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 @@ private boolean isRefundPayoutTx(String txId) { 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 f977c80f788..d6a41f76f3b 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.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 @@ else if (txTypeOptional.get().equals(TxType.REIMBURSEMENT_REQUEST)) } } } + } 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 3a2d3e1ba2e..49e307f1b07 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.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.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 @@ @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 @@ private TransactionsView(BtcWalletService btcWalletService, WalletsSetup walletsSetup, Preferences preferences, TradeDetailsWindow tradeDetailsWindow, + BsqTradeDetailsWindow bsqTradeDetailsWindow, OfferDetailsWindow offerDetailsWindow, DisplayedTransactionsFactory displayedTransactionsFactory) { this.btcWalletService = btcWalletService; @@ -130,6 +132,7 @@ private TransactionsView(BtcWalletService btcWalletService, 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 @@ private void openAddressInBlockExplorer(TransactionsListItem item) { } 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 1af8806df29..35dbd66154d 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.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 4aa94894f8e..66f9d66171f 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.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 @@ private String getAllTradesWithReferralId() { 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 @@ private String getAllOffersWithReferralId() { .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 e17ecd20214..c15860f3852 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.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 void initialize() { VBox.setMargin(chartPane, new Insets(0, 0, 5, 0)); - Tuple4, 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 void initialize() { VBox.setMargin(bottomHBox, new Insets(-5, 0, 0, 0)); HBox.setHgrow(tupleBuy.second, Priority.ALWAYS); HBox.setHgrow(tupleSell.second, Priority.ALWAYS); - tupleBuy.second.setUserData(OfferPayload.Direction.BUY.name()); - tupleSell.second.setUserData(OfferPayload.Direction.SELL.name()); + tupleBuy.second.setUserData(OfferDirection.BUY.name()); + tupleSell.second.setUserData(OfferDirection.SELL.name()); bottomHBox.getChildren().addAll(tupleBuy.second, tupleSell.second); root.getChildren().addAll(currencyComboBoxTuple.first, chartPane, bottomHBox); @@ -250,7 +250,7 @@ public Number fromString(String string) { }); if (CurrencyUtil.isCryptoCurrency(code)) { - if (bottomHBox.getChildren().size() == 2 && bottomHBox.getChildren().get(0).getUserData().equals(OfferPayload.Direction.BUY.name())) { + if (bottomHBox.getChildren().size() == 2 && bottomHBox.getChildren().get(0).getUserData().equals(OfferDirection.BUY.name())) { bottomHBox.getChildren().get(0).toFront(); reverseTableColumns(); } @@ -263,7 +263,7 @@ public Number fromString(String string) { priceColumnLabel.set(Res.get("shared.priceWithCur", Res.getBaseCurrencyCode())); } else { - if (bottomHBox.getChildren().size() == 2 && bottomHBox.getChildren().get(0).getUserData().equals(OfferPayload.Direction.SELL.name())) { + if (bottomHBox.getChildren().size() == 2 && bottomHBox.getChildren().get(0).getUserData().equals(OfferDirection.SELL.name())) { bottomHBox.getChildren().get(0).toFront(); reverseTableColumns(); } @@ -459,7 +459,7 @@ private List> filterRight(List, 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 void updateItem(final OfferListItem offerListItem, boolean empty) { } }); - boolean isSellOffer = direction == OfferPayload.Direction.SELL; + boolean isSellOffer = direction == OfferDirection.SELL; // trader avatar TableColumn 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 68178818580..1c9c275d789 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.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 @@ private void updateChartData() { 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 @@ private void updateChartData() { 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 @@ private void updateChartData() { 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 @@ private void buildChartAndTableEntries(List sortedList, 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 545c9478365..dd86703b0a7 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.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 @@ private void update(ObservableList offerBookListItems) { 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 @@ private void update(ObservableList offerBookListItems) { 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 47675d68223..f473ec71461 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.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 BuyOfferView(ViewLoader viewLoader, 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 e071b6967b8..8642ef07d03 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.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.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 @@ protected OfferView(ViewLoader viewLoader, ArbitratorManager arbitratorManager, User user, P2PService p2PService, - OfferPayload.Direction direction) { + OfferDirection direction) { this.viewLoader = viewLoader; this.navigation = navigation; this.preferences = preferences; @@ -102,8 +108,12 @@ protected void initialize() { 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 @@ protected void initialize() { 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,34 @@ else if (removedTabs.get(0).getContent().equals(takeOfferPane)) 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,7 +187,10 @@ protected void deactivate() { root.getTabs().removeListener(tabListChangeListener); } - private String getCreateOfferTabName() { + private String getCreateOfferTabName(Class viewClass) { + if (viewClass == BsqSwapCreateOfferView.class) { + return Res.get("offerbook.bsqSwap.createOffer").toUpperCase(); + } return Res.get("offerbook.createOffer").toUpperCase(); } @@ -162,7 +202,7 @@ private void loadView(Class viewClass) { 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 +213,6 @@ private void loadView(Class viewClass) { 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 +220,29 @@ public void onTakeOffer(Offer offer) { // 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 +257,26 @@ public void onTakeOffer(Offer offer) { 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 +292,29 @@ private String getArbitrationLanguages() { 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); + this.takeOfferViewOpen = true; + this.offer = offer; + if (offer.isBsqSwapOffer()) { + this.navigation.navigateTo(MainView.class, this.getClass(), BsqSwapTakeOfferView.class); + } else { + this.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) { + this.createOfferViewOpen = true; + this.tradeCurrency = tradeCurrency; + if (tradeCurrency.getCode().equals("BSQ") && paymentMethod.isBsqSwap()) { + this.navigation.navigateTo(MainView.class, this.getClass(), + BsqSwapCreateOfferView.class); + return; + } + this.navigation.navigateTo(MainView.class, this.getClass(), CreateOfferView.class); } private void onCreateOfferViewRemoved() { @@ -268,6 +323,9 @@ private void onCreateOfferViewRemoved() { createOfferView.onClose(); createOfferView = null; } + if (bsqSwapCreateOfferView != null) { + bsqSwapCreateOfferView = null; + } offerBookView.enableCreateOfferButton(); navigation.navigateTo(MainView.class, this.getClass(), OfferBookView.class); @@ -280,12 +338,15 @@ private void onTakeOfferViewRemoved() { 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 d88fb8c9dd9..bbd16178149 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.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 SellOfferView(ViewLoader viewLoader, 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 3f2a733168b..3321e647076 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.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.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 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 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 MutableOfferDataModel(CreateOfferService createOfferService, 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 @@ private void removeListeners() { /////////////////////////////////////////////////////////////////////////////////////////// // 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 @@ private void setSuggestedSecurityDeposit(PaymentAccount paymentAccount) { 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 @@ void onCurrencySelected(TradeCurrency tradeCurrency) { } @Override - public void onUpdateBalances(Coin availableConfirmedBalance, + public void onUpdateBalances(Coin availableBalance, Coin availableNonBsqBalance, Coin unverifiedBalance, Coin unconfirmedChangeBalance, @@ -471,16 +471,16 @@ boolean isMinAmountLessOrEqualAmount() { 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 @@ void swapTradeToSavings() { } 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 @@ protected void setBuyerSecurityDeposit(double value) { this.buyerSecurityDeposit.set(value); } + void resetAddressEntry() { + btcWalletService.resetAddressEntriesForOpenOffer(offerId); + } + + /////////////////////////////////////////////////////////////////////////////////////////// // Getters /////////////////////////////////////////////////////////////////////////////////////////// @@ -769,7 +783,7 @@ boolean isBsqForFeeAvailable() { 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 f0f42d5e5d1..07a86f35e44 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.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.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.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 @@ protected void deactivate() { /////////////////////////////////////////////////////////////////////////////////////////// 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 void initWithData(OfferPayload.Direction direction, TradeCurrency tradeCu }).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 @@ private void onPlaceOffer() { 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 @@ protected void onPaymentAccountsComboBoxSelected() { 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 @@ private GridPane createInfoPopover() { 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 97% 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 4e7ea068e3b..5c37f033ffd 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,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.model.ActivatableWithDataModel; @@ -45,7 +45,7 @@ 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; @@ -70,7 +70,6 @@ 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 @@ protected void deactivate() { } 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 @@ private void createListeners() { 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 @@ private void createListeners() { 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 @@ private void applyMakerFee() { 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 @@ private void removeListeners() { // 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 @@ CoinFormatter getBtcFormatter() { } public boolean isSellOffer() { - return dataModel.getDirection() == OfferPayload.Direction.SELL; + return dataModel.getDirection() == OfferDirection.SELL; } public TradeCurrency getTradeCurrency() { @@ -991,7 +989,7 @@ public TradeCurrency getTradeCurrency() { } public String getTradeAmount() { - return FeeUtil.getTradeFeeWithFiatEquivalent(offerUtil, + return OfferViewModelUtil.getTradeFeeWithFiatEquivalent(offerUtil, dataModel.getAmount().get(), true, btcFormatter); @@ -1008,7 +1006,7 @@ public String getSecurityDepositPopOverLabel(String depositInBTC) { } public String getSecurityDepositInfo() { - return FeeUtil.getTradeFeeWithFiatEquivalentAndPercentage(offerUtil, + return OfferViewModelUtil.getTradeFeeWithFiatEquivalentAndPercentage(offerUtil, dataModel.getSecurityDeposit(), dataModel.getAmount().get(), true, @@ -1024,7 +1022,7 @@ public String getSecurityDepositWithCode() { 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 String getTradeFee() { } 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 String getMakerFeePercentage() { 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 String getFundsStructure() { } public String getTxFee() { - return FeeUtil.getTradeFeeWithFiatEquivalentAndPercentage(offerUtil, + return OfferViewModelUtil.getTradeFeeWithFiatEquivalentAndPercentage(offerUtil, dataModel.getTxFee(), dataModel.getAmount().get(), true, @@ -1235,7 +1233,7 @@ private void validateAndSetBuyerSecurityDepositToModel() { 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 c502050751e..15320c7760b 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 92698c91baa..6ce76791dd9 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 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 static String getTradeFeeWithFiatEquivalentAndPercentage(OfferUtil offerU " " + 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 dfd6a375803..f5aff0937d7 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 0bec3dcfd8a..0b7e4423c81 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 @@ 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 4d419ff1f9b..679aefc9887 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 1f326ebc714..2ea28030cb3 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; 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 96% 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 62fef3ac7ee..c3700608f34 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,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.model.ViewModel; -import bisq.desktop.main.offer.MutableOfferViewModel; +import bisq.desktop.main.offer.bisq_v1.MutableOfferViewModel; import bisq.desktop.util.validation.AltcoinValidator; import bisq.desktop.util.validation.BsqValidator; import bisq.desktop.util.validation.BtcValidator; 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 1e49d56937c..c5914b0869b 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.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.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 @@ void initWithData(Offer offer) { 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 @@ void initWithData(Offer offer) { }); 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 @@ public void onClose(boolean removeOffer) { // 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 @@ void fundFromSavingsWallet() { // Getters /////////////////////////////////////////////////////////////////////////////////////////// - OfferPayload.Direction getDirection() { + OfferDirection getDirection() { return offer.getDirection(); } @@ -460,7 +463,7 @@ long getMaxTradeLimit() { } boolean canTakeOffer() { - return GUIUtil.canCreateOrTakeOfferOrShowPopup(user, navigation) && + return GUIUtil.canCreateOrTakeOfferOrShowPopup(user, navigation, paymentAccount.getSelectedTradeCurrency()) && GUIUtil.isBootstrappedOrShowPopup(p2PService); } @@ -525,11 +528,11 @@ void calculateTotalToPay() { } 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 @@ boolean isAmountLargerThanOfferAmount() { } boolean wouldCreateDustForMaker() { - //noinspection SimplifiableIfStatement boolean result; if (amount.get() != null && offer != null) { Coin customAmount = offer.getAmount().subtract(amount.get()); @@ -673,7 +675,7 @@ public Coin getUsableBsqBalance() { // 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 af183ce5696..79ec2cbad47 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 caeab48c27c..3eb24820233 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.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.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 void initWithData(Offer offer) { model.initWithData(offer); priceAsPercentageInputBox.setVisible(offer.isUseMarketBasedPrice()); - if (model.getOffer().getDirection() == OfferPayload.Direction.SELL) { + if (model.getOffer().getDirection() == OfferDirection.SELL) { takeOfferButton.setId("buy-button-big"); takeOfferButton.updateText(Res.get("takeOffer.takeOfferButton", Res.get("shared.buy"))); nextButton.setId("buy-button"); @@ -691,7 +691,7 @@ private void addSubscriptions() { errorMessageSubscription = EasyBind.subscribe(model.errorMessage, newValue -> { 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 @@ private void addSubscriptions() { } else if (newValue && model.getTrade() != null && !model.getTrade().hasFailed()) { String key = "takeOfferSuccessInfo"; if (DontShowAgainLookup.showAgain(key)) { - UserThread.runAfter(() -> 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 @@ private void addSubscriptions() { close(); }) .onClose(this::close) - .show(), 1); + .show(); } else { close(); } @@ -966,6 +966,7 @@ private void showFeeOption() { private void addOfferAvailabilityLabel() { offerAvailabilityBusyAnimation = new BusyAnimation(false); offerAvailabilityLabel = new AutoTooltipLabel(Res.get("takeOffer.fundsBox.isOfferAvailable")); + HBox.setMargin(offerAvailabilityLabel, new Insets(6, 0, 0, 0)); buttonBox.getChildren().addAll(offerAvailabilityBusyAnimation, offerAvailabilityLabel); } diff --git a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/takeoffer/TakeOfferViewModel.java similarity index 94% rename from desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferViewModel.java rename to desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/takeoffer/TakeOfferViewModel.java index 6c8a9e76ebd..1376b9baec0 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/takeoffer/TakeOfferViewModel.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.model.ActivatableWithDataModel; @@ -23,7 +23,7 @@ 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.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 @@ boolean fundFromSavingsWallet() { 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 @@ private void applyTakerFee() { 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 @@ void onFocusOutAmountTextField(boolean oldValue, boolean newValue, String userIn 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 @@ private void applyOfferState(Offer.State state) { 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 @@ private void updateButtonDisableState() { 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 @@ CoinFormatter getBtcFormatter() { } 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 @@ public String getAmountDescription() { } 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 @@ public String getSecurityDepositWithCode() { public String getTradeFee() { if (dataModel.isCurrencyForTakerFeeBtc()) { - return FeeUtil.getTradeFeeWithFiatEquivalentAndPercentage(offerUtil, + return OfferViewModelUtil.getTradeFeeWithFiatEquivalentAndPercentage(offerUtil, dataModel.getTakerFeeInBtc(), dataModel.getAmount().get(), true, @@ -716,7 +716,7 @@ public String getTradeFee() { } 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 @@ public String getTakerFeePercentage() { 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 @@ public String getTotalToPayInfo() { } 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 00000000000..f5622dc8cc6 --- /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 00000000000..aae2f0ce689 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/BsqSwapOfferView.java @@ -0,0 +1,389 @@ +/* + * 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); +} 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 00000000000..a5c64328aa3 --- /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 00000000000..b17cccf725a --- /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 00000000000..622b90ddba2 --- /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 00000000000..cc26fa733bd --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/create_offer/BsqSwapCreateOfferView.java @@ -0,0 +1,598 @@ +/* + * 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(); + return; + } + + bsqSwapOfferDetailsWindow.onPlaceOffer(model::onPlaceOffer).show(model.dataModel.offer); + } + + @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 00000000000..998257e72ec --- /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.AltcoinValidator; +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.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 00000000000..1b85cd2f9c5 --- /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 00000000000..0abbbfa98da --- /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 00000000000..6020793fcd7 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/offer/bsq_swap/take_offer/BsqSwapTakeOfferView.java @@ -0,0 +1,611 @@ +/* + * 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.CompletedBsqSwapsView; +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; + } + + private 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(); + } + + @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, CompletedBsqSwapsView.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")); + 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 00000000000..48e86c62448 --- /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 1632e6f35b3..e8bbc457e3b 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 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 void onAdded(Offer offer) { 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 void onRemoved(Offer offer) { 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 void removeOffer(Offer offer) { 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 void removeOffer(Offer offer) { } 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 void fillOfferBookListItems() { // 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 2c56defd383..ff0f08cbc3d 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 c6f8b40ebf2..5c60d5f9b7d 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.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.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 void initialize() { protected void activate() { currencyComboBox.setCellFactory(GUIUtil.getTradeCurrencyCellFactory(Res.get("shared.oneOffer"), Res.get("shared.multipleOffers"), - (model.getDirection() == OfferPayload.Direction.BUY ? model.getSellOfferCounts() : model.getBuyOfferCounts()))); + (model.getDirection() == OfferDirection.BUY ? model.getSellOfferCounts() : model.getBuyOfferCounts()))); currencyComboBox.setConverter(new CurrencyStringConverter(currencyComboBox)); currencyComboBox.getEditor().getStyleClass().add("combo-box-editor-bold"); @@ -536,38 +540,38 @@ public void enableCreateOfferButton() { createOfferButton.setDisable(false); } - public void setDirection(OfferPayload.Direction direction) { + public void setDirection(OfferDirection direction) { model.initWithDirection(direction); ImageView iconView = new ImageView(); createOfferButton.setGraphic(iconView); - iconView.setId(direction == OfferPayload.Direction.SELL ? "image-sell-white" : "image-buy-white"); - createOfferButton.setId(direction == OfferPayload.Direction.SELL ? "sell-button-big" : "buy-button-big"); - avatarColumn.setTitle(direction == OfferPayload.Direction.SELL ? Res.get("shared.buyerUpperCase") : Res.get("shared.sellerUpperCase")); + iconView.setId(direction == OfferDirection.SELL ? "image-sell-white" : "image-buy-white"); + createOfferButton.setId(direction == OfferDirection.SELL ? "sell-button-big" : "buy-button-big"); + avatarColumn.setTitle(direction == OfferDirection.SELL ? Res.get("shared.buyerUpperCase") : Res.get("shared.sellerUpperCase")); setDirectionTitles(); } private void setDirectionTitles() { TradeCurrency selectedTradeCurrency = model.getSelectedTradeCurrency(); if (selectedTradeCurrency != null) { - OfferPayload.Direction direction = model.getDirection(); + OfferDirection direction = model.getDirection(); String offerButtonText; String code = selectedTradeCurrency.getCode(); if (model.showAllTradeCurrenciesProperty.get()) { - offerButtonText = direction == OfferPayload.Direction.BUY ? + offerButtonText = direction == OfferDirection.BUY ? Res.get("offerbook.createOfferToBuy", Res.getBaseCurrencyCode()) : Res.get("offerbook.createOfferToSell", Res.getBaseCurrencyCode()); } else if (selectedTradeCurrency instanceof FiatCurrency) { - offerButtonText = direction == OfferPayload.Direction.BUY ? + offerButtonText = direction == OfferDirection.BUY ? Res.get("offerbook.createOfferToBuy.withFiat", Res.getBaseCurrencyCode(), code) : Res.get("offerbook.createOfferToSell.forFiat", Res.getBaseCurrencyCode(), code); } else { - offerButtonText = direction == OfferPayload.Direction.BUY ? + offerButtonText = direction == OfferDirection.BUY ? Res.get("offerbook.createOfferToBuy.withCrypto", code, Res.getBaseCurrencyCode()) : Res.get("offerbook.createOfferToSell.forCrypto", code, Res.getBaseCurrencyCode()); @@ -590,13 +594,15 @@ public void onTabSelected(boolean isSelected) { private void onCreateOffer() { if (model.canCreateOrTakeOffer()) { + PaymentMethod selectedPaymentMethod = model.selectedPaymentMethod; + TradeCurrency selectedTradeCurrency = model.getSelectedTradeCurrency(); if (!model.hasPaymentAccountForCurrency()) { new Popup().headLine(Res.get("offerbook.warning.noTradingAccountForCurrency.headline")) .instruction(Res.get("offerbook.warning.noTradingAccountForCurrency.msg")) .actionButtonText(Res.get("offerbook.yesCreateOffer")) .onAction(() -> { createOfferButton.setDisable(true); - offerActionHandler.onCreateOffer(model.getSelectedTradeCurrency()); + offerActionHandler.onCreateOffer(selectedTradeCurrency, selectedPaymentMethod); }) .secondaryActionButtonText(Res.get("offerbook.setupNewAccount")) .onSecondaryAction(() -> { @@ -609,11 +615,11 @@ private void onCreateOffer() { } createOfferButton.setDisable(true); - offerActionHandler.onCreateOffer(model.getSelectedTradeCurrency()); + offerActionHandler.onCreateOffer(selectedTradeCurrency, selectedPaymentMethod); } } - private void onShowInfo(Offer offer, OfferFilter.Result result) { + private void onShowInfo(Offer offer, OfferFilterService.Result result) { switch (result) { case VALID: break; @@ -666,6 +672,9 @@ private void onShowInfo(Offer offer, OfferFilter.Result result) { "isInsufficientTradeLimit case."); } break; + case HIDE_BSQ_SWAPS_DUE_DAO_DEACTIVATED: + new Popup().warning(Res.get("offerbook.warning.hideBsqSwapsDueDaoDeactivated")).show(); + break; default: break; } @@ -673,7 +682,7 @@ private void onShowInfo(Offer offer, OfferFilter.Result result) { private void onTakeOffer(Offer offer) { if (model.canCreateOrTakeOffer()) { - if (offer.getDirection() == OfferPayload.Direction.SELL && + if (offer.getDirection() == OfferDirection.SELL && offer.getPaymentMethod().getId().equals(PaymentMethod.CASH_DEPOSIT.getId())) { new Popup().confirmation(Res.get("popup.info.cashDepositInfo", offer.getBankId())) .actionButtonText(Res.get("popup.info.cashDepositInfo.confirm")) @@ -934,15 +943,22 @@ public void updateItem(final OfferBookListItem item, boolean empty) { if (item != null && !empty) { - if (model.isOfferBanned(item.getOffer())) { + Offer offer = item.getOffer(); + if (model.isOfferBanned(offer)) { setGraphic(new AutoTooltipLabel(model.getPaymentMethod(item))); } else { - if (item.getOffer().isXmrAutoConf()) { + if (offer.isXmrAutoConf()) { field = new HyperlinkWithIcon(model.getPaymentMethod(item), AwesomeIcon.ROCKET); } else { field = new HyperlinkWithIcon(model.getPaymentMethod(item)); } - field.setOnAction(event -> 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 TableCell call( public void updateItem(final OfferBookListItem item, boolean empty) { super.updateItem(item, empty); if (item != null && !empty) { - var isSellOffer = item.getOffer().getDirection() == OfferPayload.Direction.SELL; + var isSellOffer = item.getOffer().getDirection() == OfferDirection.SELL; var deposit = isSellOffer ? item.getOffer().getBuyerSecurityDeposit() : item.getOffer().getSellerSecurityDeposit(); if (deposit == null) { @@ -1021,7 +1037,7 @@ public TableCell call(TableColumn() { 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 void updateItem(final OfferBookListItem item, boolean empty) { boolean myOffer = model.isMyOffer(offer); if (tableRow != null) { - canTakeOfferResult = model.offerFilter.canTakeOffer(offer, false); + canTakeOfferResult = model.offerFilterService.canTakeOffer(offer, false); tableRow.setOpacity(canTakeOfferResult.isValid() || myOffer ? 1 : 0.4); if (myOffer) { @@ -1070,17 +1086,17 @@ public void updateItem(final OfferBookListItem item, boolean empty) { button.setStyle(CssTheme.isDarkTheme() ? "-fx-text-fill: white" : "-fx-text-fill: #444444"); button.setOnAction(e -> 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 void updateItem(final OfferBookListItem item, boolean empty) { if (!myOffer) { if (canTakeOfferResult == null) { - canTakeOfferResult = model.offerFilter.canTakeOffer(offer, false); + canTakeOfferResult = model.offerFilterService.canTakeOffer(offer, false); } if (!canTakeOfferResult.isValid()) { diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java index ee78f2796b5..5583b5e0f0a 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java @@ -38,15 +38,15 @@ import bisq.core.monetary.Price; import bisq.core.monetary.Volume; 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.OpenOfferManager; import bisq.core.payment.PaymentAccount; import bisq.core.payment.PaymentAccountUtil; import bisq.core.payment.payload.PaymentMethod; import bisq.core.provider.price.PriceFeedService; -import bisq.core.trade.Trade; -import bisq.core.trade.closed.ClosedTradableManager; +import bisq.core.trade.bisq_v1.ClosedTradableManager; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.user.Preferences; import bisq.core.user.User; import bisq.core.util.FormattingUtils; @@ -106,7 +106,7 @@ class OfferBookViewModel extends ActivatableViewModel { final AccountAgeWitnessService accountAgeWitnessService; private final Navigation navigation; private final PriceUtil priceUtil; - final OfferFilter offerFilter; + final OfferFilterService offerFilterService; private final CoinFormatter btcFormatter; private final BsqFormatter bsqFormatter; @@ -117,7 +117,7 @@ class OfferBookViewModel extends ActivatableViewModel { private TradeCurrency selectedTradeCurrency; private final ObservableList allTradeCurrencies = FXCollections.observableArrayList(); - private OfferPayload.Direction direction; + private OfferDirection direction; final StringProperty tradeCurrencyCode = new SimpleStringProperty(); @@ -152,7 +152,7 @@ public OfferBookViewModel(User user, AccountAgeWitnessService accountAgeWitnessService, Navigation navigation, PriceUtil priceUtil, - OfferFilter offerFilter, + OfferFilterService offerFilterService, @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter, BsqFormatter bsqFormatter) { super(); @@ -168,7 +168,7 @@ public OfferBookViewModel(User user, 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 @@ public OfferBookViewModel(User user, 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 @@ protected void deactivate() { // API /////////////////////////////////////////////////////////////////////////////////////////// - void initWithDirection(OfferPayload.Direction direction) { + void initWithDirection(OfferDirection direction) { this.direction = direction; } @@ -279,7 +279,7 @@ else if (!showAllEntry) { setMarketPriceFeedCurrency(); filterOffers(); - if (direction == OfferPayload.Direction.BUY) + if (direction == OfferDirection.BUY) preferences.setBuyScreenCurrencyCode(code); else preferences.setSellScreenCurrencyCode(code); @@ -341,7 +341,7 @@ boolean isMyOffer(Offer offer) { return openOfferManager.isMyOffer(offer); } - OfferPayload.Direction getDirection() { + OfferDirection getDirection() { return direction; } @@ -566,7 +566,7 @@ boolean hasPaymentAccountForCurrency() { } boolean canCreateOrTakeOffer() { - return GUIUtil.canCreateOrTakeOfferOrShowPopup(user, navigation) && + return GUIUtil.canCreateOrTakeOfferOrShowPopup(user, navigation, selectedTradeCurrency) && GUIUtil.isChainHeightSyncedWithinToleranceOrShowPopup(walletsSetup) && GUIUtil.isBootstrappedOrShowPopup(p2PService); } @@ -600,11 +600,11 @@ private Predicate getOffersMatchingMyAccountsPredicate() { // 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 @@ public String getMakerFeeAsString(Offer offer) { 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 @@ private TradeCurrency getEditEntryForCurrency() { 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 e0aa0938661..b6f26c0dd29 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.locale.GlobalSettings; import bisq.core.locale.Res; import bisq.core.offer.Offer; -import bisq.core.trade.Trade; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.user.Preferences; import bisq.common.UserThread; @@ -251,7 +251,7 @@ private void addContent() { UserThread.runAfter(() -> { PubKeyRing peersPubKeyRing = null; if (trade != null) { - peersPubKeyRing = trade.getProcessModel().getTradingPeer().getPubKeyRing(); + peersPubKeyRing = trade.getProcessModel().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 994a9768675..b86f87a82da 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.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 493f983ec4a..0d287d40d44 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 @@ private void addContent() { 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 00000000000..d2bc5d0587f --- /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) { + if (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 00000000000..22378bd2645 --- /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.getAmount()); + 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.getMakerFee())) + + " / " + Res.get("shared.takerTxFee", bsqFormatter.formatCoinWithCode(bsqSwapTrade.getTakerFee())); + 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 9d5c77b650e..12c675e2f47 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 @@ protected void setupKeyHandler(Scene scene) { 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 @@ private void doEmptyWallet2(KeyParameter aesKey) { 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 c51d9ed8bac..fca374a9b81 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.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 dd632b0e082..ffbba10522e 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.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 017ec644d3f..a2fe76adb90 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 @@ private void addContent() { 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 @@ private void addContent() { 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 @@ private void addContent() { 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 @@ private void addContent() { // 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 531469bb172..55471788da6 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.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 @@ private void addContent() { 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 @@ private void addContent() { 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 @@ private void addContent() { 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 @@ private void addConfirmAndCancelButtons(boolean isPlaceOffer) { 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 bab6e7e1c2d..36c32ee3220 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 PaymentMethod fromString(String s) { private List 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 d652aa067cd..79371c20d93 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.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; 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 332133d61cc..aa9ab9d9060 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.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; @@ -324,7 +324,7 @@ private void addContent() { 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 1050c991f16..33d0019db68 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/PortfolioView.fxml +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/PortfolioView.fxml @@ -28,5 +28,6 @@ + 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 52809d5b192..f1f24edda58 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.FxmlView; import bisq.desktop.common.view.View; import bisq.desktop.main.MainView; +import bisq.desktop.main.portfolio.bsqswaps.CompletedBsqSwapsView; 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.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 @@ 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 void initialize() { 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 @@ else if (newValue == pendingTradesTab) 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, CompletedBsqSwapsView.class); else if (newValue == failedTradesTab) navigation.navigateTo(MainView.class, PortfolioView.class, FailedTradesView.class); else if (newValue == editOpenOfferTab) @@ -163,6 +167,8 @@ else if (root.getSelectionModel().getSelectedItem() == pendingTradesTab) 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, CompletedBsqSwapsView.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 @@ private void loadView(Class viewClass, @Nullable Object data) { currentTab = pendingTradesTab; } else if (view instanceof ClosedTradesView) { currentTab = closedTradesTab; + } else if (view instanceof CompletedBsqSwapsView) { + currentTab = bsqSwapTradesTab; } else if (view instanceof FailedTradesView) { currentTab = failedTradesTab; } else if (view instanceof EditOfferView) { @@ -218,7 +226,7 @@ private void loadView(Class viewClass, @Nullable Object data) { 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/CompletedBsqSwapsDataModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/bsqswaps/CompletedBsqSwapsDataModel.java new file mode 100644 index 00000000000..90f090cd675 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/bsqswaps/CompletedBsqSwapsDataModel.java @@ -0,0 +1,82 @@ +/* + * 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.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 CompletedBsqSwapsDataModel extends ActivatableDataModel { + + // final ClosedTradableManager closedTradableManager; + final BsqSwapTradeManager bsqSwapTradeManager; + private final BsqWalletService bsqWalletService; + private final ObservableList list = FXCollections.observableArrayList(); + private final ListChangeListener tradesListChangeListener; + + @Inject + public CompletedBsqSwapsDataModel(BsqSwapTradeManager bsqSwapTradeManager, BsqWalletService bsqWalletService) { + this.bsqSwapTradeManager = bsqSwapTradeManager; + this.bsqWalletService = bsqWalletService; + + tradesListChangeListener = change -> applyList(); + } + + @Override + protected void activate() { + applyList(); + bsqSwapTradeManager.getObservableList().addListener(tradesListChangeListener); + } + + @Override + protected void deactivate() { + bsqSwapTradeManager.getObservableList().removeListener(tradesListChangeListener); + } + + 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.getObservableList().stream() + .map(bsqSwapTrade -> new CompletedBsqSwapsListItem(bsqWalletService, bsqSwapTrade)) + .collect(Collectors.toList())); + + // we sort by date, 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/CompletedBsqSwapsListItem.java b/desktop/src/main/java/bisq/desktop/main/portfolio/bsqswaps/CompletedBsqSwapsListItem.java new file mode 100644 index 00000000000..1aab83917f7 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/bsqswaps/CompletedBsqSwapsListItem.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.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; + +class CompletedBsqSwapsListItem { + @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; + + CompletedBsqSwapsListItem(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); + } + + CompletedBsqSwapsListItem() { + bsqSwapTrade = null; + bsqWalletService = null; + txId = null; + + } + + private void updateConfidence(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/CompletedBsqSwapsView.fxml b/desktop/src/main/java/bisq/desktop/main/portfolio/bsqswaps/CompletedBsqSwapsView.fxml new file mode 100644 index 00000000000..970169a4452 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/bsqswaps/CompletedBsqSwapsView.fxml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/bsqswaps/CompletedBsqSwapsView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/bsqswaps/CompletedBsqSwapsView.java new file mode 100644 index 00000000000..382840ad8a7 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/bsqswaps/CompletedBsqSwapsView.java @@ -0,0 +1,587 @@ +/* + * 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 CompletedBsqSwapsView 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 CompletedBsqSwapsView(CompletedBsqSwapsViewModel 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 CompletedBsqSwapsListItem(), 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; + } + + 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 CompletedBsqSwapsListItem 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 CompletedBsqSwapsListItem 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 CompletedBsqSwapsListItem 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 CompletedBsqSwapsListItem 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 CompletedBsqSwapsListItem 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 CompletedBsqSwapsListItem 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 CompletedBsqSwapsListItem 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 CompletedBsqSwapsListItem 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 CompletedBsqSwapsListItem 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 CompletedBsqSwapsListItem 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 CompletedBsqSwapsListItem 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/CompletedBsqSwapsViewModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/bsqswaps/CompletedBsqSwapsViewModel.java new file mode 100644 index 00000000000..243945d8c4b --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/bsqswaps/CompletedBsqSwapsViewModel.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.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.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; + +import java.util.stream.Collectors; + +class CompletedBsqSwapsViewModel extends ActivatableWithDataModel implements ViewModel { + private final BsqFormatter bsqFormatter; + private final CoinFormatter btcFormatter; + final AccountAgeWitnessService accountAgeWitnessService; + + @Inject + public CompletedBsqSwapsViewModel(CompletedBsqSwapsDataModel dataModel, + AccountAgeWitnessService accountAgeWitnessService, + BsqFormatter bsqFormatter, + @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter) { + super(dataModel); + this.accountAgeWitnessService = accountAgeWitnessService; + this.bsqFormatter = bsqFormatter; + this.btcFormatter = btcFormatter; + } + + public ObservableList getList() { + return dataModel.getList(); + } + + String getTradeId(CompletedBsqSwapsListItem item) { + return item.getBsqSwapTrade().getShortId(); + } + + String getAmount(CompletedBsqSwapsListItem item) { + if (item == null) + return ""; + + return btcFormatter.formatCoin(Coin.valueOf(item.getBsqSwapTrade().getAmount())); + } + + String getPrice(CompletedBsqSwapsListItem item) { + if (item == null) + return ""; + + return FormattingUtils.formatPrice(item.getBsqSwapTrade().getPrice()); + } + + String getVolume(CompletedBsqSwapsListItem item) { + if (item == null) + return ""; + + return VolumeUtil.formatVolumeWithCode(item.getBsqSwapTrade().getVolume()); + } + + String getTxFee(CompletedBsqSwapsListItem item) { + if (item == null) + return ""; + + return btcFormatter.formatCoinWithCode(Coin.valueOf(item.getBsqSwapTrade().getBsqSwapProtocolModel().getTxFee())); + } + + String getTradeFee(CompletedBsqSwapsListItem item) { + if (item == null) + return ""; + + if (wasMyOffer(item.getBsqSwapTrade())) { + return bsqFormatter.formatCoinWithCode(item.getBsqSwapTrade().getMakerFee()); + } else { + return bsqFormatter.formatCoinWithCode(item.getBsqSwapTrade().getTakerFee()); + } + } + + String getDirectionLabel(CompletedBsqSwapsListItem item) { + if (item == null) + return ""; + + return DisplayUtils.getDirectionWithCode(dataModel.getDirection(item.getBsqSwapTrade().getOffer()), + item.getBsqSwapTrade().getOffer().getCurrencyCode()); + } + + String getDate(CompletedBsqSwapsListItem item) { + return DisplayUtils.formatDateTime(item.getBsqSwapTrade().getDate()); + } + + String getMarketLabel(CompletedBsqSwapsListItem item) { + if ((item == null)) + return ""; + + return CurrencyUtil.getCurrencyPair(item.getBsqSwapTrade().getOffer().getCurrencyCode()); + } + + int getConfidence(CompletedBsqSwapsListItem item) { + if ((item == null)) + return 0; + return item.getConfirmations(); + } + + int getNumPastTrades(BsqSwapTrade bsqSwapTrade) { + // TODO(sq): include closed trades in count + return dataModel.bsqSwapTradeManager.getObservableList().stream() + .filter(e -> { + var candidate = e.getTradingPeerNodeAddress(); + var current = bsqSwapTrade.getTradingPeerNodeAddress(); + return candidate != null && + current != null && + candidate.getFullAddress().equals(current.getFullAddress()); + }) + .collect(Collectors.toSet()) + .size(); + } + + boolean wasMyOffer(BsqSwapTrade bsqSwapTrade) { + return dataModel.bsqSwapTradeManager.wasMyOffer(bsqSwapTrade.getOffer()); + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradableListItem.java b/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradableListItem.java index 602b4148cfa..6b85a12ca7c 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradableListItem.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradableListItem.java @@ -17,7 +17,7 @@ package bisq.desktop.main.portfolio.closedtrades; -import bisq.core.trade.Tradable; +import bisq.core.trade.model.Tradable; /** * We could remove that wrapper if it is not needed for additional UI only fields. 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 76026c6da47..e5d15cc0461 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 @@ -20,15 +20,18 @@ import bisq.desktop.common.model.ActivatableDataModel; import bisq.desktop.main.PriceUtil; +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.offer.OpenOffer; 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.bisq_v1.ClosedTradableManager; +import bisq.core.trade.bisq_v1.ClosedTradeUtil; +import bisq.core.trade.model.Tradable; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.user.Preferences; import bisq.core.util.VolumeUtil; @@ -50,17 +53,28 @@ class ClosedTradesDataModel extends ActivatableDataModel { final ClosedTradableManager closedTradableManager; private final ClosedTradeUtil closedTradeUtil; + private final BsqWalletService bsqWalletService; private final Preferences preferences; private final PriceFeedService priceFeedService; private final ObservableList list = FXCollections.observableArrayList(); private final ListChangeListener tradesListChangeListener; + /** + * 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()); + @Inject public ClosedTradesDataModel(ClosedTradableManager closedTradableManager, + BsqWalletService bsqWalletService, ClosedTradeUtil closedTradeUtil, Preferences preferences, PriceFeedService priceFeedService) { this.closedTradableManager = closedTradableManager; + this.bsqWalletService = bsqWalletService; this.closedTradeUtil = closedTradeUtil; this.preferences = preferences; this.priceFeedService = priceFeedService; @@ -83,15 +97,7 @@ public 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) { + public OfferDirection getDirection(Offer offer) { return closedTradableManager.wasMyOffer(offer) ? offer.getDirection() : offer.getMirroredDirection(); } @@ -104,6 +110,10 @@ private void applyList() { list.sort((o1, o2) -> o2.getTradable().getDate().compareTo(o1.getTradable().getDate())); } + boolean wasMyOffer(Tradable tradable) { + return closedTradableManager.wasMyOffer(tradable.getOffer()); + } + Coin getTotalAmount() { return closedTradeUtil.getTotalAmount(tradableList.get()); } @@ -139,4 +149,42 @@ public Coin getTotalTxFee() { public Coin getTotalTradeFee(boolean expectBtcFee) { return closedTradeUtil.getTotalTradeFee(tradableList.get(), expectBtcFee); } + + 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/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesView.java index b4be92daf25..f0ad654c869 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 @@ -38,11 +38,11 @@ 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.bisq_v1.Contract; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.user.Preferences; import bisq.network.p2p.NodeAddress; @@ -256,7 +256,7 @@ public void initialize() { MenuItem editItem = new MenuItem(Res.get("portfolio.context.offerLikeThis")); editItem.setOnAction((event) -> { try { - OfferPayload offerPayload = row.getItem().getTradable().getOffer().getOfferPayload(); + OfferPayload offerPayload = row.getItem().getTradable().getOffer().getOfferPayload().orElseThrow(); if (offerPayload.getPubKeyRing().equals(keyRing.getPubKeyRing())) { navigation.navigateToWithData(offerPayload, MainView.class, PortfolioView.class, DuplicateOfferView.class); } else { @@ -424,7 +424,8 @@ private void applyFilteredListPredicate(String filterString) { if (offer.getPaymentMethod().getDisplayString().contains(filterString)) { return true; } - if (offer.getOfferFeePaymentTxId().contains(filterString)) { + if (offer.getOfferFeePaymentTxId() != null && + offer.getOfferFeePaymentTxId().contains(filterString)) { return true; } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesViewModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesViewModel.java index 9b359cbaad5..2c9c10106d3 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesViewModel.java @@ -23,8 +23,8 @@ import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.monetary.Volume; -import bisq.core.trade.Tradable; -import bisq.core.trade.closed.ClosedTradeUtil; +import bisq.core.trade.bisq_v1.ClosedTradeUtil; +import bisq.core.trade.model.Tradable; import org.bitcoinj.core.Coin; 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 b713ed112e7..5818f2cc231 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 @@ 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 ca1b1a4232b..db6eb38ba19 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 @@ 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 @@ protected void doActivate() { } public void initWithData(OfferPayload offerPayload) { - initWithData(offerPayload.getDirection(), CurrencyUtil.getTradeCurrency(offerPayload.getCurrencyCode()).get()); + initWithData(offerPayload.getDirection(), + CurrencyUtil.getTradeCurrency(offerPayload.getCurrencyCode()).get(), + null); model.initWithData(offerPayload); } } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/duplicateoffer/DuplicateOfferViewModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/duplicateoffer/DuplicateOfferViewModel.java index 76f4f6e711d..756bd7f6de4 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/duplicateoffer/DuplicateOfferViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/duplicateoffer/DuplicateOfferViewModel.java @@ -18,7 +18,7 @@ package bisq.desktop.main.portfolio.duplicateoffer; import bisq.desktop.Navigation; -import bisq.desktop.main.offer.MutableOfferViewModel; +import bisq.desktop.main.offer.bisq_v1.MutableOfferViewModel; import bisq.desktop.util.validation.AltcoinValidator; import bisq.desktop.util.validation.BsqValidator; import bisq.desktop.util.validation.BtcValidator; @@ -28,8 +28,8 @@ import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload; import bisq.core.offer.OfferUtil; +import bisq.core.offer.bisq_v1.OfferPayload; import bisq.core.provider.price.PriceFeedService; import bisq.core.user.Preferences; import bisq.core.util.FormattingUtils; diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModel.java index abf533c4723..34dc0dfa0b3 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModel.java @@ -19,7 +19,7 @@ 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; @@ -27,13 +27,14 @@ import bisq.core.btc.wallet.Restrictions; import bisq.core.locale.CurrencyUtil; import bisq.core.locale.TradeCurrency; -import bisq.core.offer.CreateOfferService; -import bisq.core.offer.MutableOfferPayloadFields; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferDirection; 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.payment.PaymentAccount; import bisq.core.proto.persistable.CorePersistenceProtoResolver; import bisq.core.provider.fee.FeeService; @@ -147,7 +148,7 @@ public void applyOpenOffer(OpenOffer openOffer) { } @Override - public boolean initWithData(OfferPayload.Direction direction, TradeCurrency tradeCurrency) { + public boolean initWithData(OfferDirection direction, TradeCurrency tradeCurrency) { try { return super.initWithData(direction, tradeCurrency); } catch (NullPointerException e) { @@ -183,10 +184,15 @@ public void onStartEditOffer(ErrorMessageHandler errorMessageHandler) { } public void onPublishOffer(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { - MutableOfferPayloadFields mutableOfferPayloadFields = - new MutableOfferPayloadFields(createAndGetOffer().getOfferPayload()); - final OfferPayload editedPayload = offerUtil.getMergedOfferPayload(openOffer, mutableOfferPayloadFields); - final Offer editedOffer = new Offer(editedPayload); + Offer offer = createAndGetOffer(); + if (offer.isBsqSwapOffer()) { + return; + } + + OfferPayload offerPayload = offer.getOfferPayload().orElseThrow(); + var mutableOfferPayloadFields = new MutableOfferPayloadFields(offerPayload); + OfferPayload editedPayload = offerUtil.getMergedOfferPayload(openOffer, mutableOfferPayloadFields); + Offer editedOffer = new Offer(editedPayload); editedOffer.setPriceFeedService(priceFeedService); editedOffer.setState(Offer.State.AVAILABLE); openOfferManager.editOpenOfferPublish(editedOffer, triggerPrice, initialState, () -> { 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 2e1f80ca00b..3fe8ae92383 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.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 void applyOpenOffer(OpenOffer openOffer) { 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 d4aab63268e..7f426770471 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 @@ -19,7 +19,7 @@ import bisq.desktop.Navigation; import bisq.desktop.main.PriceUtil; -import bisq.desktop.main.offer.MutableOfferViewModel; +import bisq.desktop.main.offer.bisq_v1.MutableOfferViewModel; import bisq.desktop.util.validation.AltcoinValidator; import bisq.desktop.util.validation.BsqValidator; import bisq.desktop.util.validation.BtcValidator; 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 defd7613622..ea658e1f935 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 @@ 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 @@ public ObservableList getList() { 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 f16365e1d2e..52973a2316d 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 bb51a1af07e..d4e97b02b97 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.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; @@ -273,7 +273,8 @@ private void applyFilteredListPredicate(String filterString) { if (model.getDirectionLabel(item).contains(filterString)) { return true; } - if (offer.getOfferFeePaymentTxId().contains(filterString)) { + if (offer.getOfferFeePaymentTxId() != null && + offer.getOfferFeePaymentTxId().contains(filterString)) { return true; } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersDataModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersDataModel.java index 8d71374f7d4..752d52d8467 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersDataModel.java @@ -20,10 +20,11 @@ import bisq.desktop.common.model.ActivatableDataModel; 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.TriggerPriceService; +import bisq.core.offer.bisq_v1.TriggerPriceService; +import bisq.core.offer.bsq_swap.OpenBsqSwapOfferService; import bisq.core.provider.price.PriceFeedService; import bisq.common.handlers.ErrorMessageHandler; @@ -41,6 +42,7 @@ class OpenOffersDataModel extends ActivatableDataModel { private final OpenOfferManager openOfferManager; + private final OpenBsqSwapOfferService openBsqSwapOfferService; private final PriceFeedService priceFeedService; private final ObservableList 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 @@ protected void deactivate() { 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 @@ public ObservableList getList() { 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 @@ private void applyList() { } 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 4c08adef0b8..692d0339a15 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.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.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.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 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 void initialize() { MenuItem editItem = new MenuItem(Res.get("portfolio.context.offerLikeThis")); editItem.setOnAction((event) -> { 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 @@ private void applyFilteredListPredicate(String filterString) { if (model.getDirectionLabel(item).contains(filterString)) { return true; } - if (offer.getOfferFeePaymentTxId().contains(filterString)) { + if (offer.getOfferFeePaymentTxId() != null && + offer.getOfferFeePaymentTxId().contains(filterString)) { return true; } return false; @@ -442,10 +452,16 @@ public TableCell call(TableColumn 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 void updateItem(final OpenOfferListItem item, boolean empty) { super.updateItem(item, empty); getStyleClass().removeAll("offer-disabled"); if (item != null) { - if (model.isDeactivated(item)) getStyleClass().add("offer-disabled"); + if (model.isNotPublished(item)) getStyleClass().add("offer-disabled"); setGraphic(new AutoTooltipLabel(model.getDate(item))); } else { setGraphic(null); @@ -497,7 +513,7 @@ public void updateItem(final OpenOfferListItem item, boolean empty) { getStyleClass().removeAll("offer-disabled"); if (item != null) { - if (model.isDeactivated(item)) getStyleClass().add("offer-disabled"); + if (model.isNotPublished(item)) getStyleClass().add("offer-disabled"); setGraphic(new AutoTooltipLabel(model.getAmount(item))); } else { setGraphic(null); @@ -522,7 +538,7 @@ public void updateItem(final OpenOfferListItem item, boolean empty) { getStyleClass().removeAll("offer-disabled"); if (item != null) { - if (model.isDeactivated(item)) getStyleClass().add("offer-disabled"); + if (model.isNotPublished(item)) getStyleClass().add("offer-disabled"); setGraphic(new AutoTooltipLabel(model.getPrice(item))); } else { setGraphic(null); @@ -547,7 +563,7 @@ public void updateItem(final OpenOfferListItem item, boolean empty) { getStyleClass().removeAll("offer-disabled"); if (item != null) { - if (model.isDeactivated(item)) getStyleClass().add("offer-disabled"); + if (model.isNotPublished(item)) getStyleClass().add("offer-disabled"); AutoTooltipLabel autoTooltipLabel = new AutoTooltipLabel(model.getPriceDeviation(item)); autoTooltipLabel.setOpacity(item.getOffer().isUseMarketBasedPrice() ? 1 : 0.4); setGraphic(autoTooltipLabel); @@ -573,7 +589,7 @@ public void updateItem(final OpenOfferListItem item, boolean empty) { super.updateItem(item, empty); getStyleClass().removeAll("offer-disabled"); if (item != null) { - if (model.isDeactivated(item)) getStyleClass().add("offer-disabled"); + if (model.isNotPublished(item)) getStyleClass().add("offer-disabled"); setGraphic(new AutoTooltipLabel(model.getTriggerPrice(item))); } else { setGraphic(null); @@ -598,7 +614,7 @@ public void updateItem(final OpenOfferListItem item, boolean empty) { getStyleClass().removeAll("offer-disabled"); if (item != null) { - if (model.isDeactivated(item)) getStyleClass().add("offer-disabled"); + if (model.isNotPublished(item)) getStyleClass().add("offer-disabled"); setGraphic(new AutoTooltipLabel(model.getVolume(item))); } else { setGraphic(null); @@ -623,7 +639,7 @@ public void updateItem(final OpenOfferListItem item, boolean empty) { getStyleClass().removeAll("offer-disabled"); if (item != null) { - if (model.isDeactivated(item)) getStyleClass().add("offer-disabled"); + if (model.isNotPublished(item)) getStyleClass().add("offer-disabled"); setGraphic(new AutoTooltipLabel(model.getPaymentMethod(item))); } else { setGraphic(null); @@ -648,7 +664,7 @@ public void updateItem(final OpenOfferListItem item, boolean empty) { getStyleClass().removeAll("offer-disabled"); if (item != null) { - if (model.isDeactivated(item)) getStyleClass().add("offer-disabled"); + if (model.isNotPublished(item)) getStyleClass().add("offer-disabled"); setGraphic(new AutoTooltipLabel(model.getDirectionLabel(item))); } else { setGraphic(null); @@ -673,7 +689,7 @@ public void updateItem(final OpenOfferListItem item, boolean empty) { getStyleClass().removeAll("offer-disabled"); if (item != null) { - if (model.isDeactivated(item)) getStyleClass().add("offer-disabled"); + if (model.isNotPublished(item)) getStyleClass().add("offer-disabled"); setGraphic(new AutoTooltipLabel(model.getMarketLabel(item))); } else { setGraphic(null); @@ -824,12 +840,27 @@ public void updateItem(final OpenOfferListItem item, boolean empty) { super.updateItem(item, empty); if (item != null && !empty) { - if (button == null) { - button = getRegularIconButton(MaterialDesignIcon.PENCIL); - button.setTooltip(new Tooltip(Res.get("shared.editOffer"))); - setGraphic(button); + if (item.getOffer().isBsqSwapOffer()) { + if (button != null) { + button.setOnAction(null); + button = null; + } + if (item.getOpenOffer().isBsqSwapOfferHasMissingFunds()) { + Label label = new Label(); + Text icon = getRegularIconForLabel(MaterialDesignIcon.EYE_OFF, label); + Tooltip.install(icon, new Tooltip(Res.get("openOffer.bsqSwap.missingFunds"))); + setGraphic(icon); + } else { + setGraphic(null); + } + } else { + if (button == null) { + button = getRegularIconButton(MaterialDesignIcon.PENCIL); + button.setTooltip(new Tooltip(Res.get("shared.editOffer"))); + button.setOnAction(event -> 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 9a2e12b7d1d..ccf2567d302 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 @@ -160,8 +160,21 @@ String getDate(OpenOfferListItem item) { 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 b43a4ca52d9..818c8a189db 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.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.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; @@ -380,7 +380,9 @@ public String getReference() { 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 @@ private void doOpenDispute(boolean isSupportTicket, Transaction depositTx) { 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 @@ private void doOpenDispute(boolean isSupportTicket, Transaction depositTx) { 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 fbc3035b1b1..7d83e636924 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 @@ 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; 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 a0ef8130d67..3dba7186c9f 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.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; @@ -235,7 +235,7 @@ public void initialize() { MenuItem editItem = new MenuItem(Res.get("portfolio.context.offerLikeThis")); editItem.setOnAction((event) -> { 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 @@ private void updateMoveTradeToFailedColumnState() { private boolean isMaybeInvalidTrade(Trade trade) { return trade.hasErrorMessage() || - (Trade.Phase.DEPOSIT_PUBLISHED.ordinal() <= trade.getPhase().ordinal() && trade.isTxChainInvalid()); + (Trade.Phase.DEPOSIT_PUBLISHED.ordinal() <= trade.getTradePhase().ordinal() && trade.isTxChainInvalid()); } private void onMoveInvalidTradeToFailedTrades(Trade trade) { @@ -389,7 +389,7 @@ private void onMoveInvalidTradeToFailedTrades(Trade trade) { private void onShowInfoForInvalidTrade(Trade trade) { new Popup().width(900).attention(Res.get("portfolio.pending.failedTrade.info.popup", - getInvalidTradeDetails(trade))) + getInvalidTradeDetails(trade))) .show(); } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java index 21407c5fe42..91d059a916a 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java @@ -32,10 +32,10 @@ import bisq.core.offer.OfferUtil; import bisq.core.provider.fee.FeeService; import bisq.core.provider.mempool.MempoolService; -import bisq.core.trade.Contract; -import bisq.core.trade.Trade; -import bisq.core.trade.TradeUtil; -import bisq.core.trade.closed.ClosedTradableManager; +import bisq.core.trade.bisq_v1.ClosedTradableManager; +import bisq.core.trade.bisq_v1.TradeUtil; +import bisq.core.trade.model.bisq_v1.Contract; +import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.user.User; import bisq.core.util.FormattingUtils; import bisq.core.util.VolumeUtil; @@ -433,8 +433,8 @@ private void onTradeStateChanged(Trade.State tradeState) { // #################### Phase DEPOSIT_PAID - // DEPOSIT_TX_PUBLISHED_MSG - // seller perspective + // DEPOSIT_TX_PUBLISHED_MSG + // seller perspective case SELLER_PUBLISHED_DEPOSIT_TX: // buyer perspective case BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG: diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/TradeStepInfo.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/TradeStepInfo.java index 61508824984..c9f53e75cb3 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/TradeStepInfo.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/TradeStepInfo.java @@ -21,7 +21,7 @@ import bisq.desktop.components.TitledGroupBg; import bisq.core.locale.Res; -import bisq.core.trade.Trade; +import bisq.core.trade.model.bisq_v1.Trade; import javafx.scene.control.Label; import javafx.scene.layout.GridPane; diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/TradeSubView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/TradeSubView.java index 8f0f9d76aee..379ae4da8c9 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/TradeSubView.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/TradeSubView.java @@ -24,7 +24,7 @@ import bisq.desktop.util.Layout; import bisq.core.locale.Res; -import bisq.core.trade.Trade; +import bisq.core.trade.model.bisq_v1.Trade; import javafx.scene.control.Label; import javafx.scene.control.Separator; diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java index 1c39db14780..46036e62e74 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java @@ -31,8 +31,8 @@ import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.DisputeResult; import bisq.core.support.dispute.mediation.MediationResultState; -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.DontShowAgainLookup; import bisq.core.user.Preferences; import bisq.core.util.FormattingUtils; @@ -570,7 +570,7 @@ protected boolean hasSelfAccepted() { } private boolean peerAccepted() { - return trade.getProcessModel().getTradingPeer().getMediatedPayoutTxSignature() != null; + return trade.getProcessModel().getTradePeer().getMediatedPayoutTxSignature() != null; } private void openMediationResultPopup(String headLine) { @@ -735,7 +735,7 @@ private void checkIfLockTimeIsOver() { } protected void checkForTimeout() { - long unconfirmedHours = Duration.between(trade.getTakeOfferDate().toInstant(), Instant.now()).toHours(); + long unconfirmedHours = Duration.between(trade.getDate().toInstant(), Instant.now()).toHours(); if (unconfirmedHours >= 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 809b9a5c131..4665dd7e577 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.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 4d58c9e0526..137727790d4 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.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; 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 eb7129344ed..9696afdf305 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.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 66fb0a16a0f..9127c74967c 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.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; 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 27e2be22133..d36efe3faa6 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 @@ @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/settings/preferences/PreferencesView.java b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java index c137041f0d9..5402f3405c8 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java @@ -48,6 +48,7 @@ import bisq.core.payment.PaymentAccount; import bisq.core.payment.payload.PaymentMethod; import bisq.core.provider.fee.FeeService; +import bisq.core.user.DontShowAgainLookup; import bisq.core.user.Preferences; import bisq.core.user.User; import bisq.core.util.FormattingUtils; @@ -122,13 +123,12 @@ public class PreferencesView extends ActivatableViewAndModel { + preferences.setUseDaoMonitor(daoMonitorActivatedToggleButton.isSelected()); + if (daoMonitorActivatedToggleButton.isSelected()) { + String key = "DaoMonitorInfo"; + if (DontShowAgainLookup.showAgain(key)) { + new Popup().information(Res.get("dao.monitor.activate.popup.info")) + .dontShowAgainId(key) + .closeButtonText(Res.get("shared.iUnderstand")) + .show(); + } + } + }); + boolean daoFullNode = preferences.isDaoFullNode(); isDaoFullNodeToggleButton.setSelected(daoFullNode); @@ -1173,6 +1190,7 @@ private void deactivateDaoPreferences() { return; } + daoMonitorActivatedToggleButton.setOnAction(null); resyncDaoFromResourcesButton.setOnAction(null); resyncDaoFromGenesisButton.setOnAction(null); bsqAverageTrimThresholdTextField.textProperty().removeListener(bsqAverageTrimThresholdListener); 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 f55ac5a6a5d..0447bc9eafc 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.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 e7ed03bf36a..dc0f5baedcf 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.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 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 0594e9895e2..f967623e833 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.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 0efcfb38ae2..79446c12616 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.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 b74bffc8f39..e3ebb02d566 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.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 static String booleanToYesNo(boolean value) { // 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 static String getDirectionForSeller(boolean isMyOffer, String currencyCod } } - 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 6961273312f..fe064aedcdc 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 static Tuple2 getTopLabelWithVBox(String title, Node node) { 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 7afd59cb530..3698a13ab6b 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 @@ protected void updateItem(PaymentMethod method, boolean empty) { 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 static boolean isChainHeightSyncedWithinToleranceOrShowPopup(WalletsSetup 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/test/java/bisq/desktop/main/dao/monitor/daostate/DaoStateBlockListItemTest.java b/desktop/src/test/java/bisq/desktop/main/dao/monitor/daostate/DaoStateBlockListItemTest.java index 182be1ba09e..d36714be1db 100644 --- a/desktop/src/test/java/bisq/desktop/main/dao/monitor/daostate/DaoStateBlockListItemTest.java +++ b/desktop/src/test/java/bisq/desktop/main/dao/monitor/daostate/DaoStateBlockListItemTest.java @@ -41,7 +41,7 @@ public void setup() { @Test public void testEqualsAndHashCode() { - var block = new DaoStateBlock(new DaoStateHash(0, new byte[0], new byte[0])); + var block = new DaoStateBlock(new DaoStateHash(0, new byte[0], true)); var item1 = new DaoStateBlockListItem(block, newSupplier(1)); var item2 = new DaoStateBlockListItem(block, newSupplier(2)); var item3 = new DaoStateBlockListItem(block, newSupplier(1)); 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 86a62b28d24..38a86b5290c 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 @@ 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 4552c9bc1cc..c682ed3b344 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.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 470c86b853b..3fc291f2739 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.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 691da7676bb..17b8ea3c064 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.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 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.when; public class CreateOfferDataModelTest { - private CreateOfferDataModel model; private User user; private Preferences preferences; @@ -60,7 +58,6 @@ public void setUp() { 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 void testUseTradeCurrencySetInOfferViewWhenInPaymentAccountAvailable() { 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 void testUseTradeAccountThatMatchesTradeCurrencySetInOffer() { 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 95% 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 f82c5636e04..958d2a498b2 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,7 +15,7 @@ * 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; @@ -30,8 +30,9 @@ 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; @@ -55,12 +56,9 @@ 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 void setUp() { 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 void setUp() { 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 55b243aa82c..58599f20f4c 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 @@ 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 33c55512485..5cc3d4aafeb 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 @@ -25,8 +25,8 @@ 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; 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 d0b4b01cac6..83bddc34925 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.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 java.time.Instant; -import java.util.UUID; - import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -94,8 +92,7 @@ public void setUp() { 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 void testEditOfferOfRemovedAsset() { @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 7e956c7bea0..3be4461afa8 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 3a233ce4ee8..1ed85c4e683 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 @@ 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 f6f4df9659f..ca5ea906cd4 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 f99d336d645..93e94d1bc9d 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.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.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 @@ private void processResponse(NodeAddress nodeAddress, 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 8115c1015d2..368e9cb03c1 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 @@ 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 @@ private class VersionsStatistics extends Statistics { 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 48c6089776e..ff4bb78b6f1 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 void configure(Properties properties) { 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 bedef3ed9ea..f4cb3a46298 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.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.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 static int getPermittedMessageSize() { /////////////////////////////////////////////////////////////////////////////////////////// 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 Capabilities getCapabilities() { 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 void sendMessage(NetworkEnvelope networkEnvelope) { return; } - if (!noCapabilityRequiredOrCapabilityIsSupported(networkEnvelope)) { + if (!testCapability(networkEnvelope)) { log.debug("Capability for networkEnvelope is required but not supported"); return; } @@ -257,58 +248,6 @@ public void sendMessage(NetworkEnvelope networkEnvelope) { 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 void sendMessage(NetworkEnvelope networkEnvelope) { } } - // 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 void shutDown(CloseConnectionReason closeConnectionReason, @Nullable Runn stopped = true; - //noinspection UnstableApiUsage Uninterruptibles.sleepUninterruptibly(200, TimeUnit.MILLISECONDS); } catch (Throwable t) { log.error(t.getMessage()); @@ -576,10 +526,8 @@ private void doShutDown(CloseConnectionReason closeConnectionReason, @Nullable R //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 boolean reportInvalidRequest(RuleViolation ruleViolation) { "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 @@ private void handleException(Throwable e) { 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 d0dc97c3c27..b7b89d25795 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 @@ private List getBroadcastRequestsForConnection(Con 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 ada7d8eddc0..615cac138e6 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.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 @@ private boolean addProtectedStorageEntry(ProtectedStorageEntry protectedStorageE 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 00000000000..0ed8439118d --- /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 84e6ee18ad3..b84ea6ae4ae 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 7b48346db45..f093ecfd0c4 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 /////////////////////////////////////////////////////////////////////////////////////////// @@ -1783,6 +1945,7 @@ message PreferencesPayload { bool show_offers_matching_my_accounts = 59; bool deny_api_taker = 60; bool notify_on_pre_release = 61; + bool use_dao_monitor = 62; } message AutoConfirmSettings { @@ -2219,20 +2382,21 @@ message DaoStateStore { message DaoStateHash { int32 height = 1; bytes hash = 2; - bytes prev_hash = 3; + bytes prev_hash = 3 [deprecated = true]; + bool is_self_constructed = 4; } message ProposalStateHash { int32 height = 1; bytes hash = 2; - bytes prev_hash = 3; + bytes prev_hash = 3 [deprecated = true]; int32 num_proposals = 4; } message BlindVoteStateHash { int32 height = 1; bytes hash = 2; - bytes prev_hash = 3; + bytes prev_hash = 3 [deprecated = true]; int32 num_blind_votes = 4; } diff --git a/relay/src/main/resources/version.txt b/relay/src/main/resources/version.txt index b1ce0474b51..6a126f402d5 100644 --- a/relay/src/main/resources/version.txt +++ b/relay/src/main/resources/version.txt @@ -1 +1 @@ -1.7.4-SNAPSHOT +1.7.5 diff --git a/seednode/src/main/java/bisq/seednode/SeedNodeMain.java b/seednode/src/main/java/bisq/seednode/SeedNodeMain.java index ca97e0a8c13..080aff96d21 100644 --- a/seednode/src/main/java/bisq/seednode/SeedNodeMain.java +++ b/seednode/src/main/java/bisq/seednode/SeedNodeMain.java @@ -47,7 +47,7 @@ @Slf4j public class SeedNodeMain extends ExecutableForAppWithP2p { private static final long CHECK_CONNECTION_LOSS_SEC = 30; - private static final String VERSION = "1.7.4"; + private static final String VERSION = "1.7.5"; private SeedNode seedNode; private Timer checkConnectionLossTime;