mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-23 15:00:30 +01:00
Merge pull request #5775 from chimp1984/bsq-swap-impl
Bsq swap: Implementation [3]
This commit is contained in:
commit
da66ffe8f5
522 changed files with 20014 additions and 3714 deletions
|
@ -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
|
# 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
|
# 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
|
# 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.
|
# first displayed in Bob's getoffers command output.
|
||||||
|
|
|
@ -24,7 +24,7 @@ import java.util.List;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import static bisq.apitest.config.ApiTestConfig.BASH_PATH_VALUE;
|
import static bisq.apitest.config.ApiTestConfig.BASH_PATH_VALUE;
|
||||||
import static java.lang.management.ManagementFactory.getRuntimeMXBean;
|
import static java.lang.management.ManagementFactory.getRuntimeMXBean;
|
||||||
|
@ -33,7 +33,9 @@ import static java.lang.management.ManagementFactory.getRuntimeMXBean;
|
||||||
public class BashCommand {
|
public class BashCommand {
|
||||||
|
|
||||||
private int exitStatus = -1;
|
private int exitStatus = -1;
|
||||||
|
@Nullable
|
||||||
private String output;
|
private String output;
|
||||||
|
@Nullable
|
||||||
private String error;
|
private String error;
|
||||||
|
|
||||||
private final String command;
|
private final String command;
|
||||||
|
@ -92,6 +94,7 @@ public class BashCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO return Optional<String>
|
// TODO return Optional<String>
|
||||||
|
@Nullable
|
||||||
public String getOutput() {
|
public String getOutput() {
|
||||||
return this.output;
|
return this.output;
|
||||||
}
|
}
|
||||||
|
@ -101,7 +104,6 @@ public class BashCommand {
|
||||||
return this.error;
|
return this.error;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private List<String> tokenizeSystemCommand() {
|
private List<String> tokenizeSystemCommand() {
|
||||||
return new ArrayList<>() {{
|
return new ArrayList<>() {{
|
||||||
add(BASH_PATH_VALUE);
|
add(BASH_PATH_VALUE);
|
||||||
|
|
|
@ -57,15 +57,13 @@ class SystemCommandExecutor {
|
||||||
private ThreadedStreamHandler errorStreamHandler;
|
private ThreadedStreamHandler errorStreamHandler;
|
||||||
|
|
||||||
public SystemCommandExecutor(final List<String> cmdOptions) {
|
public SystemCommandExecutor(final List<String> cmdOptions) {
|
||||||
if (log.isDebugEnabled())
|
|
||||||
log.debug("cmd options {}", cmdOptions.toString());
|
|
||||||
|
|
||||||
if (cmdOptions.isEmpty())
|
if (cmdOptions.isEmpty())
|
||||||
throw new IllegalStateException("No command params specified.");
|
throw new IllegalStateException("No command params specified.");
|
||||||
|
|
||||||
if (cmdOptions.contains("sudo"))
|
if (cmdOptions.contains("sudo"))
|
||||||
throw new IllegalStateException("'sudo' commands are prohibited.");
|
throw new IllegalStateException("'sudo' commands are prohibited.");
|
||||||
|
|
||||||
|
log.trace("System cmd options {}", cmdOptions);
|
||||||
this.cmdOptions = cmdOptions;
|
this.cmdOptions = cmdOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
|
|
||||||
package bisq.apitest;
|
package bisq.apitest;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
@ -32,9 +34,9 @@ import static bisq.apitest.config.ApiTestRateMeterInterceptorConfig.getTestRateM
|
||||||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||||
import static bisq.apitest.config.BisqAppConfig.arbdaemon;
|
import static bisq.apitest.config.BisqAppConfig.arbdaemon;
|
||||||
import static bisq.apitest.config.BisqAppConfig.bobdaemon;
|
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.net.InetAddress.getLoopbackAddress;
|
||||||
import static java.util.Arrays.stream;
|
import static java.util.Arrays.stream;
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -131,11 +133,7 @@ public class ApiTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void sleep(long ms) {
|
protected static void sleep(long ms) {
|
||||||
try {
|
sleepUninterruptibly(Duration.ofMillis(ms));
|
||||||
MILLISECONDS.sleep(ms);
|
|
||||||
} catch (InterruptedException ignored) {
|
|
||||||
// empty
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final String testName(TestInfo testInfo) {
|
protected final String testName(TestInfo testInfo) {
|
||||||
|
|
|
@ -31,7 +31,12 @@ import java.io.PrintWriter;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import static bisq.apitest.config.ApiTestRateMeterInterceptorConfig.getTestRateMeterInterceptorConfig;
|
import static bisq.apitest.config.ApiTestRateMeterInterceptorConfig.getTestRateMeterInterceptorConfig;
|
||||||
|
import static java.lang.String.format;
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
import static java.util.Arrays.stream;
|
import static java.util.Arrays.stream;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
@ -39,6 +44,7 @@ import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
|
||||||
import bisq.apitest.ApiTestCase;
|
import bisq.apitest.ApiTestCase;
|
||||||
|
import bisq.apitest.linux.BashCommand;
|
||||||
import bisq.cli.GrpcClient;
|
import bisq.cli.GrpcClient;
|
||||||
|
|
||||||
public class MethodTest extends ApiTestCase {
|
public class MethodTest extends ApiTestCase {
|
||||||
|
@ -144,14 +150,42 @@ public class MethodTest extends ApiTestCase {
|
||||||
protected final bisq.core.payment.PaymentAccount createPaymentAccount(GrpcClient grpcClient, String jsonString) {
|
protected final bisq.core.payment.PaymentAccount createPaymentAccount(GrpcClient grpcClient, String jsonString) {
|
||||||
// Normally, we do asserts on the protos from the gRPC service, but in this
|
// Normally, we do asserts on the protos from the gRPC service, but in this
|
||||||
// case we need a bisq.core.payment.PaymentAccount so it can be cast to its
|
// case we need a bisq.core.payment.PaymentAccount so it can be cast to its
|
||||||
// sub type.
|
// sub-type.
|
||||||
var paymentAccount = grpcClient.createPaymentAccount(jsonString);
|
var paymentAccount = grpcClient.createPaymentAccount(jsonString);
|
||||||
return bisq.core.payment.PaymentAccount.fromProto(paymentAccount, CORE_PROTO_RESOLVER);
|
return bisq.core.payment.PaymentAccount.fromProto(paymentAccount, CORE_PROTO_RESOLVER);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Static conveniences for test methods and test case fixture setups.
|
|
||||||
|
|
||||||
protected static String encodeToHex(String s) {
|
protected static String encodeToHex(String s) {
|
||||||
return Utilities.bytesAsHexString(s.getBytes(UTF_8));
|
return Utilities.bytesAsHexString(s.getBytes(UTF_8));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void verifyNoLoggedNodeExceptions() {
|
||||||
|
var loggedExceptions = getNodeExceptionMessages();
|
||||||
|
if (loggedExceptions != null) {
|
||||||
|
String err = format("Exception(s) found in daemon log(s):%n%s", loggedExceptions);
|
||||||
|
fail(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void printNodeExceptionMessages(Logger log) {
|
||||||
|
var loggedExceptions = getNodeExceptionMessages();
|
||||||
|
if (loggedExceptions != null)
|
||||||
|
log.error("Exception(s) found in daemon log(s):\n{}", loggedExceptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
protected static String getNodeExceptionMessages() {
|
||||||
|
var nodeLogsSpec = config.rootAppDataDir.getAbsolutePath() + "/bisq-BTC_REGTEST_*_dao/bisq.log";
|
||||||
|
var grep = "grep Exception " + nodeLogsSpec;
|
||||||
|
var bashCommand = new BashCommand(grep);
|
||||||
|
try {
|
||||||
|
bashCommand.run();
|
||||||
|
} catch (IOException | InterruptedException ex) {
|
||||||
|
fail("Bash command execution error: " + ex);
|
||||||
|
}
|
||||||
|
if (bashCommand.getError() == null)
|
||||||
|
return bashCommand.getOutput();
|
||||||
|
else
|
||||||
|
throw new IllegalStateException("Bash command execution error: " + bashCommand.getError());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,16 @@ public abstract class AbstractOfferTest extends MethodTest {
|
||||||
bobdaemon);
|
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.
|
// Mkt Price Margin value of offer returned from server is scaled down by 10^-2.
|
||||||
protected final Function<Double, Double> scaledDownMktPriceMargin = (mktPriceMargin) ->
|
protected final Function<Double, Double> scaledDownMktPriceMargin = (mktPriceMargin) ->
|
||||||
|
|
|
@ -0,0 +1,173 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Bisq.
|
||||||
|
*
|
||||||
|
* Bisq is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package bisq.apitest.method.offer;
|
||||||
|
|
||||||
|
import bisq.proto.grpc.BsqSwapOfferInfo;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
import org.junit.jupiter.api.MethodOrderer;
|
||||||
|
import org.junit.jupiter.api.Order;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
|
||||||
|
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
||||||
|
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||||
|
import static bisq.cli.TableFormat.formatBalancesTbls;
|
||||||
|
import static java.lang.String.format;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
import static protobuf.OfferDirection.BUY;
|
||||||
|
|
||||||
|
@Disabled
|
||||||
|
@Slf4j
|
||||||
|
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||||
|
public class BsqSwapOfferTest extends AbstractOfferTest {
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public static void setUp() {
|
||||||
|
AbstractOfferTest.setUp();
|
||||||
|
createBsqSwapBsqPaymentAccounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void generateBtcBlock() {
|
||||||
|
genBtcBlocksThenWait(1, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(1)
|
||||||
|
public void testGetBalancesBeforeCreateOffers() {
|
||||||
|
var alicesBalances = aliceClient.getBalances();
|
||||||
|
log.debug("Alice's Before Trade Balance:\n{}", formatBalancesTbls(alicesBalances));
|
||||||
|
var bobsBalances = bobClient.getBalances();
|
||||||
|
log.debug("Bob's Before Trade Balance:\n{}", formatBalancesTbls(bobsBalances));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(2)
|
||||||
|
public void testAliceCreateBsqSwapBuyOffer1() {
|
||||||
|
createBsqSwapOffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(3)
|
||||||
|
public void testAliceCreateBsqSwapBuyOffer2() {
|
||||||
|
createBsqSwapOffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(4)
|
||||||
|
public void testAliceCreateBsqSwapBuyOffer3() {
|
||||||
|
createBsqSwapOffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(5)
|
||||||
|
public void testAliceCreateBsqSwapBuyOffer4() {
|
||||||
|
createBsqSwapOffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(6)
|
||||||
|
public void testGetMyBsqSwapOffers() {
|
||||||
|
var offers = aliceClient.getMyBsqSwapBsqOffersSortedByDate();
|
||||||
|
assertEquals(4, offers.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(7)
|
||||||
|
public void testGetAvailableBsqSwapOffers() {
|
||||||
|
var offers = bobClient.getBsqSwapOffersSortedByDate();
|
||||||
|
assertEquals(4, offers.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(8)
|
||||||
|
public void testGetBalancesAfterCreateOffers() {
|
||||||
|
var alicesBalances = aliceClient.getBalances();
|
||||||
|
log.debug("Alice's After Trade Balance:\n{}", formatBalancesTbls(alicesBalances));
|
||||||
|
var bobsBalances = bobClient.getBalances();
|
||||||
|
log.debug("Bob's After Trade Balance:\n{}", formatBalancesTbls(bobsBalances));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createBsqSwapOffer() {
|
||||||
|
var bsqSwapOffer = aliceClient.createBsqSwapOffer(BUY.name(),
|
||||||
|
1_000_000L,
|
||||||
|
1_000_000L,
|
||||||
|
"0.00005",
|
||||||
|
alicesBsqAcct.getId());
|
||||||
|
log.debug("BsqSwap Sell BSQ (Buy BTC) OFFER:\n{}", bsqSwapOffer);
|
||||||
|
var newOfferId = bsqSwapOffer.getId();
|
||||||
|
assertNotEquals("", newOfferId);
|
||||||
|
assertEquals(BUY.name(), bsqSwapOffer.getDirection());
|
||||||
|
assertEquals(5_000, bsqSwapOffer.getPrice());
|
||||||
|
assertEquals(1_000_000L, bsqSwapOffer.getAmount());
|
||||||
|
assertEquals(1_000_000L, bsqSwapOffer.getMinAmount());
|
||||||
|
// assertEquals(alicesBsqAcct.getId(), atomicOffer.getMakerPaymentAccountId());
|
||||||
|
assertEquals(BSQ, bsqSwapOffer.getBaseCurrencyCode());
|
||||||
|
assertEquals(BTC, bsqSwapOffer.getCounterCurrencyCode());
|
||||||
|
|
||||||
|
testGetMyBsqSwapOffer(bsqSwapOffer);
|
||||||
|
testGetBsqSwapOffer(bsqSwapOffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testGetMyBsqSwapOffer(BsqSwapOfferInfo bsqSwapOfferInfo) {
|
||||||
|
int numFetchAttempts = 0;
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
numFetchAttempts++;
|
||||||
|
var fetchedBsqSwapOffer = aliceClient.getMyBsqSwapOffer(bsqSwapOfferInfo.getId());
|
||||||
|
assertEquals(bsqSwapOfferInfo.getId(), fetchedBsqSwapOffer.getId());
|
||||||
|
log.debug("Alice found her (my) new bsq swap offer on attempt # {}.", numFetchAttempts);
|
||||||
|
break;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.warn(ex.getMessage());
|
||||||
|
|
||||||
|
if (numFetchAttempts >= 9)
|
||||||
|
fail(format("Alice giving up on fetching her (my) bsq swap offer after %d attempts.", numFetchAttempts), ex);
|
||||||
|
|
||||||
|
sleep(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testGetBsqSwapOffer(BsqSwapOfferInfo bsqSwapOfferInfo) {
|
||||||
|
int numFetchAttempts = 0;
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
numFetchAttempts++;
|
||||||
|
var fetchedBsqSwapOffer = bobClient.getBsqSwapOffer(bsqSwapOfferInfo.getId());
|
||||||
|
assertEquals(bsqSwapOfferInfo.getId(), fetchedBsqSwapOffer.getId());
|
||||||
|
log.debug("Bob found new available bsq swap offer on attempt # {}.", numFetchAttempts);
|
||||||
|
break;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.warn(ex.getMessage());
|
||||||
|
|
||||||
|
if (numFetchAttempts > 9)
|
||||||
|
fail(format("Bob gave up on fetching available bsq swap offer after %d attempts.", numFetchAttempts), ex);
|
||||||
|
|
||||||
|
sleep(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,7 +35,7 @@ import org.junit.jupiter.api.TestMethodOrder;
|
||||||
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
||||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static protobuf.OfferPayload.Direction.BUY;
|
import static protobuf.OfferDirection.BUY;
|
||||||
|
|
||||||
@Disabled
|
@Disabled
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
|
|
@ -40,8 +40,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static protobuf.OfferPayload.Direction.BUY;
|
import static protobuf.OfferDirection.BUY;
|
||||||
import static protobuf.OfferPayload.Direction.SELL;
|
import static protobuf.OfferDirection.SELL;
|
||||||
|
|
||||||
@Disabled
|
@Disabled
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
|
|
@ -36,8 +36,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static protobuf.OfferPayload.Direction.BUY;
|
import static protobuf.OfferDirection.BUY;
|
||||||
import static protobuf.OfferPayload.Direction.SELL;
|
import static protobuf.OfferDirection.SELL;
|
||||||
|
|
||||||
@Disabled
|
@Disabled
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
|
|
@ -52,8 +52,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static protobuf.OfferPayload.Direction.BUY;
|
import static protobuf.OfferDirection.BUY;
|
||||||
import static protobuf.OfferPayload.Direction.SELL;
|
import static protobuf.OfferDirection.SELL;
|
||||||
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
@Disabled
|
@Disabled
|
||||||
|
|
|
@ -47,8 +47,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static protobuf.OfferPayload.Direction.BUY;
|
import static protobuf.OfferDirection.BUY;
|
||||||
import static protobuf.OfferPayload.Direction.SELL;
|
import static protobuf.OfferDirection.SELL;
|
||||||
|
|
||||||
@SuppressWarnings("ALL")
|
@SuppressWarnings("ALL")
|
||||||
@Disabled
|
@Disabled
|
||||||
|
|
|
@ -35,7 +35,7 @@ import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAs
|
||||||
import static java.lang.String.format;
|
import static java.lang.String.format;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static protobuf.OfferPayload.Direction.BUY;
|
import static protobuf.OfferDirection.BUY;
|
||||||
|
|
||||||
@Disabled
|
@Disabled
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
|
|
@ -0,0 +1,173 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Bisq.
|
||||||
|
*
|
||||||
|
* Bisq is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package bisq.apitest.method.trade;
|
||||||
|
|
||||||
|
import bisq.proto.grpc.BsqSwapOfferInfo;
|
||||||
|
import bisq.proto.grpc.BsqSwapTradeInfo;
|
||||||
|
|
||||||
|
import protobuf.BsqSwapTrade;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
import org.junit.jupiter.api.MethodOrderer;
|
||||||
|
import org.junit.jupiter.api.Order;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
|
||||||
|
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
||||||
|
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||||
|
import static bisq.cli.TableFormat.formatBalancesTbls;
|
||||||
|
import static java.lang.String.format;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
import static protobuf.OfferDirection.BUY;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import bisq.apitest.method.offer.AbstractOfferTest;
|
||||||
|
|
||||||
|
@Disabled
|
||||||
|
@Slf4j
|
||||||
|
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||||
|
public class BsqSwapTradeTest extends AbstractOfferTest {
|
||||||
|
|
||||||
|
private static final String BISQ_FEE_CURRENCY_CODE = BSQ;
|
||||||
|
|
||||||
|
// Long-running swap trade tests might want to check node logs for exceptions.
|
||||||
|
@Setter
|
||||||
|
private boolean checkForLoggedExceptions;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public static void setUp() {
|
||||||
|
AbstractOfferTest.setUp();
|
||||||
|
createBsqSwapBsqPaymentAccounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void generateBtcBlock() {
|
||||||
|
genBtcBlocksThenWait(1, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(1)
|
||||||
|
public void testGetBalancesBeforeTrade() {
|
||||||
|
var alicesBalances = aliceClient.getBalances();
|
||||||
|
log.info("Alice's Before Trade Balance:\n{}", formatBalancesTbls(alicesBalances));
|
||||||
|
var bobsBalances = bobClient.getBalances();
|
||||||
|
log.info("Bob's Before Trade Balance:\n{}", formatBalancesTbls(bobsBalances));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(2)
|
||||||
|
public void testAliceCreateBsqSwapBuyOffer() {
|
||||||
|
var bsqSwapOffer = aliceClient.createBsqSwapOffer(BUY.name(),
|
||||||
|
1_000_000L,
|
||||||
|
1_000_000L,
|
||||||
|
"0.00005",
|
||||||
|
alicesBsqAcct.getId());
|
||||||
|
log.debug("BsqSwap Sell BSQ (Buy BTC) OFFER:\n{}", bsqSwapOffer);
|
||||||
|
var newOfferId = bsqSwapOffer.getId();
|
||||||
|
assertNotEquals("", newOfferId);
|
||||||
|
assertEquals(BUY.name(), bsqSwapOffer.getDirection());
|
||||||
|
assertEquals(5_000, bsqSwapOffer.getPrice());
|
||||||
|
assertEquals(1_000_000L, bsqSwapOffer.getAmount());
|
||||||
|
assertEquals(1_000_000L, bsqSwapOffer.getMinAmount());
|
||||||
|
// assertEquals(alicesBsqAcct.getId(), atomicOffer.getMakerPaymentAccountId());
|
||||||
|
assertEquals(BSQ, bsqSwapOffer.getBaseCurrencyCode());
|
||||||
|
assertEquals(BTC, bsqSwapOffer.getCounterCurrencyCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(3)
|
||||||
|
public void testBobTakesBsqSwapOffer() {
|
||||||
|
var bsqSwapOffer = getAvailableBsqSwapOffer();
|
||||||
|
var bsqSwapTradeInfo = bobClient.takeBsqSwapOffer(bsqSwapOffer.getId(),
|
||||||
|
bobsBsqAcct.getId(),
|
||||||
|
BISQ_FEE_CURRENCY_CODE);
|
||||||
|
log.debug("Trade at t1: {}", bsqSwapTradeInfo);
|
||||||
|
assertEquals(BsqSwapTrade.State.PREPARATION.name(), bsqSwapTradeInfo.getState());
|
||||||
|
genBtcBlocksThenWait(1, 3_000);
|
||||||
|
|
||||||
|
bsqSwapTradeInfo = getBsqSwapTrade(bsqSwapTradeInfo.getTradeId());
|
||||||
|
log.debug("Trade at t2: {}", bsqSwapTradeInfo);
|
||||||
|
assertEquals(BsqSwapTrade.State.COMPLETED.name(), bsqSwapTradeInfo.getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(4)
|
||||||
|
public void testGetBalancesAfterTrade() {
|
||||||
|
var alicesBalances = aliceClient.getBalances();
|
||||||
|
log.info("Alice's After Trade Balance:\n{}", formatBalancesTbls(alicesBalances));
|
||||||
|
var bobsBalances = bobClient.getBalances();
|
||||||
|
log.info("Bob's After Trade Balance:\n{}", formatBalancesTbls(bobsBalances));
|
||||||
|
}
|
||||||
|
|
||||||
|
private BsqSwapOfferInfo getAvailableBsqSwapOffer() {
|
||||||
|
List<BsqSwapOfferInfo> bsqSwapOffers = new ArrayList<>();
|
||||||
|
int numFetchAttempts = 0;
|
||||||
|
while (bsqSwapOffers.size() == 0) {
|
||||||
|
bsqSwapOffers.addAll(bobClient.getBsqSwapOffers(BUY.name(), BSQ));
|
||||||
|
numFetchAttempts++;
|
||||||
|
if (bsqSwapOffers.size() == 0) {
|
||||||
|
log.warn("No available bsq swap offers found after {} fetch attempts.", numFetchAttempts);
|
||||||
|
if (numFetchAttempts > 9) {
|
||||||
|
if (checkForLoggedExceptions) {
|
||||||
|
printNodeExceptionMessages(log);
|
||||||
|
}
|
||||||
|
fail(format("Bob gave up on fetching available bsq swap offers after %d attempts.", numFetchAttempts));
|
||||||
|
}
|
||||||
|
sleep(1000);
|
||||||
|
} else {
|
||||||
|
assertEquals(1, bsqSwapOffers.size());
|
||||||
|
log.debug("Bob found new available bsq swap offer on attempt # {}.", numFetchAttempts);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var bsqSwapOffer = bobClient.getBsqSwapOffer(bsqSwapOffers.get(0).getId());
|
||||||
|
assertEquals(bsqSwapOffers.get(0).getId(), bsqSwapOffer.getId());
|
||||||
|
return bsqSwapOffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BsqSwapTradeInfo getBsqSwapTrade(String tradeId) {
|
||||||
|
int numFetchAttempts = 0;
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
numFetchAttempts++;
|
||||||
|
return bobClient.getBsqSwapTrade(tradeId);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.warn(ex.getMessage());
|
||||||
|
if (numFetchAttempts > 9) {
|
||||||
|
if (checkForLoggedExceptions) {
|
||||||
|
printNodeExceptionMessages(log);
|
||||||
|
}
|
||||||
|
fail(format("Could not find new bsq swap trade after %d attempts.", numFetchAttempts));
|
||||||
|
} else {
|
||||||
|
sleep(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
package bisq.apitest.method.trade;
|
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.
|
* A test fixture encapsulating expected trade protocol status.
|
||||||
|
|
|
@ -37,13 +37,13 @@ import static bisq.apitest.config.ApiTestConfig.BSQ;
|
||||||
import static bisq.cli.TableFormat.formatBalancesTbls;
|
import static bisq.cli.TableFormat.formatBalancesTbls;
|
||||||
import static bisq.cli.TableFormat.formatOfferTable;
|
import static bisq.cli.TableFormat.formatOfferTable;
|
||||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||||
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED;
|
import static bisq.core.trade.model.bisq_v1.Trade.Phase.DEPOSIT_CONFIRMED;
|
||||||
import static bisq.core.trade.Trade.Phase.FIAT_SENT;
|
import static bisq.core.trade.model.bisq_v1.Trade.Phase.FIAT_SENT;
|
||||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
import static bisq.core.trade.model.bisq_v1.Trade.Phase.PAYOUT_PUBLISHED;
|
||||||
import static bisq.core.trade.Trade.State.BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG;
|
import static bisq.core.trade.model.bisq_v1.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.model.bisq_v1.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.model.bisq_v1.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.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
|
||||||
import static java.lang.String.format;
|
import static java.lang.String.format;
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
@ -51,7 +51,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
import static protobuf.Offer.State.OFFER_FEE_PAID;
|
import static protobuf.Offer.State.OFFER_FEE_PAID;
|
||||||
import static protobuf.OfferPayload.Direction.SELL;
|
import static protobuf.OfferDirection.SELL;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -37,17 +37,17 @@ import org.junit.jupiter.api.TestMethodOrder;
|
||||||
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
||||||
import static bisq.cli.TableFormat.formatBalancesTbls;
|
import static bisq.cli.TableFormat.formatBalancesTbls;
|
||||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||||
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED;
|
import static bisq.core.trade.model.bisq_v1.Trade.Phase.DEPOSIT_CONFIRMED;
|
||||||
import static bisq.core.trade.Trade.Phase.FIAT_SENT;
|
import static bisq.core.trade.model.bisq_v1.Trade.Phase.FIAT_SENT;
|
||||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
import static bisq.core.trade.model.bisq_v1.Trade.Phase.PAYOUT_PUBLISHED;
|
||||||
import static bisq.core.trade.Trade.State.*;
|
import static bisq.core.trade.model.bisq_v1.Trade.State.*;
|
||||||
import static java.lang.String.format;
|
import static java.lang.String.format;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
import static protobuf.Offer.State.OFFER_FEE_PAID;
|
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;
|
import static protobuf.OpenOffer.State.AVAILABLE;
|
||||||
|
|
||||||
@Disabled
|
@Disabled
|
||||||
|
|
|
@ -55,14 +55,14 @@ import org.junit.jupiter.api.TestMethodOrder;
|
||||||
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
||||||
import static bisq.cli.TableFormat.formatBalancesTbls;
|
import static bisq.cli.TableFormat.formatBalancesTbls;
|
||||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||||
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED;
|
import static bisq.core.trade.model.bisq_v1.Trade.Phase.DEPOSIT_CONFIRMED;
|
||||||
import static bisq.core.trade.Trade.Phase.FIAT_SENT;
|
import static bisq.core.trade.model.bisq_v1.Trade.Phase.FIAT_SENT;
|
||||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
import static bisq.core.trade.model.bisq_v1.Trade.Phase.PAYOUT_PUBLISHED;
|
||||||
import static bisq.core.trade.Trade.State.*;
|
import static bisq.core.trade.model.bisq_v1.Trade.State.*;
|
||||||
import static java.lang.String.format;
|
import static java.lang.String.format;
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static protobuf.Offer.State.OFFER_FEE_PAID;
|
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;
|
import static protobuf.OpenOffer.State.AVAILABLE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -38,21 +38,21 @@ import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||||
import static bisq.cli.TableFormat.formatBalancesTbls;
|
import static bisq.cli.TableFormat.formatBalancesTbls;
|
||||||
import static bisq.cli.TableFormat.formatOfferTable;
|
import static bisq.cli.TableFormat.formatOfferTable;
|
||||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||||
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED;
|
import static bisq.core.trade.model.bisq_v1.Trade.Phase.DEPOSIT_CONFIRMED;
|
||||||
import static bisq.core.trade.Trade.Phase.FIAT_SENT;
|
import static bisq.core.trade.model.bisq_v1.Trade.Phase.FIAT_SENT;
|
||||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
import static bisq.core.trade.model.bisq_v1.Trade.Phase.PAYOUT_PUBLISHED;
|
||||||
import static bisq.core.trade.Trade.Phase.WITHDRAWN;
|
import static bisq.core.trade.model.bisq_v1.Trade.Phase.WITHDRAWN;
|
||||||
import static bisq.core.trade.Trade.State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN;
|
import static bisq.core.trade.model.bisq_v1.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.model.bisq_v1.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.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.State.WITHDRAW_COMPLETED;
|
||||||
import static java.lang.String.format;
|
import static java.lang.String.format;
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
import static protobuf.OfferPayload.Direction.BUY;
|
import static protobuf.OfferDirection.BUY;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -37,18 +37,18 @@ import org.junit.jupiter.api.TestMethodOrder;
|
||||||
import static bisq.apitest.config.ApiTestConfig.BTC;
|
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||||
import static bisq.cli.TableFormat.formatBalancesTbls;
|
import static bisq.cli.TableFormat.formatBalancesTbls;
|
||||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||||
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED;
|
import static bisq.core.trade.model.bisq_v1.Trade.Phase.DEPOSIT_CONFIRMED;
|
||||||
import static bisq.core.trade.Trade.Phase.FIAT_SENT;
|
import static bisq.core.trade.model.bisq_v1.Trade.Phase.FIAT_SENT;
|
||||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
import static bisq.core.trade.model.bisq_v1.Trade.Phase.PAYOUT_PUBLISHED;
|
||||||
import static bisq.core.trade.Trade.Phase.WITHDRAWN;
|
import static bisq.core.trade.model.bisq_v1.Trade.Phase.WITHDRAWN;
|
||||||
import static bisq.core.trade.Trade.State.*;
|
import static bisq.core.trade.model.bisq_v1.Trade.State.*;
|
||||||
import static java.lang.String.format;
|
import static java.lang.String.format;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
import static protobuf.Offer.State.OFFER_FEE_PAID;
|
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;
|
import static protobuf.OpenOffer.State.AVAILABLE;
|
||||||
|
|
||||||
@Disabled
|
@Disabled
|
||||||
|
|
|
@ -124,7 +124,7 @@ public class BsqWalletTest extends MethodTest {
|
||||||
genBtcBlocksThenWait(1, 4000);
|
genBtcBlocksThenWait(1, 4000);
|
||||||
|
|
||||||
BsqBalanceInfo alicesBsqBalances = aliceClient.getBalances().getBsq();
|
BsqBalanceInfo alicesBsqBalances = aliceClient.getBalances().getBsq();
|
||||||
BsqBalanceInfo bobsBsqBalances = waitForBsqNewAvailableConfirmedBalance(bobClient, 150000000);
|
BsqBalanceInfo bobsBsqBalances = waitForBsqNewAvailableBalance(bobClient, 150000000);
|
||||||
|
|
||||||
log.debug("See Available Confirmed BSQ Balances...");
|
log.debug("See Available Confirmed BSQ Balances...");
|
||||||
printBobAndAliceBsqBalances(testInfo,
|
printBobAndAliceBsqBalances(testInfo,
|
||||||
|
@ -166,7 +166,7 @@ public class BsqWalletTest extends MethodTest {
|
||||||
return bsqBalance;
|
return bsqBalance;
|
||||||
}
|
}
|
||||||
|
|
||||||
private BsqBalanceInfo waitForBsqNewAvailableConfirmedBalance(GrpcClient grpcClient,
|
private BsqBalanceInfo waitForBsqNewAvailableBalance(GrpcClient grpcClient,
|
||||||
long staleBalance) {
|
long staleBalance) {
|
||||||
BsqBalanceInfo bsqBalance = grpcClient.getBsqBalances();
|
BsqBalanceInfo bsqBalance = grpcClient.getBsqBalances();
|
||||||
for (int numRequests = 1;
|
for (int numRequests = 1;
|
||||||
|
|
|
@ -38,13 +38,13 @@ public class WalletTestUtil {
|
||||||
0);
|
0);
|
||||||
|
|
||||||
@SuppressWarnings("SameParameterValue")
|
@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 unverifiedBalance,
|
||||||
long unconfirmedChangeBalance,
|
long unconfirmedChangeBalance,
|
||||||
long lockedForVotingBalance,
|
long lockedForVotingBalance,
|
||||||
long lockupBondsBalance,
|
long lockupBondsBalance,
|
||||||
long unlockingBondsBalance) {
|
long unlockingBondsBalance) {
|
||||||
return bisq.core.api.model.BsqBalanceInfo.valueOf(availableConfirmedBalance,
|
return bisq.core.api.model.BsqBalanceInfo.valueOf(availableBalance,
|
||||||
unverifiedBalance,
|
unverifiedBalance,
|
||||||
unconfirmedChangeBalance,
|
unconfirmedChangeBalance,
|
||||||
lockedForVotingBalance,
|
lockedForVotingBalance,
|
||||||
|
@ -54,7 +54,7 @@ public class WalletTestUtil {
|
||||||
|
|
||||||
public static void verifyBsqBalances(bisq.core.api.model.BsqBalanceInfo expected,
|
public static void verifyBsqBalances(bisq.core.api.model.BsqBalanceInfo expected,
|
||||||
BsqBalanceInfo actual) {
|
BsqBalanceInfo actual) {
|
||||||
assertEquals(expected.getAvailableConfirmedBalance(), actual.getAvailableConfirmedBalance());
|
assertEquals(expected.getAvailableBalance(), actual.getAvailableConfirmedBalance());
|
||||||
assertEquals(expected.getUnverifiedBalance(), actual.getUnverifiedBalance());
|
assertEquals(expected.getUnverifiedBalance(), actual.getUnverifiedBalance());
|
||||||
assertEquals(expected.getUnconfirmedChangeBalance(), actual.getUnconfirmedChangeBalance());
|
assertEquals(expected.getUnconfirmedChangeBalance(), actual.getUnconfirmedChangeBalance());
|
||||||
assertEquals(expected.getLockedForVotingBalance(), actual.getLockedForVotingBalance());
|
assertEquals(expected.getLockedForVotingBalance(), actual.getLockedForVotingBalance());
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Bisq.
|
||||||
|
*
|
||||||
|
* Bisq is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package bisq.apitest.scenario;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.MethodOrderer;
|
||||||
|
import org.junit.jupiter.api.Order;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
import org.junit.jupiter.api.condition.EnabledIf;
|
||||||
|
|
||||||
|
import static java.lang.System.getenv;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import bisq.apitest.method.offer.AbstractOfferTest;
|
||||||
|
import bisq.apitest.method.trade.BsqSwapTradeTest;
|
||||||
|
|
||||||
|
@EnabledIf("envLongRunningTestEnabled")
|
||||||
|
@Slf4j
|
||||||
|
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||||
|
public class LongRunningBsqSwapTest extends AbstractOfferTest {
|
||||||
|
|
||||||
|
private static final int MAX_SWAPS = 250;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public static void setUp() {
|
||||||
|
AbstractOfferTest.setUp();
|
||||||
|
createBsqSwapBsqPaymentAccounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(1)
|
||||||
|
public void testBsqSwaps() {
|
||||||
|
// TODO Fix wallet inconsistency bugs after N(?) trades.
|
||||||
|
BsqSwapTradeTest test = new BsqSwapTradeTest();
|
||||||
|
test.setCheckForLoggedExceptions(true);
|
||||||
|
|
||||||
|
for (int swapCount = 1; swapCount <= MAX_SWAPS; swapCount++) {
|
||||||
|
log.info("Beginning BSQ Swap # {}", swapCount);
|
||||||
|
|
||||||
|
test.testGetBalancesBeforeTrade();
|
||||||
|
|
||||||
|
test.testAliceCreateBsqSwapBuyOffer();
|
||||||
|
genBtcBlocksThenWait(1, 8_000);
|
||||||
|
|
||||||
|
test.testBobTakesBsqSwapOffer();
|
||||||
|
genBtcBlocksThenWait(1, 8_000);
|
||||||
|
|
||||||
|
test.testGetBalancesAfterTrade();
|
||||||
|
log.info("Finished BSQ Swap # {}", swapCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static boolean envLongRunningTestEnabled() {
|
||||||
|
String envName = "LONG_RUNNING_BSQ_SWAP_TEST_ENABLED";
|
||||||
|
String envX = getenv(envName);
|
||||||
|
if (envX != null) {
|
||||||
|
log.info("Enabled, found {}.", envName);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
log.info("Skipped, no environment variable {} defined.", envName);
|
||||||
|
log.info("To enable on Mac OS or Linux:"
|
||||||
|
+ "\tIf running in terminal, export LONG_RUNNING_BSQ_SWAP_TEST_ENABLED=true in bash shell."
|
||||||
|
+ "\tIf running in Intellij, set LONG_RUNNING_BSQ_SWAP_TEST_ENABLED=true in launcher's Environment variables field.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,8 +35,8 @@ import static bisq.cli.CurrencyFormat.formatPrice;
|
||||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||||
import static java.lang.System.getenv;
|
import static java.lang.System.getenv;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
import static protobuf.OfferPayload.Direction.BUY;
|
import static protobuf.OfferDirection.BUY;
|
||||||
import static protobuf.OfferPayload.Direction.SELL;
|
import static protobuf.OfferDirection.SELL;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
|
||||||
|
|
||||||
import bisq.apitest.method.offer.AbstractOfferTest;
|
import bisq.apitest.method.offer.AbstractOfferTest;
|
||||||
|
import bisq.apitest.method.offer.BsqSwapOfferTest;
|
||||||
import bisq.apitest.method.offer.CancelOfferTest;
|
import bisq.apitest.method.offer.CancelOfferTest;
|
||||||
import bisq.apitest.method.offer.CreateBSQOffersTest;
|
import bisq.apitest.method.offer.CreateBSQOffersTest;
|
||||||
import bisq.apitest.method.offer.CreateOfferUsingFixedPriceTest;
|
import bisq.apitest.method.offer.CreateOfferUsingFixedPriceTest;
|
||||||
|
@ -90,6 +91,19 @@ public class OfferTest extends AbstractOfferTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(6)
|
@Order(6)
|
||||||
|
public void testCreateBSQSwapOffers() {
|
||||||
|
BsqSwapOfferTest test = new BsqSwapOfferTest();
|
||||||
|
BsqSwapOfferTest.createBsqSwapBsqPaymentAccounts();
|
||||||
|
test.testAliceCreateBsqSwapBuyOffer1();
|
||||||
|
test.testAliceCreateBsqSwapBuyOffer2();
|
||||||
|
test.testAliceCreateBsqSwapBuyOffer3();
|
||||||
|
test.testAliceCreateBsqSwapBuyOffer4();
|
||||||
|
test.testGetMyBsqSwapOffers();
|
||||||
|
test.testGetAvailableBsqSwapOffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(7)
|
||||||
public void testEditOffer() {
|
public void testEditOffer() {
|
||||||
EditOfferTest test = new EditOfferTest();
|
EditOfferTest test = new EditOfferTest();
|
||||||
// Edit fiat offer tests
|
// Edit fiat offer tests
|
||||||
|
|
|
@ -29,6 +29,7 @@ import org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
|
||||||
|
|
||||||
import bisq.apitest.method.trade.AbstractTradeTest;
|
import bisq.apitest.method.trade.AbstractTradeTest;
|
||||||
|
import bisq.apitest.method.trade.BsqSwapTradeTest;
|
||||||
import bisq.apitest.method.trade.TakeBuyBSQOfferTest;
|
import bisq.apitest.method.trade.TakeBuyBSQOfferTest;
|
||||||
import bisq.apitest.method.trade.TakeBuyBTCOfferTest;
|
import bisq.apitest.method.trade.TakeBuyBTCOfferTest;
|
||||||
import bisq.apitest.method.trade.TakeBuyBTCOfferWithNationalBankAcctTest;
|
import bisq.apitest.method.trade.TakeBuyBTCOfferWithNationalBankAcctTest;
|
||||||
|
@ -97,4 +98,13 @@ public class TradeTest extends AbstractTradeTest {
|
||||||
test.testBobsConfirmPaymentReceived(testInfo);
|
test.testBobsConfirmPaymentReceived(testInfo);
|
||||||
test.testAlicesBtcWithdrawalToExternalAddress(testInfo);
|
test.testAlicesBtcWithdrawalToExternalAddress(testInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(6)
|
||||||
|
public void testBsqSwapTradeTest(final TestInfo testInfo) {
|
||||||
|
BsqSwapTradeTest test = new BsqSwapTradeTest();
|
||||||
|
test.createBsqSwapBsqPaymentAccounts();
|
||||||
|
test.testAliceCreateBsqSwapBuyOffer();
|
||||||
|
test.testBobTakesBsqSwapOffer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,9 @@
|
||||||
|
|
||||||
package bisq.apitest.scenario.bot.script;
|
package bisq.apitest.scenario.bot.script;
|
||||||
|
|
||||||
|
import bisq.core.util.JsonUtil;
|
||||||
|
|
||||||
import bisq.common.file.JsonFileManager;
|
import bisq.common.file.JsonFileManager;
|
||||||
import bisq.common.util.Utilities;
|
|
||||||
|
|
||||||
import joptsimple.BuiltinHelpFormatter;
|
import joptsimple.BuiltinHelpFormatter;
|
||||||
import joptsimple.OptionParser;
|
import joptsimple.OptionParser;
|
||||||
|
@ -214,7 +215,7 @@ public class BotScriptGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
private String generateBotScriptTemplate() {
|
private String generateBotScriptTemplate() {
|
||||||
return Utilities.objectToJson(new BotScript(
|
return JsonUtil.objectToJson(new BotScript(
|
||||||
useTestHarness,
|
useTestHarness,
|
||||||
botPaymentMethodId,
|
botPaymentMethodId,
|
||||||
countryCode,
|
countryCode,
|
||||||
|
|
|
@ -32,7 +32,7 @@ configure(subprojects) {
|
||||||
|
|
||||||
ext { // in alphabetical order
|
ext { // in alphabetical order
|
||||||
bcVersion = '1.63'
|
bcVersion = '1.63'
|
||||||
bitcoinjVersion = '3186b20'
|
bitcoinjVersion = '42bbae9'
|
||||||
codecVersion = '1.13'
|
codecVersion = '1.13'
|
||||||
easybindVersion = '1.0.3'
|
easybindVersion = '1.0.3'
|
||||||
easyVersion = '4.0.1'
|
easyVersion = '4.0.1'
|
||||||
|
|
|
@ -24,8 +24,8 @@ import java.util.function.Function;
|
||||||
|
|
||||||
import static bisq.cli.ColumnHeaderConstants.COL_HEADER_DIRECTION;
|
import static bisq.cli.ColumnHeaderConstants.COL_HEADER_DIRECTION;
|
||||||
import static java.lang.String.format;
|
import static java.lang.String.format;
|
||||||
import static protobuf.OfferPayload.Direction.BUY;
|
import static protobuf.OfferDirection.BUY;
|
||||||
import static protobuf.OfferPayload.Direction.SELL;
|
import static protobuf.OfferDirection.SELL;
|
||||||
|
|
||||||
class DirectionFormat {
|
class DirectionFormat {
|
||||||
|
|
||||||
|
|
|
@ -20,12 +20,17 @@ package bisq.cli;
|
||||||
import bisq.proto.grpc.AddressBalanceInfo;
|
import bisq.proto.grpc.AddressBalanceInfo;
|
||||||
import bisq.proto.grpc.BalancesInfo;
|
import bisq.proto.grpc.BalancesInfo;
|
||||||
import bisq.proto.grpc.BsqBalanceInfo;
|
import bisq.proto.grpc.BsqBalanceInfo;
|
||||||
|
import bisq.proto.grpc.BsqSwapOfferInfo;
|
||||||
|
import bisq.proto.grpc.BsqSwapTradeInfo;
|
||||||
import bisq.proto.grpc.BtcBalanceInfo;
|
import bisq.proto.grpc.BtcBalanceInfo;
|
||||||
|
import bisq.proto.grpc.CreateBsqSwapOfferRequest;
|
||||||
import bisq.proto.grpc.GetMethodHelpRequest;
|
import bisq.proto.grpc.GetMethodHelpRequest;
|
||||||
import bisq.proto.grpc.GetVersionRequest;
|
import bisq.proto.grpc.GetVersionRequest;
|
||||||
import bisq.proto.grpc.OfferInfo;
|
import bisq.proto.grpc.OfferInfo;
|
||||||
import bisq.proto.grpc.RegisterDisputeAgentRequest;
|
import bisq.proto.grpc.RegisterDisputeAgentRequest;
|
||||||
import bisq.proto.grpc.StopRequest;
|
import bisq.proto.grpc.StopRequest;
|
||||||
|
import bisq.proto.grpc.TakeBsqSwapOfferReply;
|
||||||
|
import bisq.proto.grpc.TakeBsqSwapOfferRequest;
|
||||||
import bisq.proto.grpc.TakeOfferReply;
|
import bisq.proto.grpc.TakeOfferReply;
|
||||||
import bisq.proto.grpc.TradeInfo;
|
import bisq.proto.grpc.TradeInfo;
|
||||||
import bisq.proto.grpc.TxFeeRateInfo;
|
import bisq.proto.grpc.TxFeeRateInfo;
|
||||||
|
@ -137,6 +142,21 @@ public final class GrpcClient {
|
||||||
return walletsServiceRequest.getTransaction(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,
|
public OfferInfo createFixedPricedOffer(String direction,
|
||||||
String currencyCode,
|
String currencyCode,
|
||||||
long amount,
|
long amount,
|
||||||
|
@ -243,14 +263,26 @@ public final class GrpcClient {
|
||||||
offersServiceRequest.cancelOffer(offerId);
|
offersServiceRequest.cancelOffer(offerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BsqSwapOfferInfo getBsqSwapOffer(String offerId) {
|
||||||
|
return offersServiceRequest.getBsqSwapOffer(offerId);
|
||||||
|
}
|
||||||
|
|
||||||
public OfferInfo getOffer(String offerId) {
|
public OfferInfo getOffer(String offerId) {
|
||||||
return offersServiceRequest.getOffer(offerId);
|
return offersServiceRequest.getOffer(offerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BsqSwapOfferInfo getMyBsqSwapOffer(String offerId) {
|
||||||
|
return offersServiceRequest.getMyBsqSwapOffer(offerId);
|
||||||
|
}
|
||||||
|
|
||||||
public OfferInfo getMyOffer(String offerId) {
|
public OfferInfo getMyOffer(String offerId) {
|
||||||
return offersServiceRequest.getMyOffer(offerId);
|
return offersServiceRequest.getMyOffer(offerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<BsqSwapOfferInfo> getBsqSwapOffers(String direction, String currencyCode) {
|
||||||
|
return offersServiceRequest.getBsqSwapOffers(direction, currencyCode);
|
||||||
|
}
|
||||||
|
|
||||||
public List<OfferInfo> getOffers(String direction, String currencyCode) {
|
public List<OfferInfo> getOffers(String direction, String currencyCode) {
|
||||||
return offersServiceRequest.getOffers(direction, currencyCode);
|
return offersServiceRequest.getOffers(direction, currencyCode);
|
||||||
}
|
}
|
||||||
|
@ -271,6 +303,14 @@ public final class GrpcClient {
|
||||||
return offersServiceRequest.getBsqOffersSortedByDate();
|
return offersServiceRequest.getBsqOffersSortedByDate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<BsqSwapOfferInfo> getBsqSwapOffersSortedByDate() {
|
||||||
|
return offersServiceRequest.getBsqSwapOffersSortedByDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<BsqSwapOfferInfo> getMyBsqSwapOffers(String direction, String currencyCode) {
|
||||||
|
return offersServiceRequest.getMyBsqSwapOffers(direction, currencyCode);
|
||||||
|
}
|
||||||
|
|
||||||
public List<OfferInfo> getMyOffers(String direction, String currencyCode) {
|
public List<OfferInfo> getMyOffers(String direction, String currencyCode) {
|
||||||
return offersServiceRequest.getMyOffers(direction, currencyCode);
|
return offersServiceRequest.getMyOffers(direction, currencyCode);
|
||||||
}
|
}
|
||||||
|
@ -291,22 +331,53 @@ public final class GrpcClient {
|
||||||
return offersServiceRequest.getMyBsqOffersSortedByDate();
|
return offersServiceRequest.getMyBsqOffersSortedByDate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<BsqSwapOfferInfo> getMyBsqSwapBsqOffersSortedByDate() {
|
||||||
|
return offersServiceRequest.getMyBsqSwapOffersSortedByDate();
|
||||||
|
}
|
||||||
|
|
||||||
public OfferInfo getMostRecentOffer(String direction, String currencyCode) {
|
public OfferInfo getMostRecentOffer(String direction, String currencyCode) {
|
||||||
return offersServiceRequest.getMostRecentOffer(direction, currencyCode);
|
return offersServiceRequest.getMostRecentOffer(direction, currencyCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<BsqSwapOfferInfo> sortBsqSwapOffersByDate(List<BsqSwapOfferInfo> offerInfoList) {
|
||||||
|
return offersServiceRequest.sortBsqSwapOffersByDate(offerInfoList);
|
||||||
|
}
|
||||||
|
|
||||||
public List<OfferInfo> sortOffersByDate(List<OfferInfo> offerInfoList) {
|
public List<OfferInfo> sortOffersByDate(List<OfferInfo> offerInfoList) {
|
||||||
return offersServiceRequest.sortOffersByDate(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) {
|
public TakeOfferReply getTakeOfferReply(String offerId, String paymentAccountId, String takerFeeCurrencyCode) {
|
||||||
return tradesServiceRequest.getTakeOfferReply(offerId, paymentAccountId, 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) {
|
public TradeInfo takeOffer(String offerId, String paymentAccountId, String takerFeeCurrencyCode) {
|
||||||
return tradesServiceRequest.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode);
|
return tradesServiceRequest.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BsqSwapTradeInfo getBsqSwapTrade(String tradeId) {
|
||||||
|
return tradesServiceRequest.getBsqSwapTrade(tradeId);
|
||||||
|
}
|
||||||
|
|
||||||
public TradeInfo getTrade(String tradeId) {
|
public TradeInfo getTrade(String tradeId) {
|
||||||
return tradesServiceRequest.getTrade(tradeId);
|
return tradesServiceRequest.getTrade(tradeId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ import lombok.Getter;
|
||||||
|
|
||||||
import static bisq.cli.opts.OptLabel.OPT_HELP;
|
import static bisq.cli.opts.OptLabel.OPT_HELP;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
abstract class AbstractMethodOptionParser implements MethodOpts {
|
abstract class AbstractMethodOptionParser implements MethodOpts {
|
||||||
|
|
||||||
// The full command line args passed to CliMain.main(String[] args).
|
// The full command line args passed to CliMain.main(String[] args).
|
||||||
|
@ -53,7 +54,6 @@ abstract class AbstractMethodOptionParser implements MethodOpts {
|
||||||
public AbstractMethodOptionParser parse() {
|
public AbstractMethodOptionParser parse() {
|
||||||
try {
|
try {
|
||||||
options = parser.parse(new ArgumentList(args).getMethodArguments());
|
options = parser.parse(new ArgumentList(args).getMethodArguments());
|
||||||
//noinspection unchecked
|
|
||||||
nonOptionArguments = (List<String>) options.nonOptionArguments();
|
nonOptionArguments = (List<String>) options.nonOptionArguments();
|
||||||
return this;
|
return this;
|
||||||
} catch (OptionException ex) {
|
} catch (OptionException ex) {
|
||||||
|
|
|
@ -20,10 +20,7 @@ package bisq.cli.opts;
|
||||||
|
|
||||||
import joptsimple.OptionSpec;
|
import joptsimple.OptionSpec;
|
||||||
|
|
||||||
import static bisq.cli.opts.OptLabel.OPT_ACCOUNT_NAME;
|
import static bisq.cli.opts.OptLabel.*;
|
||||||
import static bisq.cli.opts.OptLabel.OPT_ADDRESS;
|
|
||||||
import static bisq.cli.opts.OptLabel.OPT_CURRENCY_CODE;
|
|
||||||
import static bisq.cli.opts.OptLabel.OPT_TRADE_INSTANT;
|
|
||||||
|
|
||||||
public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodOptionParser implements MethodOpts {
|
public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodOptionParser implements MethodOpts {
|
||||||
|
|
||||||
|
@ -41,6 +38,11 @@ public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodO
|
||||||
.ofType(boolean.class)
|
.ofType(boolean.class)
|
||||||
.defaultsTo(Boolean.FALSE);
|
.defaultsTo(Boolean.FALSE);
|
||||||
|
|
||||||
|
final OptionSpec<Boolean> tradeBsqSwapOpt = parser.accepts(OPT_TRADE_BSQ_SWAP, "create trade bsq swap account")
|
||||||
|
.withOptionalArg()
|
||||||
|
.ofType(boolean.class)
|
||||||
|
.defaultsTo(Boolean.FALSE);
|
||||||
|
|
||||||
public CreateCryptoCurrencyPaymentAcctOptionParser(String[] args) {
|
public CreateCryptoCurrencyPaymentAcctOptionParser(String[] args) {
|
||||||
super(args);
|
super(args);
|
||||||
}
|
}
|
||||||
|
@ -82,4 +84,8 @@ public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodO
|
||||||
public boolean getIsTradeInstant() {
|
public boolean getIsTradeInstant() {
|
||||||
return options.valueOf(tradeInstantOpt);
|
return options.valueOf(tradeInstantOpt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getIsTradeBsqSwap() {
|
||||||
|
return options.valueOf(tradeBsqSwapOpt);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ public class OptLabel {
|
||||||
public final static String OPT_REGISTRATION_KEY = "registration-key";
|
public final static String OPT_REGISTRATION_KEY = "registration-key";
|
||||||
public final static String OPT_SECURITY_DEPOSIT = "security-deposit";
|
public final static String OPT_SECURITY_DEPOSIT = "security-deposit";
|
||||||
public final static String OPT_SHOW_CONTRACT = "show-contract";
|
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_ID = "trade-id";
|
||||||
public final static String OPT_TRADE_INSTANT = "trade-instant";
|
public final static String OPT_TRADE_INSTANT = "trade-instant";
|
||||||
public final static String OPT_TIMEOUT = "timeout";
|
public final static String OPT_TIMEOUT = "timeout";
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package bisq.cli.request;
|
package bisq.cli.request;
|
||||||
|
|
||||||
|
import bisq.proto.grpc.BsqSwapOfferInfo;
|
||||||
import bisq.proto.grpc.CancelOfferRequest;
|
import bisq.proto.grpc.CancelOfferRequest;
|
||||||
import bisq.proto.grpc.CreateOfferRequest;
|
import bisq.proto.grpc.CreateOfferRequest;
|
||||||
import bisq.proto.grpc.EditOfferRequest;
|
import bisq.proto.grpc.EditOfferRequest;
|
||||||
|
@ -38,8 +39,8 @@ import static bisq.proto.grpc.EditOfferRequest.EditType.MKT_PRICE_MARGIN_ONLY;
|
||||||
import static bisq.proto.grpc.EditOfferRequest.EditType.TRIGGER_PRICE_ONLY;
|
import static bisq.proto.grpc.EditOfferRequest.EditType.TRIGGER_PRICE_ONLY;
|
||||||
import static java.util.Comparator.comparing;
|
import static java.util.Comparator.comparing;
|
||||||
import static java.util.stream.Collectors.toList;
|
import static java.util.stream.Collectors.toList;
|
||||||
import static protobuf.OfferPayload.Direction.BUY;
|
import static protobuf.OfferDirection.BUY;
|
||||||
import static protobuf.OfferPayload.Direction.SELL;
|
import static protobuf.OfferDirection.SELL;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -207,6 +208,13 @@ public class OffersServiceRequest {
|
||||||
grpcStubs.offersService.cancelOffer(request);
|
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) {
|
public OfferInfo getOffer(String offerId) {
|
||||||
var request = GetOfferRequest.newBuilder()
|
var request = GetOfferRequest.newBuilder()
|
||||||
.setId(offerId)
|
.setId(offerId)
|
||||||
|
@ -214,6 +222,14 @@ public class OffersServiceRequest {
|
||||||
return grpcStubs.offersService.getOffer(request).getOffer();
|
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) {
|
public OfferInfo getMyOffer(String offerId) {
|
||||||
var request = GetMyOfferRequest.newBuilder()
|
var request = GetMyOfferRequest.newBuilder()
|
||||||
.setId(offerId)
|
.setId(offerId)
|
||||||
|
@ -221,6 +237,15 @@ public class OffersServiceRequest {
|
||||||
return grpcStubs.offersService.getMyOffer(request).getOffer();
|
return grpcStubs.offersService.getMyOffer(request).getOffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<BsqSwapOfferInfo> getBsqSwapOffers(String direction, String currencyCode) {
|
||||||
|
var request = GetOffersRequest.newBuilder()
|
||||||
|
.setDirection(direction)
|
||||||
|
.setCurrencyCode(currencyCode)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return grpcStubs.offersService.getBsqSwapOffers(request).getBsqSwapOffersList();
|
||||||
|
}
|
||||||
|
|
||||||
public List<OfferInfo> getOffers(String direction, String currencyCode) {
|
public List<OfferInfo> getOffers(String direction, String currencyCode) {
|
||||||
if (isSupportedCryptoCurrency(currencyCode)) {
|
if (isSupportedCryptoCurrency(currencyCode)) {
|
||||||
return getCryptoCurrencyOffers(direction, currencyCode);
|
return getCryptoCurrencyOffers(direction, currencyCode);
|
||||||
|
@ -251,6 +276,13 @@ public class OffersServiceRequest {
|
||||||
return offers.isEmpty() ? offers : sortOffersByDate(offers);
|
return offers.isEmpty() ? offers : sortOffersByDate(offers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<BsqSwapOfferInfo> getBsqSwapOffersSortedByDate() {
|
||||||
|
ArrayList<BsqSwapOfferInfo> offers = new ArrayList<>();
|
||||||
|
offers.addAll(getBsqSwapOffers(BUY.name(), "BSQ"));
|
||||||
|
offers.addAll(getBsqSwapOffers(SELL.name(), "BSQ"));
|
||||||
|
return sortBsqSwapOffersByDate(offers);
|
||||||
|
}
|
||||||
|
|
||||||
public List<OfferInfo> getBsqOffersSortedByDate() {
|
public List<OfferInfo> getBsqOffersSortedByDate() {
|
||||||
ArrayList<OfferInfo> offers = new ArrayList<>();
|
ArrayList<OfferInfo> offers = new ArrayList<>();
|
||||||
offers.addAll(getCryptoCurrencyOffers(BUY.name(), "BSQ"));
|
offers.addAll(getCryptoCurrencyOffers(BUY.name(), "BSQ"));
|
||||||
|
@ -258,6 +290,14 @@ public class OffersServiceRequest {
|
||||||
return sortOffersByDate(offers);
|
return sortOffersByDate(offers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<BsqSwapOfferInfo> getMyBsqSwapOffers(String direction, String currencyCode) {
|
||||||
|
var request = GetMyOffersRequest.newBuilder()
|
||||||
|
.setDirection(direction)
|
||||||
|
.setCurrencyCode(currencyCode)
|
||||||
|
.build();
|
||||||
|
return grpcStubs.offersService.getMyBsqSwapOffers(request).getBsqSwapOffersList();
|
||||||
|
}
|
||||||
|
|
||||||
public List<OfferInfo> getMyOffers(String direction, String currencyCode) {
|
public List<OfferInfo> getMyOffers(String direction, String currencyCode) {
|
||||||
if (isSupportedCryptoCurrency(currencyCode)) {
|
if (isSupportedCryptoCurrency(currencyCode)) {
|
||||||
return getMyCryptoCurrencyOffers(direction, currencyCode);
|
return getMyCryptoCurrencyOffers(direction, currencyCode);
|
||||||
|
@ -295,11 +335,25 @@ public class OffersServiceRequest {
|
||||||
return sortOffersByDate(offers);
|
return sortOffersByDate(offers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<BsqSwapOfferInfo> getMyBsqSwapOffersSortedByDate() {
|
||||||
|
ArrayList<BsqSwapOfferInfo> 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) {
|
public OfferInfo getMostRecentOffer(String direction, String currencyCode) {
|
||||||
List<OfferInfo> offers = getOffersSortedByDate(direction, currencyCode);
|
List<OfferInfo> offers = getOffersSortedByDate(direction, currencyCode);
|
||||||
return offers.isEmpty() ? null : offers.get(offers.size() - 1);
|
return offers.isEmpty() ? null : offers.get(offers.size() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<BsqSwapOfferInfo> sortBsqSwapOffersByDate(List<BsqSwapOfferInfo> offerInfoList) {
|
||||||
|
return offerInfoList.stream()
|
||||||
|
.sorted(comparing(BsqSwapOfferInfo::getDate))
|
||||||
|
.collect(toList());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public List<OfferInfo> sortOffersByDate(List<OfferInfo> offerInfoList) {
|
public List<OfferInfo> sortOffersByDate(List<OfferInfo> offerInfoList) {
|
||||||
return offerInfoList.stream()
|
return offerInfoList.stream()
|
||||||
.sorted(comparing(OfferInfo::getDate))
|
.sorted(comparing(OfferInfo::getDate))
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package bisq.cli.request;
|
package bisq.cli.request;
|
||||||
|
|
||||||
|
import bisq.proto.grpc.BsqSwapTradeInfo;
|
||||||
import bisq.proto.grpc.ConfirmPaymentReceivedRequest;
|
import bisq.proto.grpc.ConfirmPaymentReceivedRequest;
|
||||||
import bisq.proto.grpc.ConfirmPaymentStartedRequest;
|
import bisq.proto.grpc.ConfirmPaymentStartedRequest;
|
||||||
import bisq.proto.grpc.GetTradeRequest;
|
import bisq.proto.grpc.GetTradeRequest;
|
||||||
|
@ -55,6 +56,13 @@ public class TradesServiceRequest {
|
||||||
throw new IllegalStateException(reply.getFailureReason().getDescription());
|
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) {
|
public TradeInfo getTrade(String tradeId) {
|
||||||
var request = GetTradeRequest.newBuilder()
|
var request = GetTradeRequest.newBuilder()
|
||||||
.setTradeId(tradeId)
|
.setTradeId(tradeId)
|
||||||
|
|
|
@ -42,5 +42,6 @@ public enum Capability {
|
||||||
REFUND_AGENT, // Supports refund agents
|
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.
|
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.
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,7 @@ public class ConfigFileEditor {
|
||||||
if (ConfigFileOption.isOption(line)) {
|
if (ConfigFileOption.isOption(line)) {
|
||||||
ConfigFileOption option = ConfigFileOption.parse(line);
|
ConfigFileOption option = ConfigFileOption.parse(line);
|
||||||
if (option.name.equals(name)) {
|
if (option.name.equals(name)) {
|
||||||
log.warn("Cleared existing config file option '{}'", option);
|
log.debug("Cleared existing config file option '{}'", option);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
181
common/src/main/java/bisq/common/crypto/HashCashService.java
Normal file
181
common/src/main/java/bisq/common/crypto/HashCashService.java
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<byte[], byte[], Boolean> isChallengeValid = Arrays::equals;
|
||||||
|
private static final BiFunction<Integer, Integer, Boolean> isDifficultyValid = Integer::equals;
|
||||||
|
|
||||||
|
public static CompletableFuture<ProofOfWork> 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<byte[], byte[], Boolean> challengeValidation,
|
||||||
|
BiFunction<Integer, Integer, Boolean> 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<ProofOfWork> mint(byte[] payload,
|
||||||
|
byte[] challenge,
|
||||||
|
int difficulty,
|
||||||
|
BiFunction<byte[], Integer, Boolean> 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<byte[], Integer, Boolean> testDifficulty) {
|
||||||
|
return verify(proofOfWork,
|
||||||
|
controlChallenge,
|
||||||
|
controlDifficulty,
|
||||||
|
HashCashService.isChallengeValid,
|
||||||
|
HashCashService.isDifficultyValid,
|
||||||
|
testDifficulty);
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean verify(ProofOfWork proofOfWork,
|
||||||
|
byte[] controlChallenge,
|
||||||
|
int controlDifficulty,
|
||||||
|
BiFunction<byte[], byte[], Boolean> challengeValidation,
|
||||||
|
BiFunction<Integer, Integer, Boolean> difficultyValidation,
|
||||||
|
BiFunction<byte[], Integer, Boolean> testDifficulty) {
|
||||||
|
return challengeValidation.apply(proofOfWork.getChallenge(), controlChallenge) &&
|
||||||
|
difficultyValidation.apply(proofOfWork.getNumLeadingZeros(), controlDifficulty) &&
|
||||||
|
verify(proofOfWork, testDifficulty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean verify(ProofOfWork proofOfWork, BiFunction<byte[], Integer, Boolean> 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);
|
||||||
|
}
|
||||||
|
}
|
122
common/src/main/java/bisq/common/crypto/ProofOfWork.java
Normal file
122
common/src/main/java/bisq/common/crypto/ProofOfWork.java
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Bisq.
|
||||||
|
*
|
||||||
|
* Bisq is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package bisq.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}";
|
||||||
|
}
|
||||||
|
}
|
156
common/src/main/java/bisq/common/crypto/ProofOfWorkService.java
Normal file
156
common/src/main/java/bisq/common/crypto/ProofOfWorkService.java
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<byte[], byte[], Boolean> isChallengeValid = Arrays::equals;
|
||||||
|
private static final BiFunction<BigInteger, BigInteger, Boolean> isTargetValid = BigInteger::equals;
|
||||||
|
|
||||||
|
public static CompletableFuture<ProofOfWork> 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<byte[], byte[], Boolean> challengeValidation,
|
||||||
|
BiFunction<BigInteger, BigInteger, Boolean> 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<ProofOfWork> mint(byte[] payload,
|
||||||
|
byte[] challenge,
|
||||||
|
BigInteger target,
|
||||||
|
BiFunction<byte[], BigInteger, Boolean> 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<byte[], BigInteger, Boolean> testTarget) {
|
||||||
|
return verify(proofOfWork,
|
||||||
|
controlChallenge,
|
||||||
|
controlTarget,
|
||||||
|
ProofOfWorkService.isChallengeValid,
|
||||||
|
ProofOfWorkService.isTargetValid,
|
||||||
|
testTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean verify(ProofOfWork proofOfWork,
|
||||||
|
byte[] controlChallenge,
|
||||||
|
BigInteger controlTarget,
|
||||||
|
BiFunction<byte[], byte[], Boolean> challengeValidation,
|
||||||
|
BiFunction<BigInteger, BigInteger, Boolean> targetValidation,
|
||||||
|
BiFunction<byte[], BigInteger, Boolean> testTarget) {
|
||||||
|
return challengeValidation.apply(proofOfWork.getChallenge(), controlChallenge) &&
|
||||||
|
targetValidation.apply(proofOfWork.getTarget(), controlTarget) &&
|
||||||
|
verify(proofOfWork, testTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean verify(ProofOfWork proofOfWork, BiFunction<byte[], BigInteger, Boolean> 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,11 +19,6 @@ package bisq.common.util;
|
||||||
|
|
||||||
import org.bitcoinj.core.Utils;
|
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.base.Splitter;
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||||
|
@ -87,15 +82,6 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class Utilities {
|
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) {
|
public static ExecutorService getSingleThreadExecutor(String name) {
|
||||||
final ThreadFactory threadFactory = new ThreadFactoryBuilder()
|
final ThreadFactory threadFactory = new ThreadFactoryBuilder()
|
||||||
|
@ -449,18 +435,6 @@ public class Utilities {
|
||||||
return new File(Utilities.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getPath();
|
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) {
|
public static String toTruncatedString(Object message) {
|
||||||
return toTruncatedString(message, 200, true);
|
return toTruncatedString(message, 200, true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<ProofOfWork> 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.");
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<ProofOfWork> 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.");
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,7 +23,7 @@ import bisq.core.filter.FilterManager;
|
||||||
import bisq.core.locale.CurrencyUtil;
|
import bisq.core.locale.CurrencyUtil;
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
import bisq.core.offer.OfferPayload;
|
import bisq.core.offer.OfferDirection;
|
||||||
import bisq.core.offer.OfferRestrictions;
|
import bisq.core.offer.OfferRestrictions;
|
||||||
import bisq.core.payment.AssetAccount;
|
import bisq.core.payment.AssetAccount;
|
||||||
import bisq.core.payment.ChargeBackRisk;
|
import bisq.core.payment.ChargeBackRisk;
|
||||||
|
@ -33,9 +33,9 @@ import bisq.core.payment.payload.PaymentMethod;
|
||||||
import bisq.core.support.dispute.Dispute;
|
import bisq.core.support.dispute.Dispute;
|
||||||
import bisq.core.support.dispute.DisputeResult;
|
import bisq.core.support.dispute.DisputeResult;
|
||||||
import bisq.core.support.dispute.arbitration.TraderDataItem;
|
import bisq.core.support.dispute.arbitration.TraderDataItem;
|
||||||
import bisq.core.trade.Contract;
|
import bisq.core.trade.model.bisq_v1.Contract;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.model.bisq_v1.Trade;
|
||||||
import bisq.core.trade.protocol.TradingPeer;
|
import bisq.core.trade.protocol.bisq_v1.model.TradingPeer;
|
||||||
import bisq.core.user.User;
|
import bisq.core.user.User;
|
||||||
|
|
||||||
import bisq.network.p2p.BootstrapListener;
|
import bisq.network.p2p.BootstrapListener;
|
||||||
|
@ -308,7 +308,7 @@ public class AccountAgeWitnessService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<AccountAgeWitness> findTradePeerWitness(Trade trade) {
|
private Optional<AccountAgeWitness> findTradePeerWitness(Trade trade) {
|
||||||
TradingPeer tradingPeer = trade.getProcessModel().getTradingPeer();
|
TradingPeer tradingPeer = trade.getProcessModel().getTradePeer();
|
||||||
return (tradingPeer == null ||
|
return (tradingPeer == null ||
|
||||||
tradingPeer.getPaymentAccountPayload() == null ||
|
tradingPeer.getPaymentAccountPayload() == null ||
|
||||||
tradingPeer.getPubKeyRing() == null) ?
|
tradingPeer.getPubKeyRing() == null) ?
|
||||||
|
@ -421,11 +421,11 @@ public class AccountAgeWitnessService {
|
||||||
String currencyCode,
|
String currencyCode,
|
||||||
AccountAgeWitness accountAgeWitness,
|
AccountAgeWitness accountAgeWitness,
|
||||||
AccountAge accountAgeCategory,
|
AccountAge accountAgeCategory,
|
||||||
OfferPayload.Direction direction,
|
OfferDirection direction,
|
||||||
PaymentMethod paymentMethod) {
|
PaymentMethod paymentMethod) {
|
||||||
if (CurrencyUtil.isCryptoCurrency(currencyCode) ||
|
if (CurrencyUtil.isCryptoCurrency(currencyCode) ||
|
||||||
!PaymentMethod.hasChargebackRisk(paymentMethod, currencyCode) ||
|
!PaymentMethod.hasChargebackRisk(paymentMethod, currencyCode) ||
|
||||||
direction == OfferPayload.Direction.SELL) {
|
direction == OfferDirection.SELL) {
|
||||||
return maxTradeLimit.value;
|
return maxTradeLimit.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -500,7 +500,7 @@ public class AccountAgeWitnessService {
|
||||||
return getAccountAge(getMyWitness(paymentAccountPayload), new Date());
|
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)
|
if (paymentAccount == null)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
@ -564,7 +564,7 @@ public class AccountAgeWitnessService {
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Check if the peers trade limit is not less than the trade amount
|
// Check if the peers trade limit is not less than the trade amount
|
||||||
if (!verifyPeersTradeLimit(trade.getOffer(), trade.getTradeAmount(), peersWitness, peersCurrentDate,
|
if (!verifyPeersTradeLimit(trade.getOffer(), trade.getAmount(), peersWitness, peersCurrentDate,
|
||||||
errorMessageHandler)) {
|
errorMessageHandler)) {
|
||||||
log.error("verifyPeersTradeLimit failed: peersPaymentAccountPayload {}", peersPaymentAccountPayload);
|
log.error("verifyPeersTradeLimit failed: peersPaymentAccountPayload {}", peersPaymentAccountPayload);
|
||||||
return false;
|
return false;
|
||||||
|
@ -641,13 +641,12 @@ public class AccountAgeWitnessService {
|
||||||
ErrorMessageHandler errorMessageHandler) {
|
ErrorMessageHandler errorMessageHandler) {
|
||||||
checkNotNull(offer);
|
checkNotNull(offer);
|
||||||
final String currencyCode = offer.getCurrencyCode();
|
final String currencyCode = offer.getCurrencyCode();
|
||||||
final Coin defaultMaxTradeLimit = PaymentMethod.getPaymentMethodById(
|
final Coin defaultMaxTradeLimit = offer.getPaymentMethod().getMaxTradeLimitAsCoin(currencyCode);
|
||||||
offer.getOfferPayload().getPaymentMethodId()).getMaxTradeLimitAsCoin(currencyCode);
|
|
||||||
long peersCurrentTradeLimit = defaultMaxTradeLimit.value;
|
long peersCurrentTradeLimit = defaultMaxTradeLimit.value;
|
||||||
if (!hasTradeLimitException(peersWitness)) {
|
if (!hasTradeLimitException(peersWitness)) {
|
||||||
final long accountSignAge = getWitnessSignAge(peersWitness, peersCurrentDate);
|
final long accountSignAge = getWitnessSignAge(peersWitness, peersCurrentDate);
|
||||||
AccountAge accountAgeCategory = getPeersAccountAgeCategory(accountSignAge);
|
AccountAge accountAgeCategory = getPeersAccountAgeCategory(accountSignAge);
|
||||||
OfferPayload.Direction direction = offer.isMyOffer(keyRing) ?
|
OfferDirection direction = offer.isMyOffer(keyRing) ?
|
||||||
offer.getMirroredDirection() : offer.getDirection();
|
offer.getMirroredDirection() : offer.getDirection();
|
||||||
peersCurrentTradeLimit = getTradeLimit(defaultMaxTradeLimit, currencyCode, peersWitness,
|
peersCurrentTradeLimit = getTradeLimit(defaultMaxTradeLimit, currencyCode, peersWitness,
|
||||||
accountAgeCategory, direction, offer.getPaymentMethod());
|
accountAgeCategory, direction, offer.getPaymentMethod());
|
||||||
|
@ -731,9 +730,9 @@ public class AccountAgeWitnessService {
|
||||||
|
|
||||||
public Optional<SignedWitness> traderSignAndPublishPeersAccountAgeWitness(Trade trade) {
|
public Optional<SignedWitness> traderSignAndPublishPeersAccountAgeWitness(Trade trade) {
|
||||||
AccountAgeWitness peersWitness = findTradePeerWitness(trade).orElse(null);
|
AccountAgeWitness peersWitness = findTradePeerWitness(trade).orElse(null);
|
||||||
Coin tradeAmount = trade.getTradeAmount();
|
Coin tradeAmount = trade.getAmount();
|
||||||
checkNotNull(trade.getProcessModel().getTradingPeer().getPubKeyRing(), "Peer must have a keyring");
|
checkNotNull(trade.getProcessModel().getTradePeer().getPubKeyRing(), "Peer must have a keyring");
|
||||||
PublicKey peersPubKey = trade.getProcessModel().getTradingPeer().getPubKeyRing().getSignaturePubKey();
|
PublicKey peersPubKey = trade.getProcessModel().getTradePeer().getPubKeyRing().getSignaturePubKey();
|
||||||
checkNotNull(peersWitness, "Not able to find peers witness, unable to sign for trade {}",
|
checkNotNull(peersWitness, "Not able to find peers witness, unable to sign for trade {}",
|
||||||
trade.toString());
|
trade.toString());
|
||||||
checkNotNull(tradeAmount, "Trade amount must not be null");
|
checkNotNull(tradeAmount, "Trade amount must not be null");
|
||||||
|
@ -926,7 +925,7 @@ public class AccountAgeWitnessService {
|
||||||
|
|
||||||
return accountIsSigner(myWitness) &&
|
return accountIsSigner(myWitness) &&
|
||||||
!peerHasSignedWitness(trade) &&
|
!peerHasSignedWitness(trade) &&
|
||||||
tradeAmountIsSufficient(trade.getTradeAmount());
|
tradeAmountIsSufficient(trade.getAmount());
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSignInfoFromAccount(PaymentAccount paymentAccount) {
|
public String getSignInfoFromAccount(PaymentAccount paymentAccount) {
|
||||||
|
|
|
@ -20,7 +20,7 @@ package bisq.core.account.witness;
|
||||||
import bisq.core.account.sign.SignedWitness;
|
import bisq.core.account.sign.SignedWitness;
|
||||||
import bisq.core.account.sign.SignedWitnessService;
|
import bisq.core.account.sign.SignedWitnessService;
|
||||||
import bisq.core.payment.payload.PaymentAccountPayload;
|
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;
|
import bisq.network.p2p.storage.P2PDataStorage;
|
||||||
|
|
||||||
|
@ -143,7 +143,7 @@ public class AccountAgeWitnessUtils {
|
||||||
}
|
}
|
||||||
boolean isSignWitnessTrade = accountAgeWitnessService.accountIsSigner(witness) &&
|
boolean isSignWitnessTrade = accountAgeWitnessService.accountIsSigner(witness) &&
|
||||||
!accountAgeWitnessService.peerHasSignedWitness(trade) &&
|
!accountAgeWitnessService.peerHasSignedWitness(trade) &&
|
||||||
accountAgeWitnessService.tradeAmountIsSufficient(trade.getTradeAmount());
|
accountAgeWitnessService.tradeAmountIsSufficient(trade.getAmount());
|
||||||
log.info("AccountSigning debug log: " +
|
log.info("AccountSigning debug log: " +
|
||||||
"\ntradeId: {}" +
|
"\ntradeId: {}" +
|
||||||
"\nis buyer: {}" +
|
"\nis buyer: {}" +
|
||||||
|
@ -164,8 +164,8 @@ public class AccountAgeWitnessUtils {
|
||||||
checkingSignTrade, // Following cases added to use same logic as in seller signing check
|
checkingSignTrade, // Following cases added to use same logic as in seller signing check
|
||||||
accountAgeWitnessService.accountIsSigner(witness),
|
accountAgeWitnessService.accountIsSigner(witness),
|
||||||
accountAgeWitnessService.peerHasSignedWitness(trade),
|
accountAgeWitnessService.peerHasSignedWitness(trade),
|
||||||
trade.getTradeAmount(),
|
trade.getAmount(),
|
||||||
accountAgeWitnessService.tradeAmountIsSufficient(trade.getTradeAmount()),
|
accountAgeWitnessService.tradeAmountIsSufficient(trade.getAmount()),
|
||||||
isSignWitnessTrade);
|
isSignWitnessTrade);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,9 @@ import bisq.core.offer.Offer;
|
||||||
import bisq.core.offer.OpenOffer;
|
import bisq.core.offer.OpenOffer;
|
||||||
import bisq.core.payment.PaymentAccount;
|
import bisq.core.payment.PaymentAccount;
|
||||||
import bisq.core.payment.payload.PaymentMethod;
|
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.TradeStatistics3;
|
||||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||||
|
|
||||||
|
@ -117,6 +119,10 @@ public class CoreApi {
|
||||||
// Offers
|
// Offers
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public Offer getBsqSwapOffer(String id) {
|
||||||
|
return coreOffersService.getBsqSwapOffer(id);
|
||||||
|
}
|
||||||
|
|
||||||
public Offer getOffer(String id) {
|
public Offer getOffer(String id) {
|
||||||
return coreOffersService.getOffer(id);
|
return coreOffersService.getOffer(id);
|
||||||
}
|
}
|
||||||
|
@ -125,6 +131,14 @@ public class CoreApi {
|
||||||
return coreOffersService.getMyOffer(id);
|
return coreOffersService.getMyOffer(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Offer getMyBsqSwapOffer(String id) {
|
||||||
|
return coreOffersService.getMyBsqSwapOffer(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Offer> getBsqSwapOffers(String direction) {
|
||||||
|
return coreOffersService.getBsqSwapOffers(direction);
|
||||||
|
}
|
||||||
|
|
||||||
public List<Offer> getOffers(String direction, String currencyCode) {
|
public List<Offer> getOffers(String direction, String currencyCode) {
|
||||||
return coreOffersService.getOffers(direction, currencyCode);
|
return coreOffersService.getOffers(direction, currencyCode);
|
||||||
}
|
}
|
||||||
|
@ -133,7 +147,27 @@ public class CoreApi {
|
||||||
return coreOffersService.getMyOffers(direction, currencyCode);
|
return coreOffersService.getMyOffers(direction, currencyCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void createAnPlaceOffer(String currencyCode,
|
public List<Offer> getMyBsqSwapOffers(String direction) {
|
||||||
|
return coreOffersService.getMyBsqSwapOffers(direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public OpenOffer getMyOpenBsqSwapOffer(String id) {
|
||||||
|
return coreOffersService.getMyOpenBsqSwapOffer(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createAndPlaceBsqSwapOffer(String directionAsString,
|
||||||
|
long amountAsLong,
|
||||||
|
long minAmountAsLong,
|
||||||
|
String priceAsString,
|
||||||
|
Consumer<Offer> resultHandler) {
|
||||||
|
coreOffersService.createAndPlaceBsqSwapOffer(directionAsString,
|
||||||
|
amountAsLong,
|
||||||
|
minAmountAsLong,
|
||||||
|
priceAsString,
|
||||||
|
resultHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createAndPlaceOffer(String currencyCode,
|
||||||
String directionAsString,
|
String directionAsString,
|
||||||
String priceAsString,
|
String priceAsString,
|
||||||
boolean useMarketBasedPrice,
|
boolean useMarketBasedPrice,
|
||||||
|
@ -206,11 +240,13 @@ public class CoreApi {
|
||||||
public PaymentAccount createCryptoCurrencyPaymentAccount(String accountName,
|
public PaymentAccount createCryptoCurrencyPaymentAccount(String accountName,
|
||||||
String currencyCode,
|
String currencyCode,
|
||||||
String address,
|
String address,
|
||||||
boolean tradeInstant) {
|
boolean tradeInstant,
|
||||||
|
boolean isBsqSwap) {
|
||||||
return paymentAccountsService.createCryptoCurrencyPaymentAccount(accountName,
|
return paymentAccountsService.createCryptoCurrencyPaymentAccount(accountName,
|
||||||
currencyCode,
|
currencyCode,
|
||||||
address,
|
address,
|
||||||
tradeInstant);
|
tradeInstant,
|
||||||
|
isBsqSwap);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<PaymentMethod> getCryptoCurrencyPaymentMethods() {
|
public List<PaymentMethod> getCryptoCurrencyPaymentMethods() {
|
||||||
|
@ -229,6 +265,19 @@ public class CoreApi {
|
||||||
// Trades
|
// Trades
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public void takeBsqSwapOffer(String offerId,
|
||||||
|
String paymentAccountId,
|
||||||
|
String takerFeeCurrencyCode,
|
||||||
|
TradeResultHandler<BsqSwapTrade> tradeResultHandler,
|
||||||
|
ErrorMessageHandler errorMessageHandler) {
|
||||||
|
Offer bsqSwapOffer = coreOffersService.getBsqSwapOffer(offerId);
|
||||||
|
coreTradesService.takeBsqSwapOffer(bsqSwapOffer,
|
||||||
|
paymentAccountId,
|
||||||
|
takerFeeCurrencyCode,
|
||||||
|
tradeResultHandler,
|
||||||
|
errorMessageHandler);
|
||||||
|
}
|
||||||
|
|
||||||
public void takeOffer(String offerId,
|
public void takeOffer(String offerId,
|
||||||
String paymentAccountId,
|
String paymentAccountId,
|
||||||
String takerFeeCurrencyCode,
|
String takerFeeCurrencyCode,
|
||||||
|
@ -258,6 +307,10 @@ public class CoreApi {
|
||||||
coreTradesService.withdrawFunds(tradeId, address, memo);
|
coreTradesService.withdrawFunds(tradeId, address, memo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BsqSwapTrade getBsqSwapTrade(String tradeId) {
|
||||||
|
return coreTradesService.getBsqSwapTrade(tradeId);
|
||||||
|
}
|
||||||
|
|
||||||
public Trade getTrade(String tradeId) {
|
public Trade getTrade(String tradeId) {
|
||||||
return coreTradesService.getTrade(tradeId);
|
return coreTradesService.getTrade(tradeId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,15 +19,17 @@ package bisq.core.api;
|
||||||
|
|
||||||
import bisq.core.monetary.Altcoin;
|
import bisq.core.monetary.Altcoin;
|
||||||
import bisq.core.monetary.Price;
|
import bisq.core.monetary.Price;
|
||||||
import bisq.core.offer.CreateOfferService;
|
|
||||||
import bisq.core.offer.MutableOfferPayloadFields;
|
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
import bisq.core.offer.OfferBookService;
|
import bisq.core.offer.OfferBookService;
|
||||||
import bisq.core.offer.OfferFilter;
|
import bisq.core.offer.OfferDirection;
|
||||||
import bisq.core.offer.OfferPayload;
|
import bisq.core.offer.OfferFilterService;
|
||||||
import bisq.core.offer.OfferUtil;
|
import bisq.core.offer.OfferUtil;
|
||||||
import bisq.core.offer.OpenOffer;
|
import bisq.core.offer.OpenOffer;
|
||||||
import bisq.core.offer.OpenOfferManager;
|
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.payment.PaymentAccount;
|
||||||
import bisq.core.provider.price.PriceFeedService;
|
import bisq.core.provider.price.PriceFeedService;
|
||||||
import bisq.core.user.User;
|
import bisq.core.user.User;
|
||||||
|
@ -57,8 +59,7 @@ import static bisq.common.util.MathUtils.roundDoubleToLong;
|
||||||
import static bisq.common.util.MathUtils.scaleUpByPowerOf10;
|
import static bisq.common.util.MathUtils.scaleUpByPowerOf10;
|
||||||
import static bisq.core.locale.CurrencyUtil.isCryptoCurrency;
|
import static bisq.core.locale.CurrencyUtil.isCryptoCurrency;
|
||||||
import static bisq.core.offer.Offer.State;
|
import static bisq.core.offer.Offer.State;
|
||||||
import static bisq.core.offer.OfferPayload.Direction;
|
import static bisq.core.offer.OfferDirection.BUY;
|
||||||
import static bisq.core.offer.OfferPayload.Direction.BUY;
|
|
||||||
import static bisq.core.offer.OpenOffer.State.AVAILABLE;
|
import static bisq.core.offer.OpenOffer.State.AVAILABLE;
|
||||||
import static bisq.core.offer.OpenOffer.State.DEACTIVATED;
|
import static bisq.core.offer.OpenOffer.State.DEACTIVATED;
|
||||||
import static bisq.core.payment.PaymentAccountUtil.isPaymentAccountValidForOffer;
|
import static bisq.core.payment.PaymentAccountUtil.isPaymentAccountValidForOffer;
|
||||||
|
@ -85,8 +86,9 @@ class CoreOffersService {
|
||||||
private final CoreWalletsService coreWalletsService;
|
private final CoreWalletsService coreWalletsService;
|
||||||
private final CreateOfferService createOfferService;
|
private final CreateOfferService createOfferService;
|
||||||
private final OfferBookService offerBookService;
|
private final OfferBookService offerBookService;
|
||||||
private final OfferFilter offerFilter;
|
private final OfferFilterService offerFilterService;
|
||||||
private final OpenOfferManager openOfferManager;
|
private final OpenOfferManager openOfferManager;
|
||||||
|
private final OpenBsqSwapOfferService openBsqSwapOfferService;
|
||||||
private final OfferUtil offerUtil;
|
private final OfferUtil offerUtil;
|
||||||
private final PriceFeedService priceFeedService;
|
private final PriceFeedService priceFeedService;
|
||||||
private final User user;
|
private final User user;
|
||||||
|
@ -97,8 +99,9 @@ class CoreOffersService {
|
||||||
CoreWalletsService coreWalletsService,
|
CoreWalletsService coreWalletsService,
|
||||||
CreateOfferService createOfferService,
|
CreateOfferService createOfferService,
|
||||||
OfferBookService offerBookService,
|
OfferBookService offerBookService,
|
||||||
OfferFilter offerFilter,
|
OfferFilterService offerFilterService,
|
||||||
OpenOfferManager openOfferManager,
|
OpenOfferManager openOfferManager,
|
||||||
|
OpenBsqSwapOfferService openBsqSwapOfferService,
|
||||||
OfferUtil offerUtil,
|
OfferUtil offerUtil,
|
||||||
PriceFeedService priceFeedService,
|
PriceFeedService priceFeedService,
|
||||||
User user) {
|
User user) {
|
||||||
|
@ -107,18 +110,29 @@ class CoreOffersService {
|
||||||
this.coreWalletsService = coreWalletsService;
|
this.coreWalletsService = coreWalletsService;
|
||||||
this.createOfferService = createOfferService;
|
this.createOfferService = createOfferService;
|
||||||
this.offerBookService = offerBookService;
|
this.offerBookService = offerBookService;
|
||||||
this.offerFilter = offerFilter;
|
this.offerFilterService = offerFilterService;
|
||||||
this.openOfferManager = openOfferManager;
|
this.openOfferManager = openOfferManager;
|
||||||
|
this.openBsqSwapOfferService = openBsqSwapOfferService;
|
||||||
this.offerUtil = offerUtil;
|
this.offerUtil = offerUtil;
|
||||||
this.priceFeedService = priceFeedService;
|
this.priceFeedService = priceFeedService;
|
||||||
this.user = user;
|
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) {
|
Offer getOffer(String id) {
|
||||||
return offerBookService.getOffers().stream()
|
return offerBookService.getOffers().stream()
|
||||||
.filter(o -> o.getId().equals(id))
|
.filter(o -> o.getId().equals(id))
|
||||||
.filter(o -> !o.isMyOffer(keyRing))
|
.filter(o -> !o.isMyOffer(keyRing))
|
||||||
.filter(o -> offerFilter.canTakeOffer(o, coreContext.isApiUser()).isValid())
|
.filter(o -> offerFilterService.canTakeOffer(o, coreContext.isApiUser()).isValid())
|
||||||
.findAny().orElseThrow(() ->
|
.findAny().orElseThrow(() ->
|
||||||
new IllegalStateException(format("offer with id '%s' not found", id)));
|
new IllegalStateException(format("offer with id '%s' not found", id)));
|
||||||
}
|
}
|
||||||
|
@ -131,11 +145,31 @@ class CoreOffersService {
|
||||||
new IllegalStateException(format("offer with id '%s' not found", id)));
|
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<Offer> getBsqSwapOffers(String direction) {
|
||||||
|
var offers = offerBookService.getOffers().stream()
|
||||||
|
.filter(o -> !o.isMyOffer(keyRing))
|
||||||
|
.filter(o -> o.getDirection().name().equalsIgnoreCase(direction))
|
||||||
|
.filter(o -> o.isBsqSwapOffer())
|
||||||
|
.sorted(priceComparator(direction))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return offers;
|
||||||
|
}
|
||||||
|
|
||||||
List<Offer> getOffers(String direction, String currencyCode) {
|
List<Offer> getOffers(String direction, String currencyCode) {
|
||||||
return offerBookService.getOffers().stream()
|
return offerBookService.getOffers().stream()
|
||||||
.filter(o -> !o.isMyOffer(keyRing))
|
.filter(o -> !o.isMyOffer(keyRing))
|
||||||
.filter(o -> offerMatchesDirectionAndCurrency(o, direction, currencyCode))
|
.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))
|
.sorted(priceComparator(direction))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
@ -148,6 +182,24 @@ class CoreOffersService {
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Offer> getMyBsqSwapOffers(String direction) {
|
||||||
|
var offers = offerBookService.getOffers().stream()
|
||||||
|
.filter(o -> o.isMyOffer(keyRing))
|
||||||
|
.filter(o -> o.getDirection().name().equalsIgnoreCase(direction))
|
||||||
|
.filter(Offer::isBsqSwapOffer)
|
||||||
|
.sorted(priceComparator(direction))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return offers;
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenOffer getMyOpenBsqSwapOffer(String id) {
|
||||||
|
return openOfferManager.getOpenOfferById(id)
|
||||||
|
.filter(open -> open.getOffer().isMyOffer(keyRing))
|
||||||
|
.filter(open -> open.getOffer().isBsqSwapOffer())
|
||||||
|
.orElseThrow(() ->
|
||||||
|
new IllegalStateException(format("openoffer with id '%s' not found", id)));
|
||||||
|
}
|
||||||
|
|
||||||
OpenOffer getMyOpenOffer(String id) {
|
OpenOffer getMyOpenOffer(String id) {
|
||||||
return openOfferManager.getOpenOfferById(id)
|
return openOfferManager.getOpenOfferById(id)
|
||||||
.filter(open -> open.getOffer().isMyOffer(keyRing))
|
.filter(open -> open.getOffer().isMyOffer(keyRing))
|
||||||
|
@ -161,7 +213,28 @@ class CoreOffersService {
|
||||||
.isPresent();
|
.isPresent();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and place new offer.
|
void createAndPlaceBsqSwapOffer(String directionAsString,
|
||||||
|
long amountAsLong,
|
||||||
|
long minAmountAsLong,
|
||||||
|
String priceAsString,
|
||||||
|
Consumer<Offer> 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,
|
void createAndPlaceOffer(String currencyCode,
|
||||||
String directionAsString,
|
String directionAsString,
|
||||||
String priceAsString,
|
String priceAsString,
|
||||||
|
@ -183,8 +256,8 @@ class CoreOffersService {
|
||||||
throw new IllegalArgumentException(format("payment account with id %s not found", paymentAccountId));
|
throw new IllegalArgumentException(format("payment account with id %s not found", paymentAccountId));
|
||||||
|
|
||||||
String upperCaseCurrencyCode = currencyCode.toUpperCase();
|
String upperCaseCurrencyCode = currencyCode.toUpperCase();
|
||||||
String offerId = createOfferService.getRandomOfferId();
|
String offerId = OfferUtil.getRandomOfferId();
|
||||||
Direction direction = Direction.valueOf(directionAsString.toUpperCase());
|
OfferDirection direction = OfferDirection.valueOf(directionAsString.toUpperCase());
|
||||||
Price price = Price.valueOf(upperCaseCurrencyCode, priceStringToLong(priceAsString, upperCaseCurrencyCode));
|
Price price = Price.valueOf(upperCaseCurrencyCode, priceStringToLong(priceAsString, upperCaseCurrencyCode));
|
||||||
Coin amount = Coin.valueOf(amountAsLong);
|
Coin amount = Coin.valueOf(amountAsLong);
|
||||||
Coin minAmount = Coin.valueOf(minAmountAsLong);
|
Coin minAmount = Coin.valueOf(minAmountAsLong);
|
||||||
|
@ -256,7 +329,7 @@ class CoreOffersService {
|
||||||
editedMarketPriceMargin,
|
editedMarketPriceMargin,
|
||||||
editType);
|
editType);
|
||||||
Offer editedOffer = new Offer(editedPayload);
|
Offer editedOffer = new Offer(editedPayload);
|
||||||
priceFeedService.setCurrencyCode(openOffer.getOffer().getOfferPayload().getCurrencyCode());
|
priceFeedService.setCurrencyCode(openOffer.getOffer().getCurrencyCode());
|
||||||
editedOffer.setPriceFeedService(priceFeedService);
|
editedOffer.setPriceFeedService(priceFeedService);
|
||||||
editedOffer.setState(State.AVAILABLE);
|
editedOffer.setState(State.AVAILABLE);
|
||||||
openOfferManager.editOpenOfferStart(openOffer,
|
openOfferManager.editOpenOfferStart(openOffer,
|
||||||
|
@ -277,6 +350,15 @@ class CoreOffersService {
|
||||||
log::error);
|
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,
|
private void placeOffer(Offer offer,
|
||||||
double buyerSecurityDeposit,
|
double buyerSecurityDeposit,
|
||||||
long triggerPrice,
|
long triggerPrice,
|
||||||
|
@ -302,7 +384,7 @@ class CoreOffersService {
|
||||||
// code fields. Note: triggerPrice isDeactivated fields are in OpenOffer, not
|
// code fields. Note: triggerPrice isDeactivated fields are in OpenOffer, not
|
||||||
// in OfferPayload.
|
// in OfferPayload.
|
||||||
Offer offer = openOffer.getOffer();
|
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);
|
boolean isEditingPrice = editType.equals(FIXED_PRICE_ONLY) || editType.equals(FIXED_PRICE_AND_ACTIVATION_STATE);
|
||||||
Price editedPrice;
|
Price editedPrice;
|
||||||
if (isEditingPrice) {
|
if (isEditingPrice) {
|
||||||
|
@ -320,15 +402,15 @@ class CoreOffersService {
|
||||||
Objects.requireNonNull(editedPrice).getValue(),
|
Objects.requireNonNull(editedPrice).getValue(),
|
||||||
isUsingMktPriceMargin ? exactMultiply(editedMarketPriceMargin, 0.01) : 0.00,
|
isUsingMktPriceMargin ? exactMultiply(editedMarketPriceMargin, 0.01) : 0.00,
|
||||||
isUsingMktPriceMargin,
|
isUsingMktPriceMargin,
|
||||||
offer.getOfferPayload().getBaseCurrencyCode(),
|
offer.getBaseCurrencyCode(),
|
||||||
offer.getOfferPayload().getCounterCurrencyCode(),
|
offer.getCounterCurrencyCode(),
|
||||||
offer.getPaymentMethod().getId(),
|
offer.getPaymentMethod().getId(),
|
||||||
offer.getMakerPaymentAccountId(),
|
offer.getMakerPaymentAccountId(),
|
||||||
offer.getOfferPayload().getCountryCode(),
|
offer.getCountryCode(),
|
||||||
offer.getOfferPayload().getAcceptedCountryCodes(),
|
offer.getAcceptedCountryCodes(),
|
||||||
offer.getOfferPayload().getBankId(),
|
offer.getBankId(),
|
||||||
offer.getOfferPayload().getAcceptedBankIds(),
|
offer.getAcceptedBankIds(),
|
||||||
offer.getOfferPayload().getExtraDataMap());
|
offer.getExtraDataMap());
|
||||||
log.info("Merging OfferPayload with {}", mutableOfferPayloadFields);
|
log.info("Merging OfferPayload with {}", mutableOfferPayloadFields);
|
||||||
return offerUtil.getMergedOfferPayload(openOffer, mutableOfferPayloadFields);
|
return offerUtil.getMergedOfferPayload(openOffer, mutableOfferPayloadFields);
|
||||||
}
|
}
|
||||||
|
@ -336,7 +418,7 @@ class CoreOffersService {
|
||||||
private void verifyPaymentAccountIsValidForNewOffer(Offer offer, PaymentAccount paymentAccount) {
|
private void verifyPaymentAccountIsValidForNewOffer(Offer offer, PaymentAccount paymentAccount) {
|
||||||
if (!isPaymentAccountValidForOffer(offer, paymentAccount)) {
|
if (!isPaymentAccountValidForOffer(offer, paymentAccount)) {
|
||||||
String error = format("cannot create %s offer with payment account %s",
|
String error = format("cannot create %s offer with payment account %s",
|
||||||
offer.getOfferPayload().getCounterCurrencyCode(),
|
offer.getCounterCurrencyCode(),
|
||||||
paymentAccount.getId());
|
paymentAccount.getId());
|
||||||
throw new IllegalStateException(error);
|
throw new IllegalStateException(error);
|
||||||
}
|
}
|
||||||
|
@ -346,7 +428,7 @@ class CoreOffersService {
|
||||||
String direction,
|
String direction,
|
||||||
String currencyCode) {
|
String currencyCode) {
|
||||||
var offerOfWantedDirection = offer.getDirection().name().equalsIgnoreCase(direction);
|
var offerOfWantedDirection = offer.getDirection().name().equalsIgnoreCase(direction);
|
||||||
var offerInWantedCurrency = offer.getOfferPayload().getCounterCurrencyCode()
|
var offerInWantedCurrency = offer.getCounterCurrencyCode()
|
||||||
.equalsIgnoreCase(currencyCode);
|
.equalsIgnoreCase(currencyCode);
|
||||||
return offerOfWantedDirection && offerInWantedCurrency;
|
return offerOfWantedDirection && offerInWantedCurrency;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ package bisq.core.api;
|
||||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||||
import bisq.core.api.model.PaymentAccountForm;
|
import bisq.core.api.model.PaymentAccountForm;
|
||||||
import bisq.core.locale.CryptoCurrency;
|
import bisq.core.locale.CryptoCurrency;
|
||||||
|
import bisq.core.payment.AssetAccount;
|
||||||
import bisq.core.payment.CryptoCurrencyAccount;
|
import bisq.core.payment.CryptoCurrencyAccount;
|
||||||
import bisq.core.payment.InstantCryptoCurrencyAccount;
|
import bisq.core.payment.InstantCryptoCurrencyAccount;
|
||||||
import bisq.core.payment.PaymentAccount;
|
import bisq.core.payment.PaymentAccount;
|
||||||
|
@ -82,7 +83,7 @@ class CorePaymentAccountsService {
|
||||||
|
|
||||||
List<PaymentMethod> getFiatPaymentMethods() {
|
List<PaymentMethod> getFiatPaymentMethods() {
|
||||||
return PaymentMethod.getPaymentMethods().stream()
|
return PaymentMethod.getPaymentMethods().stream()
|
||||||
.filter(paymentMethod -> !paymentMethod.isAsset())
|
.filter(PaymentMethod::isFiat)
|
||||||
.sorted(Comparator.comparing(PaymentMethod::getId))
|
.sorted(Comparator.comparing(PaymentMethod::getId))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
@ -102,7 +103,8 @@ class CorePaymentAccountsService {
|
||||||
PaymentAccount createCryptoCurrencyPaymentAccount(String accountName,
|
PaymentAccount createCryptoCurrencyPaymentAccount(String accountName,
|
||||||
String currencyCode,
|
String currencyCode,
|
||||||
String address,
|
String address,
|
||||||
boolean tradeInstant) {
|
boolean tradeInstant,
|
||||||
|
boolean isBsqSwap) {
|
||||||
String bsqCode = currencyCode.toUpperCase();
|
String bsqCode = currencyCode.toUpperCase();
|
||||||
if (!bsqCode.equals("BSQ"))
|
if (!bsqCode.equals("BSQ"))
|
||||||
throw new IllegalArgumentException("api does not currently support " + currencyCode + " accounts");
|
throw new IllegalArgumentException("api does not currently support " + currencyCode + " accounts");
|
||||||
|
@ -110,12 +112,21 @@ class CorePaymentAccountsService {
|
||||||
// Validate the BSQ address string but ignore the return value.
|
// Validate the BSQ address string but ignore the return value.
|
||||||
coreWalletsService.getValidBsqAddress(address);
|
coreWalletsService.getValidBsqAddress(address);
|
||||||
|
|
||||||
var cryptoCurrencyAccount = tradeInstant
|
// 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)
|
? (InstantCryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS_INSTANT)
|
||||||
: (CryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS);
|
: (CryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS);
|
||||||
|
}
|
||||||
cryptoCurrencyAccount.init();
|
cryptoCurrencyAccount.init();
|
||||||
cryptoCurrencyAccount.setAccountName(accountName);
|
cryptoCurrencyAccount.setAccountName(accountName);
|
||||||
cryptoCurrencyAccount.setAddress(address);
|
if (!isBsqSwap) {
|
||||||
|
((AssetAccount) cryptoCurrencyAccount).setAddress(address);
|
||||||
|
}
|
||||||
|
|
||||||
Optional<CryptoCurrency> cryptoCurrency = getCryptoCurrency(bsqCode);
|
Optional<CryptoCurrency> cryptoCurrency = getCryptoCurrency(bsqCode);
|
||||||
cryptoCurrency.ifPresent(cryptoCurrencyAccount::setSingleTradeCurrency);
|
cryptoCurrency.ifPresent(cryptoCurrencyAccount::setSingleTradeCurrency);
|
||||||
user.addPaymentAccount(cryptoCurrencyAccount);
|
user.addPaymentAccount(cryptoCurrencyAccount);
|
||||||
|
@ -132,7 +143,7 @@ class CorePaymentAccountsService {
|
||||||
|
|
||||||
List<PaymentMethod> getCryptoCurrencyPaymentMethods() {
|
List<PaymentMethod> getCryptoCurrencyPaymentMethods() {
|
||||||
return PaymentMethod.getPaymentMethods().stream()
|
return PaymentMethod.getPaymentMethods().stream()
|
||||||
.filter(PaymentMethod::isAsset)
|
.filter(PaymentMethod::isAltcoin)
|
||||||
.sorted(Comparator.comparing(PaymentMethod::getId))
|
.sorted(Comparator.comparing(PaymentMethod::getId))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,14 +21,17 @@ import bisq.core.btc.model.AddressEntry;
|
||||||
import bisq.core.btc.wallet.BtcWalletService;
|
import bisq.core.btc.wallet.BtcWalletService;
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
import bisq.core.offer.OfferUtil;
|
import bisq.core.offer.OfferUtil;
|
||||||
import bisq.core.offer.takeoffer.TakeOfferModel;
|
import bisq.core.offer.bisq_v1.TakeOfferModel;
|
||||||
import bisq.core.trade.Tradable;
|
import bisq.core.offer.bsq_swap.BsqSwapTakeOfferModel;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.ClosedTradableManager;
|
||||||
import bisq.core.trade.TradeManager;
|
import bisq.core.trade.TradeManager;
|
||||||
import bisq.core.trade.TradeUtil;
|
import bisq.core.trade.bisq_v1.TradeResultHandler;
|
||||||
import bisq.core.trade.closed.ClosedTradableManager;
|
import bisq.core.trade.bisq_v1.TradeUtil;
|
||||||
import bisq.core.trade.protocol.BuyerProtocol;
|
import bisq.core.trade.model.Tradable;
|
||||||
import bisq.core.trade.protocol.SellerProtocol;
|
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.user.User;
|
||||||
import bisq.core.util.validation.BtcAddressValidator;
|
import bisq.core.util.validation.BtcAddressValidator;
|
||||||
|
|
||||||
|
@ -60,6 +63,7 @@ class CoreTradesService {
|
||||||
private final OfferUtil offerUtil;
|
private final OfferUtil offerUtil;
|
||||||
private final ClosedTradableManager closedTradableManager;
|
private final ClosedTradableManager closedTradableManager;
|
||||||
private final TakeOfferModel takeOfferModel;
|
private final TakeOfferModel takeOfferModel;
|
||||||
|
private final BsqSwapTakeOfferModel bsqSwapTakeOfferModel;
|
||||||
private final TradeManager tradeManager;
|
private final TradeManager tradeManager;
|
||||||
private final TradeUtil tradeUtil;
|
private final TradeUtil tradeUtil;
|
||||||
private final User user;
|
private final User user;
|
||||||
|
@ -71,6 +75,7 @@ class CoreTradesService {
|
||||||
OfferUtil offerUtil,
|
OfferUtil offerUtil,
|
||||||
ClosedTradableManager closedTradableManager,
|
ClosedTradableManager closedTradableManager,
|
||||||
TakeOfferModel takeOfferModel,
|
TakeOfferModel takeOfferModel,
|
||||||
|
BsqSwapTakeOfferModel bsqSwapTakeOfferModel,
|
||||||
TradeManager tradeManager,
|
TradeManager tradeManager,
|
||||||
TradeUtil tradeUtil,
|
TradeUtil tradeUtil,
|
||||||
User user) {
|
User user) {
|
||||||
|
@ -80,11 +85,33 @@ class CoreTradesService {
|
||||||
this.offerUtil = offerUtil;
|
this.offerUtil = offerUtil;
|
||||||
this.closedTradableManager = closedTradableManager;
|
this.closedTradableManager = closedTradableManager;
|
||||||
this.takeOfferModel = takeOfferModel;
|
this.takeOfferModel = takeOfferModel;
|
||||||
|
this.bsqSwapTakeOfferModel = bsqSwapTakeOfferModel;
|
||||||
this.tradeManager = tradeManager;
|
this.tradeManager = tradeManager;
|
||||||
this.tradeUtil = tradeUtil;
|
this.tradeUtil = tradeUtil;
|
||||||
this.user = user;
|
this.user = user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo we need to pass the intended trade amount
|
||||||
|
void takeBsqSwapOffer(Offer offer,
|
||||||
|
String paymentAccountId,
|
||||||
|
String takerFeeCurrencyCode,
|
||||||
|
TradeResultHandler<BsqSwapTrade> 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,
|
void takeOffer(Offer offer,
|
||||||
String paymentAccountId,
|
String paymentAccountId,
|
||||||
String takerFeeCurrencyCode,
|
String takerFeeCurrencyCode,
|
||||||
|
@ -100,7 +127,7 @@ class CoreTradesService {
|
||||||
throw new IllegalArgumentException(format("payment account with id '%s' not found", paymentAccountId));
|
throw new IllegalArgumentException(format("payment account with id '%s' not found", paymentAccountId));
|
||||||
|
|
||||||
var useSavingsWallet = true;
|
var useSavingsWallet = true;
|
||||||
//noinspection ConstantConditions
|
|
||||||
takeOfferModel.initModel(offer, paymentAccount, useSavingsWallet);
|
takeOfferModel.initModel(offer, paymentAccount, useSavingsWallet);
|
||||||
log.info("Initiating take {} offer, {}",
|
log.info("Initiating take {} offer, {}",
|
||||||
offer.isBuyOffer() ? "buy" : "sell",
|
offer.isBuyOffer() ? "buy" : "sell",
|
||||||
|
@ -205,6 +232,13 @@ class CoreTradesService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BsqSwapTrade getBsqSwapTrade(String tradeId) {
|
||||||
|
coreWalletsService.verifyWalletsAreAvailable();
|
||||||
|
coreWalletsService.verifyEncryptedWalletIsUnlocked();
|
||||||
|
return tradeManager.findBsqSwapTradeById(tradeId).orElseThrow(() ->
|
||||||
|
new IllegalArgumentException(format("trade with id '%s' not found", tradeId)));
|
||||||
|
}
|
||||||
|
|
||||||
String getTradeRole(String tradeId) {
|
String getTradeRole(String tradeId) {
|
||||||
coreWalletsService.verifyWalletsAreAvailable();
|
coreWalletsService.verifyWalletsAreAvailable();
|
||||||
coreWalletsService.verifyEncryptedWalletIsUnlocked();
|
coreWalletsService.verifyEncryptedWalletIsUnlocked();
|
||||||
|
|
|
@ -82,7 +82,6 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
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.btc.wallet.Restrictions.getMinNonDustOutput;
|
||||||
import static bisq.core.util.ParsingUtils.parseToCoin;
|
import static bisq.core.util.ParsingUtils.parseToCoin;
|
||||||
import static java.lang.String.format;
|
import static java.lang.String.format;
|
||||||
|
@ -583,14 +582,14 @@ class CoreWalletsService {
|
||||||
verifyWalletsAreAvailable();
|
verifyWalletsAreAvailable();
|
||||||
verifyEncryptedWalletIsUnlocked();
|
verifyEncryptedWalletIsUnlocked();
|
||||||
|
|
||||||
var availableConfirmedBalance = bsqWalletService.getAvailableConfirmedBalance();
|
var availableBalance = bsqWalletService.getAvailableBalance();
|
||||||
var unverifiedBalance = bsqWalletService.getUnverifiedBalance();
|
var unverifiedBalance = bsqWalletService.getUnverifiedBalance();
|
||||||
var unconfirmedChangeBalance = bsqWalletService.getUnconfirmedChangeBalance();
|
var unconfirmedChangeBalance = bsqWalletService.getUnconfirmedChangeBalance();
|
||||||
var lockedForVotingBalance = bsqWalletService.getLockedForVotingBalance();
|
var lockedForVotingBalance = bsqWalletService.getLockedForVotingBalance();
|
||||||
var lockupBondsBalance = bsqWalletService.getLockupBondsBalance();
|
var lockupBondsBalance = bsqWalletService.getLockupBondsBalance();
|
||||||
var unlockingBondsBalance = bsqWalletService.getUnlockingBondsBalance();
|
var unlockingBondsBalance = bsqWalletService.getUnlockingBondsBalance();
|
||||||
|
|
||||||
return new BsqBalanceInfo(availableConfirmedBalance.value,
|
return new BsqBalanceInfo(availableBalance.value,
|
||||||
unverifiedBalance.value,
|
unverifiedBalance.value,
|
||||||
unconfirmedChangeBalance.value,
|
unconfirmedChangeBalance.value,
|
||||||
lockedForVotingBalance.value,
|
lockedForVotingBalance.value,
|
||||||
|
|
|
@ -17,20 +17,20 @@ public class BsqBalanceInfo implements Payload {
|
||||||
-1);
|
-1);
|
||||||
|
|
||||||
// All balances are in BSQ satoshis.
|
// All balances are in BSQ satoshis.
|
||||||
private final long availableConfirmedBalance;
|
private final long availableBalance;
|
||||||
private final long unverifiedBalance;
|
private final long unverifiedBalance;
|
||||||
private final long unconfirmedChangeBalance;
|
private final long unconfirmedChangeBalance;
|
||||||
private final long lockedForVotingBalance;
|
private final long lockedForVotingBalance;
|
||||||
private final long lockupBondsBalance;
|
private final long lockupBondsBalance;
|
||||||
private final long unlockingBondsBalance;
|
private final long unlockingBondsBalance;
|
||||||
|
|
||||||
public BsqBalanceInfo(long availableConfirmedBalance,
|
public BsqBalanceInfo(long availableBalance,
|
||||||
long unverifiedBalance,
|
long unverifiedBalance,
|
||||||
long unconfirmedChangeBalance,
|
long unconfirmedChangeBalance,
|
||||||
long lockedForVotingBalance,
|
long lockedForVotingBalance,
|
||||||
long lockupBondsBalance,
|
long lockupBondsBalance,
|
||||||
long unlockingBondsBalance) {
|
long unlockingBondsBalance) {
|
||||||
this.availableConfirmedBalance = availableConfirmedBalance;
|
this.availableBalance = availableBalance;
|
||||||
this.unverifiedBalance = unverifiedBalance;
|
this.unverifiedBalance = unverifiedBalance;
|
||||||
this.unconfirmedChangeBalance = unconfirmedChangeBalance;
|
this.unconfirmedChangeBalance = unconfirmedChangeBalance;
|
||||||
this.lockedForVotingBalance = lockedForVotingBalance;
|
this.lockedForVotingBalance = lockedForVotingBalance;
|
||||||
|
@ -39,14 +39,14 @@ public class BsqBalanceInfo implements Payload {
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public static BsqBalanceInfo valueOf(long availableConfirmedBalance,
|
public static BsqBalanceInfo valueOf(long availableBalance,
|
||||||
long unverifiedBalance,
|
long unverifiedBalance,
|
||||||
long unconfirmedChangeBalance,
|
long unconfirmedChangeBalance,
|
||||||
long lockedForVotingBalance,
|
long lockedForVotingBalance,
|
||||||
long lockupBondsBalance,
|
long lockupBondsBalance,
|
||||||
long unlockingBondsBalance) {
|
long unlockingBondsBalance) {
|
||||||
// Convenience for creating a model instance instead of a proto.
|
// Convenience for creating a model instance instead of a proto.
|
||||||
return new BsqBalanceInfo(availableConfirmedBalance,
|
return new BsqBalanceInfo(availableBalance,
|
||||||
unverifiedBalance,
|
unverifiedBalance,
|
||||||
unconfirmedChangeBalance,
|
unconfirmedChangeBalance,
|
||||||
lockedForVotingBalance,
|
lockedForVotingBalance,
|
||||||
|
@ -58,10 +58,11 @@ public class BsqBalanceInfo implements Payload {
|
||||||
// PROTO BUFFER
|
// PROTO BUFFER
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// TODO rename availableConfirmedBalance in proto if possible
|
||||||
@Override
|
@Override
|
||||||
public bisq.proto.grpc.BsqBalanceInfo toProtoMessage() {
|
public bisq.proto.grpc.BsqBalanceInfo toProtoMessage() {
|
||||||
return bisq.proto.grpc.BsqBalanceInfo.newBuilder()
|
return bisq.proto.grpc.BsqBalanceInfo.newBuilder()
|
||||||
.setAvailableConfirmedBalance(availableConfirmedBalance)
|
.setAvailableConfirmedBalance(availableBalance)
|
||||||
.setUnverifiedBalance(unverifiedBalance)
|
.setUnverifiedBalance(unverifiedBalance)
|
||||||
.setUnconfirmedChangeBalance(unconfirmedChangeBalance)
|
.setUnconfirmedChangeBalance(unconfirmedChangeBalance)
|
||||||
.setLockedForVotingBalance(lockedForVotingBalance)
|
.setLockedForVotingBalance(lockedForVotingBalance)
|
||||||
|
@ -83,7 +84,7 @@ public class BsqBalanceInfo implements Payload {
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "BsqBalanceInfo{" +
|
return "BsqBalanceInfo{" +
|
||||||
"availableConfirmedBalance=" + availableConfirmedBalance +
|
"availableBalance=" + availableBalance +
|
||||||
", unverifiedBalance=" + unverifiedBalance +
|
", unverifiedBalance=" + unverifiedBalance +
|
||||||
", unconfirmedChangeBalance=" + unconfirmedChangeBalance +
|
", unconfirmedChangeBalance=" + unconfirmedChangeBalance +
|
||||||
", lockedForVotingBalance=" + lockedForVotingBalance +
|
", lockedForVotingBalance=" + lockedForVotingBalance +
|
||||||
|
|
227
core/src/main/java/bisq/core/api/model/BsqSwapOfferInfo.java
Normal file
227
core/src/main/java/bisq/core/api/model/BsqSwapOfferInfo.java
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
323
core/src/main/java/bisq/core/api/model/BsqSwapTradeInfo.java
Normal file
323
core/src/main/java/bisq/core/api/model/BsqSwapTradeInfo.java
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -144,8 +144,8 @@ public class OfferInfo implements Payload {
|
||||||
.withPaymentAccountId(offer.getMakerPaymentAccountId())
|
.withPaymentAccountId(offer.getMakerPaymentAccountId())
|
||||||
.withPaymentMethodId(offer.getPaymentMethod().getId())
|
.withPaymentMethodId(offer.getPaymentMethod().getId())
|
||||||
.withPaymentMethodShortName(offer.getPaymentMethod().getShortName())
|
.withPaymentMethodShortName(offer.getPaymentMethod().getShortName())
|
||||||
.withBaseCurrencyCode(offer.getOfferPayload().getBaseCurrencyCode())
|
.withBaseCurrencyCode(offer.getBaseCurrencyCode())
|
||||||
.withCounterCurrencyCode(offer.getOfferPayload().getCounterCurrencyCode())
|
.withCounterCurrencyCode(offer.getCounterCurrencyCode())
|
||||||
.withDate(offer.getDate().getTime())
|
.withDate(offer.getDate().getTime())
|
||||||
.withState(offer.getState().name())
|
.withState(offer.getState().name())
|
||||||
.withIsMyOffer(isMyOffer);
|
.withIsMyOffer(isMyOffer);
|
||||||
|
|
|
@ -17,8 +17,8 @@
|
||||||
|
|
||||||
package bisq.core.api.model;
|
package bisq.core.api.model;
|
||||||
|
|
||||||
import bisq.core.trade.Contract;
|
import bisq.core.trade.model.bisq_v1.Contract;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.model.bisq_v1.Trade;
|
||||||
|
|
||||||
import bisq.common.Payload;
|
import bisq.common.Payload;
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ import static bisq.core.api.model.PaymentAccountPayloadInfo.toPaymentAccountPayl
|
||||||
@Getter
|
@Getter
|
||||||
public class TradeInfo implements Payload {
|
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
|
// lighter weight TradeInfo proto wrapper instead, containing just enough fields to
|
||||||
// view and interact with trades.
|
// view and interact with trades.
|
||||||
|
|
||||||
|
@ -127,19 +127,19 @@ public class TradeInfo implements Payload {
|
||||||
.withDate(trade.getDate().getTime())
|
.withDate(trade.getDate().getTime())
|
||||||
.withRole(role == null ? "" : role)
|
.withRole(role == null ? "" : role)
|
||||||
.withIsCurrencyForTakerFeeBtc(trade.isCurrencyForTakerFeeBtc())
|
.withIsCurrencyForTakerFeeBtc(trade.isCurrencyForTakerFeeBtc())
|
||||||
.withTxFeeAsLong(trade.getTxFeeAsLong())
|
.withTxFeeAsLong(trade.getTradeTxFeeAsLong())
|
||||||
.withTakerFeeAsLong(trade.getTakerFeeAsLong())
|
.withTakerFeeAsLong(trade.getTakerFeeAsLong())
|
||||||
.withTakerFeeAsLong(trade.getTakerFeeAsLong())
|
.withTakerFeeAsLong(trade.getTakerFeeAsLong())
|
||||||
.withTakerFeeTxId(trade.getTakerFeeTxId())
|
.withTakerFeeTxId(trade.getTakerFeeTxId())
|
||||||
.withDepositTxId(trade.getDepositTxId())
|
.withDepositTxId(trade.getDepositTxId())
|
||||||
.withPayoutTxId(trade.getPayoutTxId())
|
.withPayoutTxId(trade.getPayoutTxId())
|
||||||
.withTradeAmountAsLong(trade.getTradeAmountAsLong())
|
.withTradeAmountAsLong(trade.getAmountAsLong())
|
||||||
.withTradePrice(trade.getTradePrice().getValue())
|
.withTradePrice(trade.getPrice().getValue())
|
||||||
.withTradeVolume(trade.getTradeVolume() == null ? 0 : trade.getTradeVolume().getValue())
|
.withTradeVolume(trade.getVolume() == null ? 0 : trade.getVolume().getValue())
|
||||||
.withTradingPeerNodeAddress(Objects.requireNonNull(
|
.withTradingPeerNodeAddress(Objects.requireNonNull(
|
||||||
trade.getTradingPeerNodeAddress()).getHostNameWithoutPostFix())
|
trade.getTradingPeerNodeAddress()).getHostNameWithoutPostFix())
|
||||||
.withState(trade.getState().name())
|
.withState(trade.getTradeState().name())
|
||||||
.withPhase(trade.getPhase().name())
|
.withPhase(trade.getTradePhase().name())
|
||||||
.withTradePeriodState(trade.getTradePeriodState().name())
|
.withTradePeriodState(trade.getTradePeriodState().name())
|
||||||
.withIsDepositPublished(trade.isDepositPublished())
|
.withIsDepositPublished(trade.isDepositPublished())
|
||||||
.withIsDepositConfirmed(trade.isDepositConfirmed())
|
.withIsDepositConfirmed(trade.isDepositConfirmed())
|
||||||
|
|
|
@ -23,6 +23,7 @@ import bisq.core.btc.wallet.BtcWalletService;
|
||||||
import bisq.core.dao.DaoSetup;
|
import bisq.core.dao.DaoSetup;
|
||||||
import bisq.core.dao.node.full.RpcService;
|
import bisq.core.dao.node.full.RpcService;
|
||||||
import bisq.core.offer.OpenOfferManager;
|
import bisq.core.offer.OpenOfferManager;
|
||||||
|
import bisq.core.offer.bsq_swap.OpenBsqSwapOfferService;
|
||||||
import bisq.core.provider.price.PriceFeedService;
|
import bisq.core.provider.price.PriceFeedService;
|
||||||
import bisq.core.setup.CorePersistedDataHost;
|
import bisq.core.setup.CorePersistedDataHost;
|
||||||
import bisq.core.setup.CoreSetup;
|
import bisq.core.setup.CoreSetup;
|
||||||
|
@ -227,6 +228,7 @@ public abstract class BisqExecutable implements GracefulShutDownHandler, BisqSet
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
injector.getInstance(OpenBsqSwapOfferService.class).shutDown();
|
||||||
injector.getInstance(PriceFeedService.class).shutDown();
|
injector.getInstance(PriceFeedService.class).shutDown();
|
||||||
injector.getInstance(ArbitratorManager.class).shutDown();
|
injector.getInstance(ArbitratorManager.class).shutDown();
|
||||||
injector.getInstance(TradeStatisticsManager.class).shutDown();
|
injector.getInstance(TradeStatisticsManager.class).shutDown();
|
||||||
|
|
|
@ -42,7 +42,7 @@ import bisq.core.support.dispute.arbitration.ArbitrationManager;
|
||||||
import bisq.core.support.dispute.mediation.MediationManager;
|
import bisq.core.support.dispute.mediation.MediationManager;
|
||||||
import bisq.core.support.dispute.refund.RefundManager;
|
import bisq.core.support.dispute.refund.RefundManager;
|
||||||
import bisq.core.trade.TradeManager;
|
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.Preferences;
|
||||||
import bisq.core.user.User;
|
import bisq.core.user.User;
|
||||||
import bisq.core.util.FormattingUtils;
|
import bisq.core.util.FormattingUtils;
|
||||||
|
@ -525,6 +525,9 @@ public class BisqSetup {
|
||||||
// miner fee was too low and the transaction got removed from mempool and got out from our wallet after a
|
// miner fee was too low and the transaction got removed from mempool and got out from our wallet after a
|
||||||
// resync.
|
// resync.
|
||||||
openOfferManager.getObservableList().forEach(e -> {
|
openOfferManager.getObservableList().forEach(e -> {
|
||||||
|
if (e.getOffer().isBsqSwapOffer()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
String offerFeePaymentTxId = e.getOffer().getOfferFeePaymentTxId();
|
String offerFeePaymentTxId = e.getOffer().getOfferFeePaymentTxId();
|
||||||
if (btcWalletService.getConfidenceForTxId(offerFeePaymentTxId) == null) {
|
if (btcWalletService.getConfidenceForTxId(offerFeePaymentTxId) == null) {
|
||||||
String message = Res.get("popup.warning.openOfferWithInvalidMakerFeeTx",
|
String message = Res.get("popup.warning.openOfferWithInvalidMakerFeeTx",
|
||||||
|
@ -657,7 +660,10 @@ public class BisqSetup {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void maybeShowLocalhostRunningInfo() {
|
private void maybeShowLocalhostRunningInfo() {
|
||||||
maybeTriggerDisplayHandler("bitcoinLocalhostNode", displayLocalhostHandler, localBitcoinNode.shouldBeUsed());
|
if (Config.baseCurrencyNetwork().isMainnet()) {
|
||||||
|
maybeTriggerDisplayHandler("bitcoinLocalhostNode", displayLocalhostHandler,
|
||||||
|
localBitcoinNode.shouldBeUsed());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void maybeShowAccountSigningStateInfo() {
|
private void maybeShowAccountSigningStateInfo() {
|
||||||
|
|
|
@ -34,7 +34,8 @@ import bisq.core.notifications.alerts.TradeEvents;
|
||||||
import bisq.core.notifications.alerts.market.MarketAlerts;
|
import bisq.core.notifications.alerts.market.MarketAlerts;
|
||||||
import bisq.core.notifications.alerts.price.PriceAlert;
|
import bisq.core.notifications.alerts.price.PriceAlert;
|
||||||
import bisq.core.offer.OpenOfferManager;
|
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.AmazonGiftCardAccount;
|
||||||
import bisq.core.payment.RevolutAccount;
|
import bisq.core.payment.RevolutAccount;
|
||||||
import bisq.core.payment.TradeLimits;
|
import bisq.core.payment.TradeLimits;
|
||||||
|
@ -48,9 +49,10 @@ import bisq.core.support.dispute.mediation.mediator.MediatorManager;
|
||||||
import bisq.core.support.dispute.refund.RefundManager;
|
import bisq.core.support.dispute.refund.RefundManager;
|
||||||
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
|
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
|
||||||
import bisq.core.support.traderchat.TraderChatManager;
|
import bisq.core.support.traderchat.TraderChatManager;
|
||||||
|
import bisq.core.trade.ClosedTradableManager;
|
||||||
import bisq.core.trade.TradeManager;
|
import bisq.core.trade.TradeManager;
|
||||||
import bisq.core.trade.closed.ClosedTradableManager;
|
import bisq.core.trade.bisq_v1.FailedTradesManager;
|
||||||
import bisq.core.trade.failed.FailedTradesManager;
|
import bisq.core.trade.bsq_swap.BsqSwapTradeManager;
|
||||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||||
import bisq.core.trade.txproof.xmr.XmrTxProofService;
|
import bisq.core.trade.txproof.xmr.XmrTxProofService;
|
||||||
import bisq.core.user.User;
|
import bisq.core.user.User;
|
||||||
|
@ -84,6 +86,7 @@ public class DomainInitialisation {
|
||||||
private final TraderChatManager traderChatManager;
|
private final TraderChatManager traderChatManager;
|
||||||
private final TradeManager tradeManager;
|
private final TradeManager tradeManager;
|
||||||
private final ClosedTradableManager closedTradableManager;
|
private final ClosedTradableManager closedTradableManager;
|
||||||
|
private final BsqSwapTradeManager bsqSwapTradeManager;
|
||||||
private final FailedTradesManager failedTradesManager;
|
private final FailedTradesManager failedTradesManager;
|
||||||
private final XmrTxProofService xmrTxProofService;
|
private final XmrTxProofService xmrTxProofService;
|
||||||
private final OpenOfferManager openOfferManager;
|
private final OpenOfferManager openOfferManager;
|
||||||
|
@ -112,6 +115,7 @@ public class DomainInitialisation {
|
||||||
private final DaoStateSnapshotService daoStateSnapshotService;
|
private final DaoStateSnapshotService daoStateSnapshotService;
|
||||||
private final TriggerPriceService triggerPriceService;
|
private final TriggerPriceService triggerPriceService;
|
||||||
private final MempoolService mempoolService;
|
private final MempoolService mempoolService;
|
||||||
|
private final OpenBsqSwapOfferService openBsqSwapOfferService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public DomainInitialisation(ClockWatcher clockWatcher,
|
public DomainInitialisation(ClockWatcher clockWatcher,
|
||||||
|
@ -122,6 +126,7 @@ public class DomainInitialisation {
|
||||||
TraderChatManager traderChatManager,
|
TraderChatManager traderChatManager,
|
||||||
TradeManager tradeManager,
|
TradeManager tradeManager,
|
||||||
ClosedTradableManager closedTradableManager,
|
ClosedTradableManager closedTradableManager,
|
||||||
|
BsqSwapTradeManager bsqSwapTradeManager,
|
||||||
FailedTradesManager failedTradesManager,
|
FailedTradesManager failedTradesManager,
|
||||||
XmrTxProofService xmrTxProofService,
|
XmrTxProofService xmrTxProofService,
|
||||||
OpenOfferManager openOfferManager,
|
OpenOfferManager openOfferManager,
|
||||||
|
@ -149,7 +154,8 @@ public class DomainInitialisation {
|
||||||
User user,
|
User user,
|
||||||
DaoStateSnapshotService daoStateSnapshotService,
|
DaoStateSnapshotService daoStateSnapshotService,
|
||||||
TriggerPriceService triggerPriceService,
|
TriggerPriceService triggerPriceService,
|
||||||
MempoolService mempoolService) {
|
MempoolService mempoolService,
|
||||||
|
OpenBsqSwapOfferService openBsqSwapOfferService) {
|
||||||
this.clockWatcher = clockWatcher;
|
this.clockWatcher = clockWatcher;
|
||||||
this.tradeLimits = tradeLimits;
|
this.tradeLimits = tradeLimits;
|
||||||
this.arbitrationManager = arbitrationManager;
|
this.arbitrationManager = arbitrationManager;
|
||||||
|
@ -158,6 +164,7 @@ public class DomainInitialisation {
|
||||||
this.traderChatManager = traderChatManager;
|
this.traderChatManager = traderChatManager;
|
||||||
this.tradeManager = tradeManager;
|
this.tradeManager = tradeManager;
|
||||||
this.closedTradableManager = closedTradableManager;
|
this.closedTradableManager = closedTradableManager;
|
||||||
|
this.bsqSwapTradeManager = bsqSwapTradeManager;
|
||||||
this.failedTradesManager = failedTradesManager;
|
this.failedTradesManager = failedTradesManager;
|
||||||
this.xmrTxProofService = xmrTxProofService;
|
this.xmrTxProofService = xmrTxProofService;
|
||||||
this.openOfferManager = openOfferManager;
|
this.openOfferManager = openOfferManager;
|
||||||
|
@ -186,6 +193,7 @@ public class DomainInitialisation {
|
||||||
this.daoStateSnapshotService = daoStateSnapshotService;
|
this.daoStateSnapshotService = daoStateSnapshotService;
|
||||||
this.triggerPriceService = triggerPriceService;
|
this.triggerPriceService = triggerPriceService;
|
||||||
this.mempoolService = mempoolService;
|
this.mempoolService = mempoolService;
|
||||||
|
this.openBsqSwapOfferService = openBsqSwapOfferService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initDomainServices(Consumer<String> rejectedTxErrorMessageHandler,
|
public void initDomainServices(Consumer<String> rejectedTxErrorMessageHandler,
|
||||||
|
@ -210,10 +218,12 @@ public class DomainInitialisation {
|
||||||
traderChatManager.onAllServicesInitialized();
|
traderChatManager.onAllServicesInitialized();
|
||||||
|
|
||||||
closedTradableManager.onAllServicesInitialized();
|
closedTradableManager.onAllServicesInitialized();
|
||||||
|
bsqSwapTradeManager.onAllServicesInitialized();
|
||||||
failedTradesManager.onAllServicesInitialized();
|
failedTradesManager.onAllServicesInitialized();
|
||||||
xmrTxProofService.onAllServicesInitialized();
|
xmrTxProofService.onAllServicesInitialized();
|
||||||
|
|
||||||
openOfferManager.onAllServicesInitialized();
|
openOfferManager.onAllServicesInitialized();
|
||||||
|
openBsqSwapOfferService.onAllServicesInitialized();
|
||||||
|
|
||||||
balances.onAllServicesInitialized();
|
balances.onAllServicesInitialized();
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
package bisq.core.app;
|
package bisq.core.app;
|
||||||
|
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
import bisq.core.btc.setup.WalletsSetup;
|
||||||
|
import bisq.core.filter.Filter;
|
||||||
|
import bisq.core.filter.FilterManager;
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
import bisq.core.provider.price.PriceFeedService;
|
import bisq.core.provider.price.PriceFeedService;
|
||||||
import bisq.core.user.Preferences;
|
import bisq.core.user.Preferences;
|
||||||
|
@ -27,6 +29,7 @@ import bisq.network.p2p.P2PServiceListener;
|
||||||
import bisq.network.p2p.network.CloseConnectionReason;
|
import bisq.network.p2p.network.CloseConnectionReason;
|
||||||
import bisq.network.p2p.network.Connection;
|
import bisq.network.p2p.network.Connection;
|
||||||
import bisq.network.p2p.network.ConnectionListener;
|
import bisq.network.p2p.network.ConnectionListener;
|
||||||
|
import bisq.network.p2p.storage.payload.ProofOfWorkPayload;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
@ -71,25 +74,31 @@ public class P2PNetworkSetup {
|
||||||
final BooleanProperty updatedDataReceived = new SimpleBooleanProperty();
|
final BooleanProperty updatedDataReceived = new SimpleBooleanProperty();
|
||||||
@Getter
|
@Getter
|
||||||
final BooleanProperty p2pNetworkFailed = new SimpleBooleanProperty();
|
final BooleanProperty p2pNetworkFailed = new SimpleBooleanProperty();
|
||||||
|
final FilterManager filterManager;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public P2PNetworkSetup(PriceFeedService priceFeedService,
|
public P2PNetworkSetup(PriceFeedService priceFeedService,
|
||||||
P2PService p2PService,
|
P2PService p2PService,
|
||||||
WalletsSetup walletsSetup,
|
WalletsSetup walletsSetup,
|
||||||
Preferences preferences) {
|
Preferences preferences,
|
||||||
|
FilterManager filterManager) {
|
||||||
|
|
||||||
this.priceFeedService = priceFeedService;
|
this.priceFeedService = priceFeedService;
|
||||||
this.p2PService = p2PService;
|
this.p2PService = p2PService;
|
||||||
this.walletsSetup = walletsSetup;
|
this.walletsSetup = walletsSetup;
|
||||||
this.preferences = preferences;
|
this.preferences = preferences;
|
||||||
|
this.filterManager = filterManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
BooleanProperty init(Runnable initWalletServiceHandler, @Nullable Consumer<Boolean> displayTorNetworkSettingsHandler) {
|
BooleanProperty init(Runnable initWalletServiceHandler,
|
||||||
|
@Nullable Consumer<Boolean> displayTorNetworkSettingsHandler) {
|
||||||
StringProperty bootstrapState = new SimpleStringProperty();
|
StringProperty bootstrapState = new SimpleStringProperty();
|
||||||
StringProperty bootstrapWarning = new SimpleStringProperty();
|
StringProperty bootstrapWarning = new SimpleStringProperty();
|
||||||
BooleanProperty hiddenServicePublished = new SimpleBooleanProperty();
|
BooleanProperty hiddenServicePublished = new SimpleBooleanProperty();
|
||||||
BooleanProperty initialP2PNetworkDataReceived = new SimpleBooleanProperty();
|
BooleanProperty initialP2PNetworkDataReceived = new SimpleBooleanProperty();
|
||||||
|
|
||||||
|
addP2PMessageFilter();
|
||||||
|
|
||||||
p2PNetworkInfoBinding = EasyBind.combine(bootstrapState, bootstrapWarning, p2PService.getNumConnectedPeers(),
|
p2PNetworkInfoBinding = EasyBind.combine(bootstrapState, bootstrapWarning, p2PService.getNumConnectedPeers(),
|
||||||
walletsSetup.numPeersProperty(), hiddenServicePublished, initialP2PNetworkDataReceived,
|
walletsSetup.numPeersProperty(), hiddenServicePublished, initialP2PNetworkDataReceived,
|
||||||
(state, warning, numP2pPeers, numBtcPeers, hiddenService, dataReceived) -> {
|
(state, warning, numP2pPeers, numBtcPeers, hiddenService, dataReceived) -> {
|
||||||
|
@ -225,4 +234,13 @@ public class P2PNetworkSetup {
|
||||||
public void setSplashP2PNetworkAnimationVisible(boolean value) {
|
public void setSplashP2PNetworkAnimationVisible(boolean value) {
|
||||||
splashP2PNetworkAnimationVisible.set(value);
|
splashP2PNetworkAnimationVisible.set(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addP2PMessageFilter() {
|
||||||
|
p2PService.getP2PDataStorage().setFilterPredicate(payload -> {
|
||||||
|
Filter filter = filterManager.getFilter();
|
||||||
|
return filter == null ||
|
||||||
|
!filter.isDisablePowMessage() ||
|
||||||
|
!(payload instanceof ProofOfWorkPayload);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import bisq.core.btc.wallet.BtcWalletService;
|
||||||
import bisq.core.dao.DaoSetup;
|
import bisq.core.dao.DaoSetup;
|
||||||
import bisq.core.dao.node.full.RpcService;
|
import bisq.core.dao.node.full.RpcService;
|
||||||
import bisq.core.offer.OpenOfferManager;
|
import bisq.core.offer.OpenOfferManager;
|
||||||
|
import bisq.core.offer.bsq_swap.OpenBsqSwapOfferService;
|
||||||
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
||||||
|
|
||||||
import bisq.network.p2p.NodeAddress;
|
import bisq.network.p2p.NodeAddress;
|
||||||
|
@ -87,6 +88,7 @@ public abstract class ExecutableForAppWithP2p extends BisqExecutable {
|
||||||
try {
|
try {
|
||||||
if (injector != null) {
|
if (injector != null) {
|
||||||
JsonFileManager.shutDownAllInstances();
|
JsonFileManager.shutDownAllInstances();
|
||||||
|
injector.getInstance(OpenBsqSwapOfferService.class).shutDown();
|
||||||
injector.getInstance(RpcService.class).shutDown();
|
injector.getInstance(RpcService.class).shutDown();
|
||||||
injector.getInstance(DaoSetup.class).shutDown();
|
injector.getInstance(DaoSetup.class).shutDown();
|
||||||
injector.getInstance(ArbitratorManager.class).shutDown();
|
injector.getInstance(ArbitratorManager.class).shutDown();
|
||||||
|
|
|
@ -24,10 +24,10 @@ import bisq.core.offer.OpenOffer;
|
||||||
import bisq.core.offer.OpenOfferManager;
|
import bisq.core.offer.OpenOfferManager;
|
||||||
import bisq.core.support.dispute.Dispute;
|
import bisq.core.support.dispute.Dispute;
|
||||||
import bisq.core.support.dispute.refund.RefundManager;
|
import bisq.core.support.dispute.refund.RefundManager;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.ClosedTradableManager;
|
||||||
import bisq.core.trade.TradeManager;
|
import bisq.core.trade.TradeManager;
|
||||||
import bisq.core.trade.closed.ClosedTradableManager;
|
import bisq.core.trade.bisq_v1.FailedTradesManager;
|
||||||
import bisq.core.trade.failed.FailedTradesManager;
|
import bisq.core.trade.model.bisq_v1.Trade;
|
||||||
|
|
||||||
import bisq.common.UserThread;
|
import bisq.common.UserThread;
|
||||||
|
|
||||||
|
|
|
@ -55,11 +55,11 @@ public class TxFeeEstimationService {
|
||||||
// segwit deposit tx with change vsize = 263
|
// segwit deposit tx with change vsize = 263
|
||||||
// segwit payout tx vsize = 169
|
// segwit payout tx vsize = 169
|
||||||
// segwit delayed payout tx vsize = 139
|
// segwit delayed payout tx vsize = 139
|
||||||
public static int TYPICAL_TX_WITH_1_INPUT_VSIZE = 175;
|
public static final int TYPICAL_TX_WITH_1_INPUT_VSIZE = 175;
|
||||||
private static int DEPOSIT_TX_VSIZE = 233;
|
private static final int DEPOSIT_TX_VSIZE = 233;
|
||||||
|
|
||||||
private static int BSQ_INPUT_INCREASE = 70;
|
private static final int BSQ_INPUT_INCREASE = 70;
|
||||||
private static int MAX_ITERATIONS = 10;
|
private static final int MAX_ITERATIONS = 10;
|
||||||
|
|
||||||
private final FeeService feeService;
|
private final FeeService feeService;
|
||||||
private final BtcWalletService btcWalletService;
|
private final BtcWalletService btcWalletService;
|
||||||
|
|
|
@ -20,7 +20,7 @@ package bisq.core.btc.listeners;
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.Coin;
|
||||||
|
|
||||||
public interface BsqBalanceListener {
|
public interface BsqBalanceListener {
|
||||||
void onUpdateBalances(Coin availableConfirmedBalance,
|
void onUpdateBalances(Coin availableBalance,
|
||||||
Coin availableNonBsqBalance,
|
Coin availableNonBsqBalance,
|
||||||
Coin unverifiedBalance,
|
Coin unverifiedBalance,
|
||||||
Coin unconfirmedChangeBalance,
|
Coin unconfirmedChangeBalance,
|
||||||
|
|
|
@ -19,18 +19,15 @@ package bisq.core.btc.listeners;
|
||||||
|
|
||||||
import org.bitcoinj.core.TransactionConfidence;
|
import org.bitcoinj.core.TransactionConfidence;
|
||||||
|
|
||||||
public class TxConfidenceListener {
|
import lombok.Getter;
|
||||||
private final String txID;
|
|
||||||
|
|
||||||
public TxConfidenceListener(String txID) {
|
public abstract class TxConfidenceListener {
|
||||||
this.txID = txID;
|
@Getter
|
||||||
|
private final String txId;
|
||||||
|
|
||||||
|
public TxConfidenceListener(String txId) {
|
||||||
|
this.txId = txId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTxID() {
|
abstract public void onTransactionConfidenceChanged(TransactionConfidence confidence);
|
||||||
return txID;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("UnusedParameters")
|
|
||||||
public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,33 +17,82 @@
|
||||||
|
|
||||||
package bisq.core.btc.model;
|
package bisq.core.btc.model;
|
||||||
|
|
||||||
|
import bisq.core.btc.wallet.BtcWalletService;
|
||||||
|
|
||||||
import bisq.common.proto.network.NetworkPayload;
|
import bisq.common.proto.network.NetworkPayload;
|
||||||
import bisq.common.proto.persistable.PersistablePayload;
|
import bisq.common.proto.persistable.PersistablePayload;
|
||||||
import bisq.common.util.Utilities;
|
import bisq.common.util.Utilities;
|
||||||
|
|
||||||
import com.google.protobuf.ByteString;
|
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.EqualsAndHashCode;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
@EqualsAndHashCode
|
@EqualsAndHashCode
|
||||||
@Immutable
|
@Immutable
|
||||||
|
@Getter
|
||||||
public final class RawTransactionInput implements NetworkPayload, PersistablePayload {
|
public final class RawTransactionInput implements NetworkPayload, PersistablePayload {
|
||||||
public final long index; // Index of spending txo
|
public final long index; // Index of spending txo
|
||||||
public final byte[] parentTransaction; // Spending tx (fromTx)
|
public final byte[] parentTransaction; // Spending tx (fromTx)
|
||||||
public final long value;
|
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.
|
* Holds the relevant data for the connected output for a tx input.
|
||||||
* @param index the index of the parentTransaction
|
* @param index the index of the parentTransaction
|
||||||
* @param parentTransaction the spending output tx, not the parent tx of the input
|
* @param parentTransaction the spending output tx, not the parent tx of the input
|
||||||
* @param value the number of satoshis being spent
|
* @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.index = index;
|
||||||
this.parentTransaction = parentTransaction;
|
this.parentTransaction = parentTransaction;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
|
this.scriptTypeId = scriptTypeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -52,11 +101,36 @@ public final class RawTransactionInput implements NetworkPayload, PersistablePay
|
||||||
.setIndex(index)
|
.setIndex(index)
|
||||||
.setParentTransaction(ByteString.copyFrom(parentTransaction))
|
.setParentTransaction(ByteString.copyFrom(parentTransaction))
|
||||||
.setValue(value)
|
.setValue(value)
|
||||||
|
.setScriptTypeId(scriptTypeId)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RawTransactionInput fromProto(protobuf.RawTransactionInput proto) {
|
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
|
@Override
|
||||||
|
@ -65,6 +139,7 @@ public final class RawTransactionInput implements NetworkPayload, PersistablePay
|
||||||
"index=" + index +
|
"index=" + index +
|
||||||
", parentTransaction as HEX " + Utilities.bytesAsHexString(parentTransaction) +
|
", parentTransaction as HEX " + Utilities.bytesAsHexString(parentTransaction) +
|
||||||
", value=" + value +
|
", value=" + value +
|
||||||
|
", scriptTypeId=" + scriptTypeId +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,9 +64,6 @@ public class BtcNodes {
|
||||||
new BtcNode("btc1.sqrrm.net", "jygcc54etaubgdpcvzgbihjaqbc37cstpvum5sjzvka4bibkp4wrgnqd.onion", "185.25.48.184", BtcNode.DEFAULT_PORT, "@sqrrm"),
|
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"),
|
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
|
// Devin Bileck
|
||||||
new BtcNode("btc1.bisq.services", "devinbtctu7uctl7hly2juu3thbgeivfnvw3ckj3phy6nyvpnx66yeyd.onion", "172.105.21.216", BtcNode.DEFAULT_PORT, "@devinbileck"),
|
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"),
|
new BtcNode("btc2.bisq.services", "devinbtcyk643iruzfpaxw3on2jket7rbjmwygm42dmdyub3ietrbmid.onion", "173.255.240.205", BtcNode.DEFAULT_PORT, "@devinbileck"),
|
||||||
|
@ -77,9 +74,6 @@ public class BtcNodes {
|
||||||
new BtcNode("node140.hnl.wiz.biz", "jto2jfbsxhb6yvhcrrjddrgbakte6tgsy3c3z3prss64gndgvovvosyd.onion", "103.99.168.140", BtcNode.DEFAULT_PORT, "@wiz"),
|
new BtcNode("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("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")
|
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<>();
|
new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,8 @@ import org.bitcoinj.core.TransactionOutput;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,9 +38,13 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
public class BsqCoinSelector extends BisqDefaultCoinSelector {
|
public class BsqCoinSelector extends BisqDefaultCoinSelector {
|
||||||
private final DaoStateService daoStateService;
|
private final DaoStateService daoStateService;
|
||||||
private final UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService;
|
private final UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService;
|
||||||
|
@Setter
|
||||||
|
@Getter
|
||||||
|
private boolean allowSpendMyOwnUnconfirmedTxOutputs = true;
|
||||||
|
|
||||||
@Inject
|
@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.
|
// permitForeignPendingTx is not relevant here as we do not support pending foreign utxos anyway.
|
||||||
super(false);
|
super(false);
|
||||||
this.daoStateService = daoStateService;
|
this.daoStateService = daoStateService;
|
||||||
|
@ -53,17 +59,19 @@ public class BsqCoinSelector extends BisqDefaultCoinSelector {
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// If it is a normal confirmed BSQ output we use the default lookup at the daoState
|
// 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;
|
return true;
|
||||||
|
|
||||||
// It might be that it is an unconfirmed change output which we allow to be used for spending without requiring a confirmation.
|
// 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).
|
// 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;
|
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
|
// 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.
|
// 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.
|
// For BSQ we do not check for dust attack utxos as they are 5.46 BSQ and a considerable value.
|
||||||
|
|
|
@ -42,7 +42,7 @@ public class BsqTransferService {
|
||||||
|
|
||||||
Transaction preparedSendTx = bsqWalletService.getPreparedSendBsqTx(address.toString(), receiverAmount);
|
Transaction preparedSendTx = bsqWalletService.getPreparedSendBsqTx(address.toString(), receiverAmount);
|
||||||
Transaction txWithBtcFee = btcWalletService.completePreparedSendBsqTx(preparedSendTx, txFeePerVbyte);
|
Transaction txWithBtcFee = btcWalletService.completePreparedSendBsqTx(preparedSendTx, txFeePerVbyte);
|
||||||
Transaction signedTx = bsqWalletService.signTx(txWithBtcFee);
|
Transaction signedTx = bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee);
|
||||||
|
|
||||||
return new BsqTransferModel(address,
|
return new BsqTransferModel(address,
|
||||||
receiverAmount,
|
receiverAmount,
|
||||||
|
|
|
@ -22,6 +22,7 @@ import bisq.core.btc.exceptions.InsufficientBsqException;
|
||||||
import bisq.core.btc.exceptions.TransactionVerificationException;
|
import bisq.core.btc.exceptions.TransactionVerificationException;
|
||||||
import bisq.core.btc.exceptions.WalletException;
|
import bisq.core.btc.exceptions.WalletException;
|
||||||
import bisq.core.btc.listeners.BsqBalanceListener;
|
import bisq.core.btc.listeners.BsqBalanceListener;
|
||||||
|
import bisq.core.btc.model.RawTransactionInput;
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
import bisq.core.btc.setup.WalletsSetup;
|
||||||
import bisq.core.dao.DaoKillSwitch;
|
import bisq.core.dao.DaoKillSwitch;
|
||||||
import bisq.core.dao.state.DaoStateListener;
|
import bisq.core.dao.state.DaoStateListener;
|
||||||
|
@ -34,8 +35,10 @@ import bisq.core.dao.state.model.blockchain.TxType;
|
||||||
import bisq.core.dao.state.unconfirmed.UnconfirmedBsqChangeOutputListService;
|
import bisq.core.dao.state.unconfirmed.UnconfirmedBsqChangeOutputListService;
|
||||||
import bisq.core.provider.fee.FeeService;
|
import bisq.core.provider.fee.FeeService;
|
||||||
import bisq.core.user.Preferences;
|
import bisq.core.user.Preferences;
|
||||||
|
import bisq.core.util.coin.BsqFormatter;
|
||||||
|
|
||||||
import bisq.common.UserThread;
|
import bisq.common.UserThread;
|
||||||
|
import bisq.common.util.Tuple2;
|
||||||
|
|
||||||
import org.bitcoinj.core.Address;
|
import org.bitcoinj.core.Address;
|
||||||
import org.bitcoinj.core.AddressFormatException;
|
import org.bitcoinj.core.AddressFormatException;
|
||||||
|
@ -95,15 +98,20 @@ public class BsqWalletService extends WalletService implements DaoStateListener
|
||||||
private final CopyOnWriteArraySet<BsqBalanceListener> bsqBalanceListeners = new CopyOnWriteArraySet<>();
|
private final CopyOnWriteArraySet<BsqBalanceListener> bsqBalanceListeners = new CopyOnWriteArraySet<>();
|
||||||
private final List<WalletTransactionsChangeListener> walletTransactionsChangeListeners = new ArrayList<>();
|
private final List<WalletTransactionsChangeListener> walletTransactionsChangeListeners = new ArrayList<>();
|
||||||
private boolean updateBsqWalletTransactionsPending;
|
private boolean updateBsqWalletTransactionsPending;
|
||||||
|
@Getter
|
||||||
|
private final BsqFormatter bsqFormatter;
|
||||||
|
|
||||||
|
|
||||||
// balance of non BSQ satoshis
|
// balance of non BSQ satoshis
|
||||||
@Getter
|
@Getter
|
||||||
private Coin availableNonBsqBalance = Coin.ZERO;
|
private Coin availableNonBsqBalance = Coin.ZERO;
|
||||||
@Getter
|
@Getter
|
||||||
private Coin availableConfirmedBalance = Coin.ZERO;
|
private Coin availableBalance = Coin.ZERO;
|
||||||
@Getter
|
@Getter
|
||||||
private Coin unverifiedBalance = Coin.ZERO;
|
private Coin unverifiedBalance = Coin.ZERO;
|
||||||
@Getter
|
@Getter
|
||||||
|
private Coin verifiedBalance = Coin.ZERO;
|
||||||
|
@Getter
|
||||||
private Coin unconfirmedChangeBalance = Coin.ZERO;
|
private Coin unconfirmedChangeBalance = Coin.ZERO;
|
||||||
@Getter
|
@Getter
|
||||||
private Coin lockedForVotingBalance = Coin.ZERO;
|
private Coin lockedForVotingBalance = Coin.ZERO;
|
||||||
|
@ -125,7 +133,8 @@ public class BsqWalletService extends WalletService implements DaoStateListener
|
||||||
UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService,
|
UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService,
|
||||||
Preferences preferences,
|
Preferences preferences,
|
||||||
FeeService feeService,
|
FeeService feeService,
|
||||||
DaoKillSwitch daoKillSwitch) {
|
DaoKillSwitch daoKillSwitch,
|
||||||
|
BsqFormatter bsqFormatter) {
|
||||||
super(walletsSetup,
|
super(walletsSetup,
|
||||||
preferences,
|
preferences,
|
||||||
feeService);
|
feeService);
|
||||||
|
@ -135,6 +144,7 @@ public class BsqWalletService extends WalletService implements DaoStateListener
|
||||||
this.daoStateService = daoStateService;
|
this.daoStateService = daoStateService;
|
||||||
this.unconfirmedBsqChangeOutputListService = unconfirmedBsqChangeOutputListService;
|
this.unconfirmedBsqChangeOutputListService = unconfirmedBsqChangeOutputListService;
|
||||||
this.daoKillSwitch = daoKillSwitch;
|
this.daoKillSwitch = daoKillSwitch;
|
||||||
|
this.bsqFormatter = bsqFormatter;
|
||||||
|
|
||||||
nonBsqCoinSelector.setPreferences(preferences);
|
nonBsqCoinSelector.setPreferences(preferences);
|
||||||
|
|
||||||
|
@ -284,18 +294,20 @@ public class BsqWalletService extends WalletService implements DaoStateListener
|
||||||
.mapToLong(TxOutput::getValue)
|
.mapToLong(TxOutput::getValue)
|
||||||
.sum());
|
.sum());
|
||||||
|
|
||||||
availableConfirmedBalance = bsqCoinSelector.select(NetworkParameters.MAX_MONEY,
|
availableBalance = bsqCoinSelector.select(NetworkParameters.MAX_MONEY,
|
||||||
wallet.calculateAllSpendCandidates()).valueGathered;
|
wallet.calculateAllSpendCandidates()).valueGathered;
|
||||||
|
|
||||||
if (availableConfirmedBalance.isNegative())
|
if (availableBalance.isNegative())
|
||||||
availableConfirmedBalance = Coin.ZERO;
|
availableBalance = Coin.ZERO;
|
||||||
|
|
||||||
unconfirmedChangeBalance = unconfirmedBsqChangeOutputListService.getBalance();
|
unconfirmedChangeBalance = unconfirmedBsqChangeOutputListService.getBalance();
|
||||||
|
|
||||||
availableNonBsqBalance = nonBsqCoinSelector.select(NetworkParameters.MAX_MONEY,
|
availableNonBsqBalance = nonBsqCoinSelector.select(NetworkParameters.MAX_MONEY,
|
||||||
wallet.calculateAllSpendCandidates()).valueGathered;
|
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));
|
unconfirmedChangeBalance, lockedForVotingBalance, lockupBondsBalance, unlockingBondsBalance));
|
||||||
log.info("updateBsqBalance took {} ms", System.currentTimeMillis() - ts);
|
log.info("updateBsqBalance took {} ms", System.currentTimeMillis() - ts);
|
||||||
}
|
}
|
||||||
|
@ -481,28 +493,10 @@ public class BsqWalletService extends WalletService implements DaoStateListener
|
||||||
// Sign tx
|
// Sign tx
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public Transaction signTx(Transaction tx) throws WalletException, TransactionVerificationException {
|
public Transaction signTxAndVerifyNoDustOutputs(Transaction tx)
|
||||||
for (int i = 0; i < tx.getInputs().size(); i++) {
|
throws WalletException, TransactionVerificationException {
|
||||||
TransactionInput txIn = tx.getInputs().get(i);
|
WalletService.signTx(wallet, aesKey, tx);
|
||||||
TransactionOutput connectedOutput = txIn.getConnectedOutput();
|
WalletService.verifyNonDustTxo(tx);
|
||||||
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);
|
|
||||||
return tx;
|
return tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -540,6 +534,7 @@ public class BsqWalletService extends WalletService implements DaoStateListener
|
||||||
return getPreparedSendTx(receiverAddress, receiverAmount, bsqCoinSelector);
|
return getPreparedSendTx(receiverAddress, receiverAmount, bsqCoinSelector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Send BTC (non-BSQ) with BTC fee (e.g. the issuance output from a lost comp. request)
|
// Send BTC (non-BSQ) with BTC fee (e.g. the issuance output from a lost comp. request)
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -732,6 +727,46 @@ public class BsqWalletService extends WalletService implements DaoStateListener
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// BsqSwap tx
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public Tuple2<List<RawTransactionInput>, 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<RawTransactionInput> inputs = dummyTx.getInputs().stream()
|
||||||
|
.map(RawTransactionInput::new)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return new Tuple2<>(inputs, change);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void signBsqSwapTransaction(Transaction transaction, List<TransactionInput> 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
|
// Blind vote tx
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -781,6 +816,7 @@ public class BsqWalletService extends WalletService implements DaoStateListener
|
||||||
return tx;
|
return tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Unlock bond tx
|
// Unlock bond tx
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -23,6 +23,7 @@ import bisq.core.btc.exceptions.TransactionVerificationException;
|
||||||
import bisq.core.btc.exceptions.WalletException;
|
import bisq.core.btc.exceptions.WalletException;
|
||||||
import bisq.core.btc.model.AddressEntry;
|
import bisq.core.btc.model.AddressEntry;
|
||||||
import bisq.core.btc.model.AddressEntryList;
|
import bisq.core.btc.model.AddressEntryList;
|
||||||
|
import bisq.core.btc.model.RawTransactionInput;
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
import bisq.core.btc.setup.WalletsSetup;
|
||||||
import bisq.core.btc.wallet.http.MemPoolSpaceTxBroadcaster;
|
import bisq.core.btc.wallet.http.MemPoolSpaceTxBroadcaster;
|
||||||
import bisq.core.provider.fee.FeeService;
|
import bisq.core.provider.fee.FeeService;
|
||||||
|
@ -47,6 +48,7 @@ import org.bitcoinj.crypto.KeyCrypterScrypt;
|
||||||
import org.bitcoinj.script.Script;
|
import org.bitcoinj.script.Script;
|
||||||
import org.bitcoinj.script.ScriptBuilder;
|
import org.bitcoinj.script.ScriptBuilder;
|
||||||
import org.bitcoinj.script.ScriptPattern;
|
import org.bitcoinj.script.ScriptPattern;
|
||||||
|
import org.bitcoinj.wallet.CoinSelection;
|
||||||
import org.bitcoinj.wallet.SendRequest;
|
import org.bitcoinj.wallet.SendRequest;
|
||||||
import org.bitcoinj.wallet.Wallet;
|
import org.bitcoinj.wallet.Wallet;
|
||||||
|
|
||||||
|
@ -61,6 +63,7 @@ import org.bouncycastle.crypto.params.KeyParameter;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -1330,4 +1333,30 @@ public class BtcWalletService extends WalletService {
|
||||||
|
|
||||||
return resultTx;
|
return resultTx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Find inputs and change
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public Tuple2<List<RawTransactionInput>, 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<RawTransactionInput> inputs = dummyTx.getInputs().stream()
|
||||||
|
.map(RawTransactionInput::new)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return new Tuple2<>(inputs, change);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,13 +63,12 @@ import org.bouncycastle.crypto.params.KeyParameter;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
@ -891,8 +890,7 @@ public class TradeWalletService {
|
||||||
input.setScriptSig(inputScript);
|
input.setScriptSig(inputScript);
|
||||||
} else {
|
} else {
|
||||||
input.setScriptSig(ScriptBuilder.createEmpty());
|
input.setScriptSig(ScriptBuilder.createEmpty());
|
||||||
TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig);
|
input.setWitness(TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig));
|
||||||
input.setWitness(witness);
|
|
||||||
}
|
}
|
||||||
WalletService.printTx("payoutTx", payoutTx);
|
WalletService.printTx("payoutTx", payoutTx);
|
||||||
WalletService.verifyTransaction(payoutTx);
|
WalletService.verifyTransaction(payoutTx);
|
||||||
|
@ -971,8 +969,7 @@ public class TradeWalletService {
|
||||||
input.setScriptSig(inputScript);
|
input.setScriptSig(inputScript);
|
||||||
} else {
|
} else {
|
||||||
input.setScriptSig(ScriptBuilder.createEmpty());
|
input.setScriptSig(ScriptBuilder.createEmpty());
|
||||||
TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig);
|
input.setWitness(TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig));
|
||||||
input.setWitness(witness);
|
|
||||||
}
|
}
|
||||||
WalletService.printTx("mediated payoutTx", payoutTx);
|
WalletService.printTx("mediated payoutTx", payoutTx);
|
||||||
WalletService.verifyTransaction(payoutTx);
|
WalletService.verifyTransaction(payoutTx);
|
||||||
|
@ -1059,8 +1056,7 @@ public class TradeWalletService {
|
||||||
input.setScriptSig(inputScript);
|
input.setScriptSig(inputScript);
|
||||||
} else {
|
} else {
|
||||||
input.setScriptSig(ScriptBuilder.createEmpty());
|
input.setScriptSig(ScriptBuilder.createEmpty());
|
||||||
TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript, arbitratorTxSig, tradersTxSig);
|
input.setWitness(TransactionWitness.redeemP2WSH(redeemScript, arbitratorTxSig, tradersTxSig));
|
||||||
input.setWitness(witness);
|
|
||||||
}
|
}
|
||||||
WalletService.printTx("disputed payoutTx", payoutTx);
|
WalletService.printTx("disputed payoutTx", payoutTx);
|
||||||
WalletService.verifyTransaction(payoutTx);
|
WalletService.verifyTransaction(payoutTx);
|
||||||
|
@ -1105,7 +1101,10 @@ public class TradeWalletService {
|
||||||
return new Tuple2<>(redeemScriptHex, unsignedTxHex);
|
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 {
|
throws IllegalArgumentException {
|
||||||
boolean hashedMultiSigOutputIsLegacy = true;
|
boolean hashedMultiSigOutputIsLegacy = true;
|
||||||
if (rawTxHex.startsWith("010000000001"))
|
if (rawTxHex.startsWith("010000000001"))
|
||||||
|
@ -1146,8 +1145,7 @@ public class TradeWalletService {
|
||||||
input.setScriptSig(inputScript);
|
input.setScriptSig(inputScript);
|
||||||
} else {
|
} else {
|
||||||
input.setScriptSig(ScriptBuilder.createEmpty());
|
input.setScriptSig(ScriptBuilder.createEmpty());
|
||||||
TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig);
|
input.setWitness(TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig));
|
||||||
input.setWitness(witness);
|
|
||||||
}
|
}
|
||||||
String txId = payoutTx.getTxId().toString();
|
String txId = payoutTx.getTxId().toString();
|
||||||
String signedTxHex = Utils.HEX.encode(payoutTx.bitcoinSerialize(!hashedMultiSigOutputIsLegacy));
|
String signedTxHex = Utils.HEX.encode(payoutTx.bitcoinSerialize(!hashedMultiSigOutputIsLegacy));
|
||||||
|
@ -1163,6 +1161,96 @@ public class TradeWalletService {
|
||||||
broadcastTx(payoutTx, callback, 20);
|
broadcastTx(payoutTx, callback, 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// BsqSwap tx
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public Transaction sellerBuildBsqSwapTx(List<RawTransactionInput> buyersBsqInputs,
|
||||||
|
List<RawTransactionInput> 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<TransactionInput> 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<RawTransactionInput> buyersBsqInputs,
|
||||||
|
List<TransactionInput> 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<RawTransactionInput> buyersBsqInputs,
|
||||||
|
List<TransactionInput> 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<TransactionInput> myInputs)
|
||||||
|
throws SigningException {
|
||||||
|
for (TransactionInput input : myInputs) {
|
||||||
|
signInput(transaction, input, input.getIndex());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Broadcast tx
|
// Broadcast tx
|
||||||
|
@ -1207,7 +1295,12 @@ public class TradeWalletService {
|
||||||
// Private methods
|
// 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(), "input.getConnectedOutput() must not be null");
|
||||||
checkNotNull(input.getConnectedOutput().getParentTransaction(),
|
checkNotNull(input.getConnectedOutput().getParentTransaction(),
|
||||||
"input.getConnectedOutput().getParentTransaction() must not be null");
|
"input.getConnectedOutput().getParentTransaction() must not be null");
|
||||||
|
@ -1222,10 +1315,13 @@ public class TradeWalletService {
|
||||||
input.getValue().value);
|
input.getValue().value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TransactionInput getTransactionInput(Transaction depositTx,
|
private TransactionInput getTransactionInput(Transaction parentTransaction,
|
||||||
byte[] scriptProgram,
|
byte[] scriptProgram,
|
||||||
RawTransactionInput rawTransactionInput) {
|
RawTransactionInput rawTransactionInput) {
|
||||||
return new TransactionInput(params, depositTx, scriptProgram, getConnectedOutPoint(rawTransactionInput),
|
return new TransactionInput(params,
|
||||||
|
parentTransaction,
|
||||||
|
scriptProgram,
|
||||||
|
getConnectedOutPoint(rawTransactionInput),
|
||||||
Coin.valueOf(rawTransactionInput.value));
|
Coin.valueOf(rawTransactionInput.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1239,7 +1335,6 @@ public class TradeWalletService {
|
||||||
checkNotNull(getConnectedOutPoint(rawTransactionInput).getConnectedOutput()).getScriptPubKey());
|
checkNotNull(getConnectedOutPoint(rawTransactionInput).getConnectedOutput()).getScriptPubKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO: Once we have removed legacy arbitrator from dispute domain we can remove that method as well.
|
// 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.
|
// Atm it is still used by traderSignAndFinalizeDisputedPayoutTx which is used by ArbitrationManager.
|
||||||
|
|
||||||
|
@ -1299,7 +1394,6 @@ public class TradeWalletService {
|
||||||
private void signInput(Transaction transaction, TransactionInput input, int inputIndex) throws SigningException {
|
private void signInput(Transaction transaction, TransactionInput input, int inputIndex) throws SigningException {
|
||||||
checkNotNull(input.getConnectedOutput(), "input.getConnectedOutput() must not be null");
|
checkNotNull(input.getConnectedOutput(), "input.getConnectedOutput() must not be null");
|
||||||
Script scriptPubKey = input.getConnectedOutput().getScriptPubKey();
|
Script scriptPubKey = input.getConnectedOutput().getScriptPubKey();
|
||||||
checkNotNull(wallet);
|
|
||||||
ECKey sigKey = input.getOutpoint().getConnectedKey(wallet);
|
ECKey sigKey = input.getOutpoint().getConnectedKey(wallet);
|
||||||
checkNotNull(sigKey, "signInput: sigKey must not be null. input.getOutpoint()=" +
|
checkNotNull(sigKey, "signInput: sigKey must not be null. input.getOutpoint()=" +
|
||||||
input.getOutpoint().toString());
|
input.getOutpoint().toString());
|
||||||
|
|
|
@ -102,6 +102,7 @@ import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
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.checkNotNull;
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
|
||||||
|
@ -278,6 +279,31 @@ public abstract class WalletService {
|
||||||
// Sign tx
|
// 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,
|
public static void signTransactionInput(Wallet wallet,
|
||||||
KeyParameter aesKey,
|
KeyParameter aesKey,
|
||||||
Transaction tx,
|
Transaction tx,
|
||||||
|
@ -357,12 +383,13 @@ public abstract class WalletService {
|
||||||
txIn.setScriptSig(ScriptBuilder.createEmpty());
|
txIn.setScriptSig(ScriptBuilder.createEmpty());
|
||||||
txIn.setWitness(TransactionWitness.redeemP2WPKH(txSig, key));
|
txIn.setWitness(TransactionWitness.redeemP2WPKH(txSig, key));
|
||||||
} catch (ECKey.KeyIsEncryptedException e1) {
|
} catch (ECKey.KeyIsEncryptedException e1) {
|
||||||
|
log.error(e1.toString());
|
||||||
throw e1;
|
throw e1;
|
||||||
} catch (ECKey.MissingPrivateKeyException e1) {
|
} catch (ECKey.MissingPrivateKeyException e1) {
|
||||||
log.warn("No private key in keypair for input {}", index);
|
log.warn("No private key in keypair for input {}", index);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// log.error("Unexpected script type.");
|
log.error("Unexpected script type.");
|
||||||
throw new RuntimeException("Unexpected script type.");
|
throw new RuntimeException("Unexpected script type.");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -374,6 +401,23 @@ public abstract class WalletService {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Dust
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public static void verifyNonDustTxo(Transaction tx) {
|
||||||
|
for (TransactionOutput txo : tx.getOutputs()) {
|
||||||
|
Coin value = txo.getValue();
|
||||||
|
// OpReturn outputs have value 0
|
||||||
|
if (value.isPositive()) {
|
||||||
|
checkArgument(Restrictions.isAboveDust(txo.getValue()),
|
||||||
|
"An output value is below dust limit. Transaction=" + tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Broadcast tx
|
// Broadcast tx
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -482,7 +526,7 @@ public abstract class WalletService {
|
||||||
// Balance
|
// Balance
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public Coin getAvailableConfirmedBalance() {
|
public Coin getAvailableBalance() {
|
||||||
return wallet != null ? wallet.getBalance(Wallet.BalanceType.AVAILABLE) : Coin.ZERO;
|
return wallet != null ? wallet.getBalance(Wallet.BalanceType.AVAILABLE) : Coin.ZERO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -546,6 +590,10 @@ public abstract class WalletService {
|
||||||
return getNumTxOutputsForAddress(address) == 0;
|
return getNumTxOutputsForAddress(address) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isMine(TransactionOutput transactionOutput) {
|
||||||
|
return transactionOutput.isMine(wallet);
|
||||||
|
}
|
||||||
|
|
||||||
// BISQ issue #4039: Prevent dust outputs from being created.
|
// BISQ issue #4039: Prevent dust outputs from being created.
|
||||||
// Check the outputs of a proposed transaction. If any are below the dust threshold,
|
// Check the outputs of a proposed transaction. If any are below the dust threshold,
|
||||||
// add up the dust, log the details, and return the cumulative dust amount.
|
// add up the dust, log the details, and return the cumulative dust amount.
|
||||||
|
@ -818,11 +866,15 @@ public abstract class WalletService {
|
||||||
return maybeAddTxToWallet(transaction.bitcoinSerialize(), wallet, source);
|
return maybeAddTxToWallet(transaction.bitcoinSerialize(), wallet, source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// bisqWalletEventListener
|
// bisqWalletEventListener
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public class BisqWalletListener implements WalletCoinsReceivedEventListener, WalletCoinsSentEventListener, WalletReorganizeEventListener, TransactionConfidenceEventListener {
|
public class BisqWalletListener implements WalletCoinsReceivedEventListener,
|
||||||
|
WalletCoinsSentEventListener,
|
||||||
|
WalletReorganizeEventListener,
|
||||||
|
TransactionConfidenceEventListener {
|
||||||
@Override
|
@Override
|
||||||
public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
|
public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
|
||||||
notifyBalanceListeners(tx);
|
notifyBalanceListeners(tx);
|
||||||
|
@ -848,7 +900,7 @@ public abstract class WalletService {
|
||||||
.filter(txConfidenceListener -> tx != null &&
|
.filter(txConfidenceListener -> tx != null &&
|
||||||
tx.getTxId().toString() != null &&
|
tx.getTxId().toString() != null &&
|
||||||
txConfidenceListener != null &&
|
txConfidenceListener != null &&
|
||||||
tx.getTxId().toString().equals(txConfidenceListener.getTxID()))
|
tx.getTxId().toString().equals(txConfidenceListener.getTxId()))
|
||||||
.forEach(txConfidenceListener ->
|
.forEach(txConfidenceListener ->
|
||||||
txConfidenceListener.onTransactionConfidenceChanged(tx.getConfidence()));
|
txConfidenceListener.onTransactionConfidenceChanged(tx.getConfidence()));
|
||||||
}
|
}
|
||||||
|
@ -859,7 +911,7 @@ public abstract class WalletService {
|
||||||
if (balanceListener.getAddress() != null)
|
if (balanceListener.getAddress() != null)
|
||||||
balance = getBalanceForAddress(balanceListener.getAddress());
|
balance = getBalanceForAddress(balanceListener.getAddress());
|
||||||
else
|
else
|
||||||
balance = getAvailableConfirmedBalance();
|
balance = getAvailableBalance();
|
||||||
|
|
||||||
balanceListener.onBalanceChanged(balance, tx);
|
balanceListener.onBalanceChanged(balance, tx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,7 @@ import bisq.core.dao.state.model.blockchain.BaseTxOutput;
|
||||||
import bisq.core.dao.state.model.blockchain.Block;
|
import bisq.core.dao.state.model.blockchain.Block;
|
||||||
import bisq.core.dao.state.model.blockchain.Tx;
|
import bisq.core.dao.state.model.blockchain.Tx;
|
||||||
import bisq.core.dao.state.model.blockchain.TxOutput;
|
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.blockchain.TxType;
|
||||||
import bisq.core.dao.state.model.governance.Ballot;
|
import bisq.core.dao.state.model.governance.Ballot;
|
||||||
import bisq.core.dao.state.model.governance.BondedRoleType;
|
import bisq.core.dao.state.model.governance.BondedRoleType;
|
||||||
|
@ -649,6 +650,14 @@ public class DaoFacade implements DaoSetupService {
|
||||||
return daoStateService.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() {
|
public int getNumTxs() {
|
||||||
return daoStateService.getNumTxs();
|
return daoStateService.getNumTxs();
|
||||||
}
|
}
|
||||||
|
@ -796,4 +805,8 @@ public class DaoFacade implements DaoSetupService {
|
||||||
|
|
||||||
return allPastParamValues;
|
return allPastParamValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isParseBlockChainComplete() {
|
||||||
|
return daoStateService.isParseBlockChainComplete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -205,7 +205,7 @@ public class AssetService implements DaoSetupService, DaoStateListener {
|
||||||
// We add the BTC inputs for the miner fee.
|
// We add the BTC inputs for the miner fee.
|
||||||
Transaction txWithBtcFee = btcWalletService.completePreparedBurnBsqTx(preparedBurnFeeTx, opReturnData);
|
Transaction txWithBtcFee = btcWalletService.completePreparedBurnBsqTx(preparedBurnFeeTx, opReturnData);
|
||||||
// We sign the BSQ inputs of the final tx.
|
// 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);
|
log.info("Asset listing fee tx: " + transaction);
|
||||||
return transaction;
|
return transaction;
|
||||||
} catch (WalletException | TransactionVerificationException e) {
|
} catch (WalletException | TransactionVerificationException e) {
|
||||||
|
|
|
@ -356,7 +356,7 @@ public class MyBlindVoteListService implements PersistedDataHost, DaoStateListen
|
||||||
throws InsufficientMoneyException, WalletException, TransactionVerificationException {
|
throws InsufficientMoneyException, WalletException, TransactionVerificationException {
|
||||||
Transaction preparedTx = bsqWalletService.getPreparedBlindVoteTx(fee, stake);
|
Transaction preparedTx = bsqWalletService.getPreparedBlindVoteTx(fee, stake);
|
||||||
Transaction txWithBtcFee = btcWalletService.completePreparedBlindVoteTx(preparedTx, opReturnData);
|
Transaction txWithBtcFee = btcWalletService.completePreparedBlindVoteTx(preparedTx, opReturnData);
|
||||||
return bsqWalletService.signTx(txWithBtcFee);
|
return bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void maybeRePublishMyBlindVote() {
|
private void maybeRePublishMyBlindVote() {
|
||||||
|
|
|
@ -104,7 +104,7 @@ public class LockupTxService {
|
||||||
byte[] opReturnData = BondConsensus.getLockupOpReturnData(lockTime, lockupReason, hash);
|
byte[] opReturnData = BondConsensus.getLockupOpReturnData(lockTime, lockupReason, hash);
|
||||||
Transaction preparedTx = bsqWalletService.getPreparedLockupTx(lockupAmount);
|
Transaction preparedTx = bsqWalletService.getPreparedLockupTx(lockupAmount);
|
||||||
Transaction txWithBtcFee = btcWalletService.completePreparedBsqTx(preparedTx, opReturnData);
|
Transaction txWithBtcFee = btcWalletService.completePreparedBsqTx(preparedTx, opReturnData);
|
||||||
Transaction transaction = bsqWalletService.signTx(txWithBtcFee);
|
Transaction transaction = bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee);
|
||||||
log.info("Lockup tx: " + transaction);
|
log.info("Lockup tx: " + transaction);
|
||||||
return transaction;
|
return transaction;
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,7 +104,7 @@ public class UnlockTxService {
|
||||||
TxOutput lockupTxOutput = optionalLockupTxOutput.get();
|
TxOutput lockupTxOutput = optionalLockupTxOutput.get();
|
||||||
Transaction preparedTx = bsqWalletService.getPreparedUnlockTx(lockupTxOutput);
|
Transaction preparedTx = bsqWalletService.getPreparedUnlockTx(lockupTxOutput);
|
||||||
Transaction txWithBtcFee = btcWalletService.completePreparedBsqTx(preparedTx, null);
|
Transaction txWithBtcFee = btcWalletService.completePreparedBsqTx(preparedTx, null);
|
||||||
Transaction transaction = bsqWalletService.signTx(txWithBtcFee);
|
Transaction transaction = bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee);
|
||||||
log.info("Unlock tx: " + transaction);
|
log.info("Unlock tx: " + transaction);
|
||||||
return transaction;
|
return transaction;
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,7 +140,7 @@ public class ProofOfBurnService implements DaoSetupService, DaoStateListener {
|
||||||
// We add the BTC inputs for the miner fee.
|
// We add the BTC inputs for the miner fee.
|
||||||
Transaction txWithBtcFee = btcWalletService.completePreparedBurnBsqTx(preparedBurnFeeTx, opReturnData);
|
Transaction txWithBtcFee = btcWalletService.completePreparedBurnBsqTx(preparedBurnFeeTx, opReturnData);
|
||||||
// We sign the BSQ inputs of the final tx.
|
// 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);
|
log.info("Proof of burn tx: " + transaction);
|
||||||
return transaction;
|
return transaction;
|
||||||
} catch (WalletException | TransactionVerificationException e) {
|
} catch (WalletException | TransactionVerificationException e) {
|
||||||
|
|
|
@ -99,7 +99,7 @@ public abstract class BaseProposalFactory<R extends Proposal> {
|
||||||
Transaction txWithBtcFee = completeTx(preparedBurnFeeTx, opReturnData, proposal);
|
Transaction txWithBtcFee = completeTx(preparedBurnFeeTx, opReturnData, proposal);
|
||||||
|
|
||||||
// We sign the BSQ inputs of the final tx.
|
// We sign the BSQ inputs of the final tx.
|
||||||
Transaction transaction = bsqWalletService.signTx(txWithBtcFee);
|
Transaction transaction = bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee);
|
||||||
log.info("Proposal tx: " + transaction);
|
log.info("Proposal tx: " + transaction);
|
||||||
return transaction;
|
return transaction;
|
||||||
} catch (WalletException | TransactionVerificationException e) {
|
} catch (WalletException | TransactionVerificationException e) {
|
||||||
|
|
|
@ -270,6 +270,6 @@ public class VoteRevealService implements DaoStateListener, DaoSetupService {
|
||||||
throws InsufficientMoneyException, WalletException, TransactionVerificationException {
|
throws InsufficientMoneyException, WalletException, TransactionVerificationException {
|
||||||
Transaction preparedTx = bsqWalletService.getPreparedVoteRevealTx(stakeTxOutput);
|
Transaction preparedTx = bsqWalletService.getPreparedVoteRevealTx(stakeTxOutput);
|
||||||
Transaction txWithBtcFee = btcWalletService.completePreparedVoteRevealTx(preparedTx, opReturnData);
|
Transaction txWithBtcFee = btcWalletService.completePreparedVoteRevealTx(preparedTx, opReturnData);
|
||||||
return bsqWalletService.signTx(txWithBtcFee);
|
return bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import bisq.core.dao.state.model.blockchain.PubKeyScript;
|
||||||
import bisq.core.dao.state.model.blockchain.Tx;
|
import bisq.core.dao.state.model.blockchain.Tx;
|
||||||
import bisq.core.dao.state.model.blockchain.TxOutput;
|
import bisq.core.dao.state.model.blockchain.TxOutput;
|
||||||
import bisq.core.dao.state.model.blockchain.TxType;
|
import bisq.core.dao.state.model.blockchain.TxType;
|
||||||
|
import bisq.core.util.JsonUtil;
|
||||||
|
|
||||||
import bisq.common.config.Config;
|
import bisq.common.config.Config;
|
||||||
import bisq.common.file.FileUtil;
|
import bisq.common.file.FileUtil;
|
||||||
|
@ -154,9 +155,9 @@ public class ExportJsonFilesService implements DaoSetupService {
|
||||||
JsonBlocks jsonBlocks = new JsonBlocks(daoState.getChainHeight(), jsonBlockList);
|
JsonBlocks jsonBlocks = new JsonBlocks(daoState.getChainHeight(), jsonBlockList);
|
||||||
|
|
||||||
ListenableFuture<Void> future = executor.submit(() -> {
|
ListenableFuture<Void> future = executor.submit(() -> {
|
||||||
bsqStateFileManager.writeToDisc(Utilities.objectToJson(jsonBlocks), "blocks");
|
bsqStateFileManager.writeToDisc(JsonUtil.objectToJson(jsonBlocks), "blocks");
|
||||||
allJsonTxOutputs.forEach(jsonTxOutput -> txOutputFileManager.writeToDisc(Utilities.objectToJson(jsonTxOutput), jsonTxOutput.getId()));
|
allJsonTxOutputs.forEach(jsonTxOutput -> txOutputFileManager.writeToDisc(JsonUtil.objectToJson(jsonTxOutput), jsonTxOutput.getId()));
|
||||||
jsonTxs.forEach(jsonTx -> txFileManager.writeToDisc(Utilities.objectToJson(jsonTx), jsonTx.getId()));
|
jsonTxs.forEach(jsonTx -> txFileManager.writeToDisc(JsonUtil.objectToJson(jsonTx), jsonTx.getId()));
|
||||||
|
|
||||||
GcUtil.maybeReleaseMemory();
|
GcUtil.maybeReleaseMemory();
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import bisq.core.dao.DaoSetupService;
|
||||||
import bisq.core.dao.governance.bond.BondConsensus;
|
import bisq.core.dao.governance.bond.BondConsensus;
|
||||||
import bisq.core.dao.governance.param.Param;
|
import bisq.core.dao.governance.param.Param;
|
||||||
import bisq.core.dao.state.model.DaoState;
|
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.Block;
|
||||||
import bisq.core.dao.state.model.blockchain.SpentInfo;
|
import bisq.core.dao.state.model.blockchain.SpentInfo;
|
||||||
import bisq.core.dao.state.model.blockchain.Tx;
|
import bisq.core.dao.state.model.blockchain.Tx;
|
||||||
|
@ -484,6 +485,12 @@ public class DaoStateService implements DaoSetupService {
|
||||||
return Optional.ofNullable(getUnspentTxOutputMap().getOrDefault(key, null));
|
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) {
|
public boolean isTxOutputSpendable(TxOutputKey key) {
|
||||||
if (!isUnspent(key))
|
if (!isUnspent(key))
|
||||||
return false;
|
return false;
|
||||||
|
@ -492,7 +499,12 @@ public class DaoStateService implements DaoSetupService {
|
||||||
// The above isUnspent call satisfies optionalTxOutput.isPresent()
|
// The above isUnspent call satisfies optionalTxOutput.isPresent()
|
||||||
checkArgument(optionalTxOutput.isPresent(), "optionalTxOutput must be present");
|
checkArgument(optionalTxOutput.isPresent(), "optionalTxOutput must be present");
|
||||||
TxOutput txOutput = optionalTxOutput.get();
|
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()) {
|
switch (txOutput.getTxOutputType()) {
|
||||||
case UNDEFINED_OUTPUT:
|
case UNDEFINED_OUTPUT:
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -39,13 +39,15 @@ import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import lombok.Value;
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Value
|
@Getter
|
||||||
|
@EqualsAndHashCode
|
||||||
public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
||||||
public static final long TTL = TimeUnit.DAYS.toMillis(180);
|
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
|
// added at v1.6.0
|
||||||
private final boolean disableMempoolValidation;
|
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
|
// After we have created the signature from the filter data we clone it and apply the signature
|
||||||
static Filter cloneWithSig(Filter filter, String signatureAsBase64) {
|
static Filter cloneWithSig(Filter filter, String signatureAsBase64) {
|
||||||
return new Filter(filter.getBannedOfferIds(),
|
return new Filter(filter.getBannedOfferIds(),
|
||||||
|
@ -130,7 +138,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
||||||
filter.getBannedAutoConfExplorers(),
|
filter.getBannedAutoConfExplorers(),
|
||||||
filter.getNodeAddressesBannedFromNetwork(),
|
filter.getNodeAddressesBannedFromNetwork(),
|
||||||
filter.isDisableMempoolValidation(),
|
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
|
// Used for signature verification as we created the sig without the signatureAsBase64 field we set it to null again
|
||||||
|
@ -162,7 +172,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
||||||
filter.getBannedAutoConfExplorers(),
|
filter.getBannedAutoConfExplorers(),
|
||||||
filter.getNodeAddressesBannedFromNetwork(),
|
filter.getNodeAddressesBannedFromNetwork(),
|
||||||
filter.isDisableMempoolValidation(),
|
filter.isDisableMempoolValidation(),
|
||||||
filter.isDisableApi());
|
filter.isDisableApi(),
|
||||||
|
filter.isDisablePowMessage(),
|
||||||
|
filter.getPowDifficulty());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Filter(List<String> bannedOfferIds,
|
public Filter(List<String> bannedOfferIds,
|
||||||
|
@ -189,7 +201,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
||||||
List<String> bannedAutoConfExplorers,
|
List<String> bannedAutoConfExplorers,
|
||||||
Set<String> nodeAddressesBannedFromNetwork,
|
Set<String> nodeAddressesBannedFromNetwork,
|
||||||
boolean disableMempoolValidation,
|
boolean disableMempoolValidation,
|
||||||
boolean disableApi) {
|
boolean disableApi,
|
||||||
|
boolean disablePowMessage,
|
||||||
|
int powDifficulty) {
|
||||||
this(bannedOfferIds,
|
this(bannedOfferIds,
|
||||||
nodeAddressesBannedFromTrading,
|
nodeAddressesBannedFromTrading,
|
||||||
bannedPaymentAccounts,
|
bannedPaymentAccounts,
|
||||||
|
@ -217,7 +231,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
||||||
bannedAutoConfExplorers,
|
bannedAutoConfExplorers,
|
||||||
nodeAddressesBannedFromNetwork,
|
nodeAddressesBannedFromNetwork,
|
||||||
disableMempoolValidation,
|
disableMempoolValidation,
|
||||||
disableApi);
|
disableApi,
|
||||||
|
disablePowMessage,
|
||||||
|
powDifficulty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -253,7 +269,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
||||||
List<String> bannedAutoConfExplorers,
|
List<String> bannedAutoConfExplorers,
|
||||||
Set<String> nodeAddressesBannedFromNetwork,
|
Set<String> nodeAddressesBannedFromNetwork,
|
||||||
boolean disableMempoolValidation,
|
boolean disableMempoolValidation,
|
||||||
boolean disableApi) {
|
boolean disableApi,
|
||||||
|
boolean disablePowMessage,
|
||||||
|
int powDifficulty) {
|
||||||
this.bannedOfferIds = bannedOfferIds;
|
this.bannedOfferIds = bannedOfferIds;
|
||||||
this.nodeAddressesBannedFromTrading = nodeAddressesBannedFromTrading;
|
this.nodeAddressesBannedFromTrading = nodeAddressesBannedFromTrading;
|
||||||
this.bannedPaymentAccounts = bannedPaymentAccounts;
|
this.bannedPaymentAccounts = bannedPaymentAccounts;
|
||||||
|
@ -282,6 +300,8 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
||||||
this.nodeAddressesBannedFromNetwork = nodeAddressesBannedFromNetwork;
|
this.nodeAddressesBannedFromNetwork = nodeAddressesBannedFromNetwork;
|
||||||
this.disableMempoolValidation = disableMempoolValidation;
|
this.disableMempoolValidation = disableMempoolValidation;
|
||||||
this.disableApi = disableApi;
|
this.disableApi = disableApi;
|
||||||
|
this.disablePowMessage = disablePowMessage;
|
||||||
|
this.powDifficulty = powDifficulty;
|
||||||
|
|
||||||
// ownerPubKeyBytes can be null when called from tests
|
// ownerPubKeyBytes can be null when called from tests
|
||||||
if (ownerPubKeyBytes != null) {
|
if (ownerPubKeyBytes != null) {
|
||||||
|
@ -322,7 +342,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
||||||
.addAllBannedAutoConfExplorers(bannedAutoConfExplorers)
|
.addAllBannedAutoConfExplorers(bannedAutoConfExplorers)
|
||||||
.addAllNodeAddressesBannedFromNetwork(nodeAddressesBannedFromNetwork)
|
.addAllNodeAddressesBannedFromNetwork(nodeAddressesBannedFromNetwork)
|
||||||
.setDisableMempoolValidation(disableMempoolValidation)
|
.setDisableMempoolValidation(disableMempoolValidation)
|
||||||
.setDisableApi(disableApi);
|
.setDisableApi(disableApi)
|
||||||
|
.setDisablePowMessage(disablePowMessage)
|
||||||
|
.setPowDifficulty(powDifficulty);
|
||||||
|
|
||||||
Optional.ofNullable(signatureAsBase64).ifPresent(builder::setSignatureAsBase64);
|
Optional.ofNullable(signatureAsBase64).ifPresent(builder::setSignatureAsBase64);
|
||||||
Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData);
|
Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData);
|
||||||
|
@ -363,7 +385,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
||||||
ProtoUtil.protocolStringListToList(proto.getBannedAutoConfExplorersList()),
|
ProtoUtil.protocolStringListToList(proto.getBannedAutoConfExplorersList()),
|
||||||
ProtoUtil.protocolStringListToSet(proto.getNodeAddressesBannedFromNetworkList()),
|
ProtoUtil.protocolStringListToSet(proto.getNodeAddressesBannedFromNetworkList()),
|
||||||
proto.getDisableMempoolValidation(),
|
proto.getDisableMempoolValidation(),
|
||||||
proto.getDisableApi()
|
proto.getDisableApi(),
|
||||||
|
proto.getDisablePowMessage(),
|
||||||
|
proto.getPowDifficulty()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -409,6 +433,8 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
||||||
",\n nodeAddressesBannedFromNetwork=" + nodeAddressesBannedFromNetwork +
|
",\n nodeAddressesBannedFromNetwork=" + nodeAddressesBannedFromNetwork +
|
||||||
",\n disableMempoolValidation=" + disableMempoolValidation +
|
",\n disableMempoolValidation=" + disableMempoolValidation +
|
||||||
",\n disableApi=" + disableApi +
|
",\n disableApi=" + disableApi +
|
||||||
|
",\n disablePowMessage=" + disablePowMessage +
|
||||||
|
",\n powDifficulty=" + powDifficulty +
|
||||||
"\n}";
|
"\n}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ package bisq.core.filter;
|
||||||
|
|
||||||
import bisq.core.btc.nodes.BtcNodes;
|
import bisq.core.btc.nodes.BtcNodes;
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
|
import bisq.core.offer.Offer;
|
||||||
import bisq.core.payment.payload.PaymentAccountPayload;
|
import bisq.core.payment.payload.PaymentAccountPayload;
|
||||||
import bisq.core.payment.payload.PaymentMethod;
|
import bisq.core.payment.payload.PaymentMethod;
|
||||||
import bisq.core.provider.ProvidersRepository;
|
import bisq.core.provider.ProvidersRepository;
|
||||||
|
@ -36,6 +37,7 @@ import bisq.common.app.DevEnv;
|
||||||
import bisq.common.app.Version;
|
import bisq.common.app.Version;
|
||||||
import bisq.common.config.Config;
|
import bisq.common.config.Config;
|
||||||
import bisq.common.config.ConfigFileEditor;
|
import bisq.common.config.ConfigFileEditor;
|
||||||
|
import bisq.common.crypto.HashCashService;
|
||||||
import bisq.common.crypto.KeyRing;
|
import bisq.common.crypto.KeyRing;
|
||||||
|
|
||||||
import org.bitcoinj.core.ECKey;
|
import org.bitcoinj.core.ECKey;
|
||||||
|
@ -55,6 +57,7 @@ import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
@ -62,6 +65,7 @@ import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
@ -70,6 +74,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
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.checkNotNull;
|
||||||
import static org.bitcoinj.core.Utils.HEX;
|
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_SEED_NODES = "bannedSeedNodes";
|
||||||
private static final String BANNED_BTC_NODES = "bannedBtcNodes";
|
private static final String BANNED_BTC_NODES = "bannedBtcNodes";
|
||||||
|
|
||||||
|
private final BiFunction<byte[], byte[], Boolean> challengeValidation = Arrays::equals;
|
||||||
|
// We only require a new pow if difficulty has increased
|
||||||
|
private final BiFunction<Integer, Integer, Boolean> difficultyValidation =
|
||||||
|
(value, controlValue) -> value - controlValue >= 0;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Listener
|
// Listener
|
||||||
|
@ -476,6 +486,20 @@ public class FilterManager {
|
||||||
.anyMatch(e -> e.equals(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
|
// Private
|
||||||
|
@ -499,13 +523,13 @@ public class FilterManager {
|
||||||
|
|
||||||
if (currentFilter != null) {
|
if (currentFilter != null) {
|
||||||
if (currentFilter.getCreationDate() > newFilter.getCreationDate()) {
|
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.");
|
"filter we have already. We ignore the new filter.");
|
||||||
|
|
||||||
addToInvalidFilters(newFilter);
|
addToInvalidFilters(newFilter);
|
||||||
return;
|
return;
|
||||||
} else {
|
} 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.");
|
"filter we have already. We ignore the old filter.");
|
||||||
addToInvalidFilters(currentFilter);
|
addToInvalidFilters(currentFilter);
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,13 +120,13 @@ public class Res {
|
||||||
.replace("bitcoin", baseCurrencyNameLowerCase);
|
.replace("bitcoin", baseCurrencyNameLowerCase);
|
||||||
} catch (MissingResourceException e) {
|
} catch (MissingResourceException e) {
|
||||||
log.warn("Missing resource for key: {}", key);
|
log.warn("Missing resource for key: {}", key);
|
||||||
|
if (DevEnv.isDevMode()) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
if (DevEnv.isDevMode())
|
|
||||||
UserThread.runAfter(() -> {
|
UserThread.runAfter(() -> {
|
||||||
// We delay a bit to not throw while UI is not ready
|
// We delay a bit to not throw while UI is not ready
|
||||||
throw new RuntimeException("Missing resource for key: " + key);
|
throw new RuntimeException("Missing resource for key: " + key);
|
||||||
}, 1);
|
}, 1);
|
||||||
|
}
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,8 @@ import bisq.core.locale.Res;
|
||||||
import bisq.core.notifications.MobileMessage;
|
import bisq.core.notifications.MobileMessage;
|
||||||
import bisq.core.notifications.MobileMessageType;
|
import bisq.core.notifications.MobileMessageType;
|
||||||
import bisq.core.notifications.MobileNotificationService;
|
import bisq.core.notifications.MobileNotificationService;
|
||||||
import bisq.core.trade.Trade;
|
|
||||||
import bisq.core.trade.TradeManager;
|
import bisq.core.trade.TradeManager;
|
||||||
|
import bisq.core.trade.model.bisq_v1.Trade;
|
||||||
|
|
||||||
import bisq.common.crypto.KeyRing;
|
import bisq.common.crypto.KeyRing;
|
||||||
import bisq.common.crypto.PubKeyRing;
|
import bisq.common.crypto.PubKeyRing;
|
||||||
|
|
|
@ -26,7 +26,7 @@ import bisq.core.notifications.MobileMessageType;
|
||||||
import bisq.core.notifications.MobileNotificationService;
|
import bisq.core.notifications.MobileNotificationService;
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
import bisq.core.offer.OfferBookService;
|
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.MarketPrice;
|
||||||
import bisq.core.provider.price.PriceFeedService;
|
import bisq.core.provider.price.PriceFeedService;
|
||||||
import bisq.core.user.User;
|
import bisq.core.user.User;
|
||||||
|
@ -109,7 +109,7 @@ public class MarketAlerts {
|
||||||
// % price get multiplied by 10000 to have 0.12% be converted to 12. For fixed price we have precision of 8 for
|
// % 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.
|
// altcoins and precision of 4 for fiat.
|
||||||
private String getAlertId(Offer offer) {
|
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);
|
String priceString = String.valueOf((long) price);
|
||||||
return offer.getId() + "|" + priceString;
|
return offer.getId() + "|" + priceString;
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,7 @@ public class MarketAlerts {
|
||||||
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
|
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
|
||||||
Price offerPrice = offer.getPrice();
|
Price offerPrice = offer.getPrice();
|
||||||
if (marketPrice != null && offerPrice != null) {
|
if (marketPrice != null && offerPrice != null) {
|
||||||
boolean isSellOffer = offer.getDirection() == OfferPayload.Direction.SELL;
|
boolean isSellOffer = offer.getDirection() == OfferDirection.SELL;
|
||||||
String shortOfferId = offer.getShortId();
|
String shortOfferId = offer.getShortId();
|
||||||
boolean isFiatCurrency = CurrencyUtil.isFiatCurrency(currencyCode);
|
boolean isFiatCurrency = CurrencyUtil.isFiatCurrency(currencyCode);
|
||||||
String alertId = getAlertId(offer);
|
String alertId = getAlertId(offer);
|
||||||
|
|
|
@ -24,6 +24,9 @@ import bisq.core.monetary.Price;
|
||||||
import bisq.core.monetary.Volume;
|
import bisq.core.monetary.Volume;
|
||||||
import bisq.core.offer.availability.OfferAvailabilityModel;
|
import bisq.core.offer.availability.OfferAvailabilityModel;
|
||||||
import bisq.core.offer.availability.OfferAvailabilityProtocol;
|
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.payment.payload.PaymentMethod;
|
||||||
import bisq.core.provider.price.MarketPrice;
|
import bisq.core.provider.price.MarketPrice;
|
||||||
import bisq.core.provider.price.PriceFeedService;
|
import bisq.core.provider.price.PriceFeedService;
|
||||||
|
@ -75,7 +78,6 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
||||||
// from one provider.
|
// from one provider.
|
||||||
private final static double PRICE_TOLERANCE = 0.01;
|
private final static double PRICE_TOLERANCE = 0.01;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Enums
|
// Enums
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -94,7 +96,7 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final OfferPayload offerPayload;
|
private final OfferPayloadBase offerPayloadBase;
|
||||||
@JsonExclude
|
@JsonExclude
|
||||||
@Getter
|
@Getter
|
||||||
final transient private ObjectProperty<Offer.State> stateProperty = new SimpleObjectProperty<>(Offer.State.UNKNOWN);
|
final transient private ObjectProperty<Offer.State> stateProperty = new SimpleObjectProperty<>(Offer.State.UNKNOWN);
|
||||||
|
@ -119,8 +121,8 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
||||||
// Constructor
|
// Constructor
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public Offer(OfferPayload offerPayload) {
|
public Offer(OfferPayloadBase offerPayloadBase) {
|
||||||
this.offerPayload = offerPayload;
|
this.offerPayloadBase = offerPayloadBase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -130,11 +132,21 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public protobuf.Offer toProtoMessage() {
|
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) {
|
public static Offer fromProto(protobuf.Offer proto) {
|
||||||
|
if (proto.hasOfferPayload()) {
|
||||||
return new Offer(OfferPayload.fromProto(proto.getOfferPayload()));
|
return new Offer(OfferPayload.fromProto(proto.getOfferPayload()));
|
||||||
|
} else {
|
||||||
|
return new Offer(BsqSwapOfferPayload.fromProto(proto.getBsqSwapOfferPayload()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -166,17 +178,26 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
||||||
@Nullable
|
@Nullable
|
||||||
public Price getPrice() {
|
public Price getPrice() {
|
||||||
String currencyCode = getCurrencyCode();
|
String currencyCode = getCurrencyCode();
|
||||||
if (offerPayload.isUseMarketBasedPrice()) {
|
Optional<OfferPayload> 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");
|
checkNotNull(priceFeedService, "priceFeed must not be null");
|
||||||
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
|
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
|
||||||
if (marketPrice != null && marketPrice.isRecentExternalPriceAvailable()) {
|
if (marketPrice != null && marketPrice.isRecentExternalPriceAvailable()) {
|
||||||
double factor;
|
double factor;
|
||||||
double marketPriceMargin = offerPayload.getMarketPriceMargin();
|
double marketPriceMargin = offerPayload.getMarketPriceMargin();
|
||||||
if (CurrencyUtil.isCryptoCurrency(currencyCode)) {
|
if (CurrencyUtil.isCryptoCurrency(currencyCode)) {
|
||||||
factor = getDirection() == OfferPayload.Direction.SELL ?
|
factor = getDirection() == OfferDirection.SELL ?
|
||||||
1 - marketPriceMargin : 1 + marketPriceMargin;
|
1 - marketPriceMargin : 1 + marketPriceMargin;
|
||||||
} else {
|
} else {
|
||||||
factor = getDirection() == OfferPayload.Direction.BUY ?
|
factor = getDirection() == OfferDirection.BUY ?
|
||||||
1 - marketPriceMargin : 1 + marketPriceMargin;
|
1 - marketPriceMargin : 1 + marketPriceMargin;
|
||||||
}
|
}
|
||||||
double marketPriceAsDouble = marketPrice.getPrice();
|
double marketPriceAsDouble = marketPrice.getPrice();
|
||||||
|
@ -189,7 +210,7 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
||||||
final long roundedToLong = MathUtils.roundDoubleToLong(scaled);
|
final long roundedToLong = MathUtils.roundDoubleToLong(scaled);
|
||||||
return Price.valueOf(currencyCode, roundedToLong);
|
return Price.valueOf(currencyCode, roundedToLong);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Exception at getPrice / parseToFiat: " + e.toString() + "\n" +
|
log.error("Exception at getPrice / parseToFiat: " + e + "\n" +
|
||||||
"That case should never happen.");
|
"That case should never happen.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -198,9 +219,10 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
||||||
"That case could only happen if you don't have a price feed.");
|
"That case could only happen if you don't have a price feed.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return Price.valueOf(currencyCode, offerPayload.getPrice());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getFixedPrice() {
|
||||||
|
return offerPayloadBase.getPrice();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void checkTradePriceTolerance(long takersTradePrice) throws TradePriceOutOfToleranceException,
|
public void checkTradePriceTolerance(long takersTradePrice) throws TradePriceOutOfToleranceException,
|
||||||
|
@ -234,17 +256,16 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
||||||
@Nullable
|
@Nullable
|
||||||
public Volume getVolumeByAmount(Coin amount) {
|
public Volume getVolumeByAmount(Coin amount) {
|
||||||
Price price = getPrice();
|
Price price = getPrice();
|
||||||
if (price != null && amount != null) {
|
if (price == null || amount == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
Volume volumeByAmount = price.getVolumeByAmount(amount);
|
Volume volumeByAmount = price.getVolumeByAmount(amount);
|
||||||
if (offerPayload.getPaymentMethodId().equals(PaymentMethod.HAL_CASH_ID))
|
if (offerPayloadBase.getPaymentMethodId().equals(PaymentMethod.HAL_CASH_ID))
|
||||||
volumeByAmount = VolumeUtil.getAdjustedVolumeForHalCash(volumeByAmount);
|
volumeByAmount = VolumeUtil.getAdjustedVolumeForHalCash(volumeByAmount);
|
||||||
else if (CurrencyUtil.isFiatCurrency(offerPayload.getCurrencyCode()))
|
else if (CurrencyUtil.isFiatCurrency(offerPayloadBase.getCurrencyCode()))
|
||||||
volumeByAmount = VolumeUtil.getRoundedFiatVolume(volumeByAmount);
|
volumeByAmount = VolumeUtil.getRoundedFiatVolume(volumeByAmount);
|
||||||
|
|
||||||
return volumeByAmount;
|
return volumeByAmount;
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resetState() {
|
public void resetState() {
|
||||||
|
@ -265,7 +286,7 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOfferFeePaymentTxId(String offerFeePaymentTxID) {
|
public void setOfferFeePaymentTxId(String offerFeePaymentTxID) {
|
||||||
offerPayload.setOfferFeePaymentTxId(offerFeePaymentTxID);
|
getOfferPayload().ifPresent(p -> p.setOfferFeePaymentTxId(offerFeePaymentTxID));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setErrorMessage(String errorMessage) {
|
public void setErrorMessage(String errorMessage) {
|
||||||
|
@ -279,52 +300,52 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
||||||
|
|
||||||
// converted payload properties
|
// converted payload properties
|
||||||
public Coin getTxFee() {
|
public Coin getTxFee() {
|
||||||
return Coin.valueOf(offerPayload.getTxFee());
|
return Coin.valueOf(getOfferPayload().map(OfferPayload::getTxFee).orElse(0L));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Coin getMakerFee() {
|
public Coin getMakerFee() {
|
||||||
return Coin.valueOf(offerPayload.getMakerFee());
|
return getOfferPayload().map(OfferPayload::getMakerFee).map(Coin::valueOf).orElse(Coin.ZERO);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCurrencyForMakerFeeBtc() {
|
public boolean isCurrencyForMakerFeeBtc() {
|
||||||
return offerPayload.isCurrencyForMakerFeeBtc();
|
return getOfferPayload().map(OfferPayload::isCurrencyForMakerFeeBtc).orElse(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Coin getBuyerSecurityDeposit() {
|
public Coin getBuyerSecurityDeposit() {
|
||||||
return Coin.valueOf(offerPayload.getBuyerSecurityDeposit());
|
return Coin.valueOf(getOfferPayload().map(OfferPayload::getBuyerSecurityDeposit).orElse(0L));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Coin getSellerSecurityDeposit() {
|
public Coin getSellerSecurityDeposit() {
|
||||||
return Coin.valueOf(offerPayload.getSellerSecurityDeposit());
|
return Coin.valueOf(getOfferPayload().map(OfferPayload::getSellerSecurityDeposit).orElse(0L));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Coin getMaxTradeLimit() {
|
public Coin getMaxTradeLimit() {
|
||||||
return Coin.valueOf(offerPayload.getMaxTradeLimit());
|
return getOfferPayload().map(OfferPayload::getMaxTradeLimit).map(Coin::valueOf).orElse(Coin.ZERO);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Coin getAmount() {
|
public Coin getAmount() {
|
||||||
return Coin.valueOf(offerPayload.getAmount());
|
return Coin.valueOf(offerPayloadBase.getAmount());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Coin getMinAmount() {
|
public Coin getMinAmount() {
|
||||||
return Coin.valueOf(offerPayload.getMinAmount());
|
return Coin.valueOf(offerPayloadBase.getMinAmount());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isRange() {
|
public boolean isRange() {
|
||||||
return offerPayload.getAmount() != offerPayload.getMinAmount();
|
return offerPayloadBase.getAmount() != offerPayloadBase.getMinAmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Date getDate() {
|
public Date getDate() {
|
||||||
return new Date(offerPayload.getDate());
|
return new Date(offerPayloadBase.getDate());
|
||||||
}
|
}
|
||||||
|
|
||||||
public PaymentMethod getPaymentMethod() {
|
public PaymentMethod getPaymentMethod() {
|
||||||
return PaymentMethod.getPaymentMethodById(offerPayload.getPaymentMethodId());
|
return PaymentMethod.getPaymentMethodById(offerPayloadBase.getPaymentMethodId());
|
||||||
}
|
}
|
||||||
|
|
||||||
// utils
|
// utils
|
||||||
public String getShortId() {
|
public String getShortId() {
|
||||||
return Utilities.getShortId(offerPayload.getId());
|
return Utilities.getShortId(offerPayloadBase.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -338,18 +359,17 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isBuyOffer() {
|
public boolean isBuyOffer() {
|
||||||
return getDirection() == OfferPayload.Direction.BUY;
|
return getDirection() == OfferDirection.BUY;
|
||||||
}
|
}
|
||||||
|
|
||||||
public OfferPayload.Direction getMirroredDirection() {
|
public OfferDirection getMirroredDirection() {
|
||||||
return getDirection() == OfferPayload.Direction.BUY ? OfferPayload.Direction.SELL : OfferPayload.Direction.BUY;
|
return getDirection() == OfferDirection.BUY ? OfferDirection.SELL : OfferDirection.BUY;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isMyOffer(KeyRing keyRing) {
|
public boolean isMyOffer(KeyRing keyRing) {
|
||||||
return getPubKeyRing().equals(keyRing.getPubKeyRing());
|
return getPubKeyRing().equals(keyRing.getPubKeyRing());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Optional<String> getAccountAgeWitnessHashAsHex() {
|
public Optional<String> getAccountAgeWitnessHashAsHex() {
|
||||||
Map<String, String> extraDataMap = getExtraDataMap();
|
Map<String, String> extraDataMap = getExtraDataMap();
|
||||||
if (extraDataMap != null && extraDataMap.containsKey(OfferPayload.ACCOUNT_AGE_WITNESS_HASH))
|
if (extraDataMap != null && extraDataMap.containsKey(OfferPayload.ACCOUNT_AGE_WITNESS_HASH))
|
||||||
|
@ -400,32 +420,32 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
||||||
// Delegate Getter (boilerplate code generated via IntelliJ generate delegate feature)
|
// Delegate Getter (boilerplate code generated via IntelliJ generate delegate feature)
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public OfferPayload.Direction getDirection() {
|
public OfferDirection getDirection() {
|
||||||
return offerPayload.getDirection();
|
return offerPayloadBase.getDirection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return offerPayload.getId();
|
return offerPayloadBase.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public List<String> getAcceptedBankIds() {
|
public List<String> getAcceptedBankIds() {
|
||||||
return offerPayload.getAcceptedBankIds();
|
return getOfferPayload().map(OfferPayload::getAcceptedBankIds).orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public String getBankId() {
|
public String getBankId() {
|
||||||
return offerPayload.getBankId();
|
return getOfferPayload().map(OfferPayload::getBankId).orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public List<String> getAcceptedCountryCodes() {
|
public List<String> getAcceptedCountryCodes() {
|
||||||
return offerPayload.getAcceptedCountryCodes();
|
return getOfferPayload().map(OfferPayload::getAcceptedCountryCodes).orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public String getCountryCode() {
|
public String getCountryCode() {
|
||||||
return offerPayload.getCountryCode();
|
return getOfferPayload().map(OfferPayload::getCountryCode).orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCurrencyCode() {
|
public String getCurrencyCode() {
|
||||||
|
@ -433,89 +453,84 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
||||||
return currencyCode;
|
return currencyCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
currencyCode = offerPayload.getBaseCurrencyCode().equals("BTC") ?
|
currencyCode = getBaseCurrencyCode().equals("BTC") ?
|
||||||
offerPayload.getCounterCurrencyCode() :
|
getCounterCurrencyCode() :
|
||||||
offerPayload.getBaseCurrencyCode();
|
getBaseCurrencyCode();
|
||||||
return currencyCode;
|
return currencyCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getCounterCurrencyCode() {
|
||||||
|
return offerPayloadBase.getCounterCurrencyCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBaseCurrencyCode() {
|
||||||
|
return offerPayloadBase.getBaseCurrencyCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPaymentMethodId() {
|
||||||
|
return offerPayloadBase.getPaymentMethodId();
|
||||||
|
}
|
||||||
|
|
||||||
public long getProtocolVersion() {
|
public long getProtocolVersion() {
|
||||||
return offerPayload.getProtocolVersion();
|
return offerPayloadBase.getProtocolVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUseMarketBasedPrice() {
|
public boolean isUseMarketBasedPrice() {
|
||||||
return offerPayload.isUseMarketBasedPrice();
|
return getOfferPayload().map(OfferPayload::isUseMarketBasedPrice).orElse(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getMarketPriceMargin() {
|
public double getMarketPriceMargin() {
|
||||||
return offerPayload.getMarketPriceMargin();
|
return getOfferPayload().map(OfferPayload::getMarketPriceMargin).orElse(0D);
|
||||||
}
|
}
|
||||||
|
|
||||||
public NodeAddress getMakerNodeAddress() {
|
public NodeAddress getMakerNodeAddress() {
|
||||||
return offerPayload.getOwnerNodeAddress();
|
return offerPayloadBase.getOwnerNodeAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
public PubKeyRing getPubKeyRing() {
|
public PubKeyRing getPubKeyRing() {
|
||||||
return offerPayload.getPubKeyRing();
|
return offerPayloadBase.getPubKeyRing();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMakerPaymentAccountId() {
|
public String getMakerPaymentAccountId() {
|
||||||
return offerPayload.getMakerPaymentAccountId();
|
return offerPayloadBase.getMakerPaymentAccountId();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getOfferFeePaymentTxId() {
|
public String getOfferFeePaymentTxId() {
|
||||||
return offerPayload.getOfferFeePaymentTxId();
|
return getOfferPayload().map(OfferPayload::getOfferFeePaymentTxId).orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getVersionNr() {
|
public String getVersionNr() {
|
||||||
return offerPayload.getVersionNr();
|
return offerPayloadBase.getVersionNr();
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getMaxTradePeriod() {
|
public long getMaxTradePeriod() {
|
||||||
return offerPayload.getMaxTradePeriod();
|
return getOfferPayload().map(OfferPayload::getMaxTradePeriod).orElse(0L);
|
||||||
}
|
}
|
||||||
|
|
||||||
public NodeAddress getOwnerNodeAddress() {
|
public NodeAddress getOwnerNodeAddress() {
|
||||||
return offerPayload.getOwnerNodeAddress();
|
return offerPayloadBase.getOwnerNodeAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Yet unused
|
// Yet unused
|
||||||
public PublicKey getOwnerPubKey() {
|
public PublicKey getOwnerPubKey() {
|
||||||
return offerPayload.getOwnerPubKey();
|
return offerPayloadBase.getOwnerPubKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public Map<String, String> getExtraDataMap() {
|
public Map<String, String> getExtraDataMap() {
|
||||||
return offerPayload.getExtraDataMap();
|
return offerPayloadBase.getExtraDataMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUseAutoClose() {
|
public boolean isUseAutoClose() {
|
||||||
return offerPayload.isUseAutoClose();
|
return getOfferPayload().map(OfferPayload::isUseAutoClose).orElse(false);
|
||||||
}
|
|
||||||
|
|
||||||
public long getBlockHeightAtOfferCreation() {
|
|
||||||
return offerPayload.getBlockHeightAtOfferCreation();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public String getHashOfChallenge() {
|
|
||||||
return offerPayload.getHashOfChallenge();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isPrivateOffer() {
|
|
||||||
return offerPayload.isPrivateOffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getUpperClosePrice() {
|
|
||||||
return offerPayload.getUpperClosePrice();
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getLowerClosePrice() {
|
|
||||||
return offerPayload.getLowerClosePrice();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUseReOpenAfterAutoClose() {
|
public boolean isUseReOpenAfterAutoClose() {
|
||||||
return offerPayload.isUseReOpenAfterAutoClose();
|
return getOfferPayload().map(OfferPayload::isUseReOpenAfterAutoClose).orElse(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBsqSwapOffer() {
|
||||||
|
return getOfferPayloadBase() instanceof BsqSwapOfferPayload;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isXmrAutoConf() {
|
public boolean isXmrAutoConf() {
|
||||||
|
@ -533,6 +548,24 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
||||||
return getCurrencyCode().equals("XMR");
|
return getCurrencyCode().equals("XMR");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Optional<OfferPayload> getOfferPayload() {
|
||||||
|
if (offerPayloadBase instanceof OfferPayload) {
|
||||||
|
return Optional.of((OfferPayload) offerPayloadBase);
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<BsqSwapOfferPayload> getBsqSwapOfferPayload() {
|
||||||
|
if (offerPayloadBase instanceof BsqSwapOfferPayload) {
|
||||||
|
return Optional.of((BsqSwapOfferPayload) offerPayloadBase);
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getOfferPayloadHash() {
|
||||||
|
return offerPayloadBase.getHash();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
@ -540,7 +573,8 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
||||||
|
|
||||||
Offer offer = (Offer) 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
|
//noinspection SimplifiableIfStatement
|
||||||
if (getState() != offer.getState()) return false;
|
if (getState() != offer.getState()) return false;
|
||||||
return !(getErrorMessage() != null ? !getErrorMessage().equals(offer.getErrorMessage()) : offer.getErrorMessage() != null);
|
return !(getErrorMessage() != null ? !getErrorMessage().equals(offer.getErrorMessage()) : offer.getErrorMessage() != null);
|
||||||
|
@ -549,7 +583,7 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
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 + (getState() != null ? getState().hashCode() : 0);
|
||||||
result = 31 * result + (getErrorMessage() != null ? getErrorMessage().hashCode() : 0);
|
result = 31 * result + (getErrorMessage() != null ? getErrorMessage().hashCode() : 0);
|
||||||
return result;
|
return result;
|
||||||
|
@ -560,7 +594,7 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
||||||
return "Offer{" +
|
return "Offer{" +
|
||||||
"getErrorMessage()='" + getErrorMessage() + '\'' +
|
"getErrorMessage()='" + getErrorMessage() + '\'' +
|
||||||
", state=" + getState() +
|
", state=" + getState() +
|
||||||
", offerPayload=" + offerPayload +
|
", offerPayloadBase=" + offerPayloadBase +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ package bisq.core.offer;
|
||||||
import bisq.core.filter.FilterManager;
|
import bisq.core.filter.FilterManager;
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
import bisq.core.provider.price.PriceFeedService;
|
import bisq.core.provider.price.PriceFeedService;
|
||||||
|
import bisq.core.util.JsonUtil;
|
||||||
|
|
||||||
import bisq.network.p2p.BootstrapListener;
|
import bisq.network.p2p.BootstrapListener;
|
||||||
import bisq.network.p2p.P2PService;
|
import bisq.network.p2p.P2PService;
|
||||||
|
@ -31,7 +32,6 @@ import bisq.common.config.Config;
|
||||||
import bisq.common.file.JsonFileManager;
|
import bisq.common.file.JsonFileManager;
|
||||||
import bisq.common.handlers.ErrorMessageHandler;
|
import bisq.common.handlers.ErrorMessageHandler;
|
||||||
import bisq.common.handlers.ResultHandler;
|
import bisq.common.handlers.ResultHandler;
|
||||||
import bisq.common.util.Utilities;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
@ -87,9 +87,9 @@ public class OfferBookService {
|
||||||
@Override
|
@Override
|
||||||
public void onAdded(Collection<ProtectedStorageEntry> protectedStorageEntries) {
|
public void onAdded(Collection<ProtectedStorageEntry> protectedStorageEntries) {
|
||||||
protectedStorageEntries.forEach(protectedStorageEntry -> offerBookChangedListeners.forEach(listener -> {
|
protectedStorageEntries.forEach(protectedStorageEntry -> offerBookChangedListeners.forEach(listener -> {
|
||||||
if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayload) {
|
if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayloadBase) {
|
||||||
OfferPayload offerPayload = (OfferPayload) protectedStorageEntry.getProtectedStoragePayload();
|
OfferPayloadBase offerPayloadBase = (OfferPayloadBase) protectedStorageEntry.getProtectedStoragePayload();
|
||||||
Offer offer = new Offer(offerPayload);
|
Offer offer = new Offer(offerPayloadBase);
|
||||||
offer.setPriceFeedService(priceFeedService);
|
offer.setPriceFeedService(priceFeedService);
|
||||||
listener.onAdded(offer);
|
listener.onAdded(offer);
|
||||||
}
|
}
|
||||||
|
@ -99,9 +99,9 @@ public class OfferBookService {
|
||||||
@Override
|
@Override
|
||||||
public void onRemoved(Collection<ProtectedStorageEntry> protectedStorageEntries) {
|
public void onRemoved(Collection<ProtectedStorageEntry> protectedStorageEntries) {
|
||||||
protectedStorageEntries.forEach(protectedStorageEntry -> offerBookChangedListeners.forEach(listener -> {
|
protectedStorageEntries.forEach(protectedStorageEntry -> offerBookChangedListeners.forEach(listener -> {
|
||||||
if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayload) {
|
if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayloadBase) {
|
||||||
OfferPayload offerPayload = (OfferPayload) protectedStorageEntry.getProtectedStoragePayload();
|
OfferPayloadBase offerPayloadBase = (OfferPayloadBase) protectedStorageEntry.getProtectedStoragePayload();
|
||||||
Offer offer = new Offer(offerPayload);
|
Offer offer = new Offer(offerPayloadBase);
|
||||||
offer.setPriceFeedService(priceFeedService);
|
offer.setPriceFeedService(priceFeedService);
|
||||||
listener.onRemoved(offer);
|
listener.onRemoved(offer);
|
||||||
}
|
}
|
||||||
|
@ -141,7 +141,7 @@ public class OfferBookService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean result = p2PService.addProtectedStorageEntry(offer.getOfferPayload());
|
boolean result = p2PService.addProtectedStorageEntry(offer.getOfferPayloadBase());
|
||||||
if (result) {
|
if (result) {
|
||||||
resultHandler.handleResult();
|
resultHandler.handleResult();
|
||||||
} else {
|
} else {
|
||||||
|
@ -149,7 +149,7 @@ public class OfferBookService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void refreshTTL(OfferPayload offerPayload,
|
public void refreshTTL(OfferPayloadBase offerPayloadBase,
|
||||||
ResultHandler resultHandler,
|
ResultHandler resultHandler,
|
||||||
ErrorMessageHandler errorMessageHandler) {
|
ErrorMessageHandler errorMessageHandler) {
|
||||||
if (filterManager.requireUpdateToNewVersionForTrading()) {
|
if (filterManager.requireUpdateToNewVersionForTrading()) {
|
||||||
|
@ -157,7 +157,7 @@ public class OfferBookService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean result = p2PService.refreshTTL(offerPayload);
|
boolean result = p2PService.refreshTTL(offerPayloadBase);
|
||||||
if (result) {
|
if (result) {
|
||||||
resultHandler.handleResult();
|
resultHandler.handleResult();
|
||||||
} else {
|
} else {
|
||||||
|
@ -171,16 +171,16 @@ public class OfferBookService {
|
||||||
addOffer(offer, resultHandler, errorMessageHandler);
|
addOffer(offer, resultHandler, errorMessageHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deactivateOffer(OfferPayload offerPayload,
|
public void deactivateOffer(OfferPayloadBase offerPayloadBase,
|
||||||
@Nullable ResultHandler resultHandler,
|
@Nullable ResultHandler resultHandler,
|
||||||
@Nullable ErrorMessageHandler errorMessageHandler) {
|
@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 ResultHandler resultHandler,
|
||||||
@Nullable ErrorMessageHandler errorMessageHandler) {
|
@Nullable ErrorMessageHandler errorMessageHandler) {
|
||||||
if (p2PService.removeData(offerPayload)) {
|
if (p2PService.removeData(offerPayloadBase)) {
|
||||||
if (resultHandler != null)
|
if (resultHandler != null)
|
||||||
resultHandler.handleResult();
|
resultHandler.handleResult();
|
||||||
} else {
|
} else {
|
||||||
|
@ -191,18 +191,18 @@ public class OfferBookService {
|
||||||
|
|
||||||
public List<Offer> getOffers() {
|
public List<Offer> getOffers() {
|
||||||
return p2PService.getDataMap().values().stream()
|
return p2PService.getDataMap().values().stream()
|
||||||
.filter(data -> data.getProtectedStoragePayload() instanceof OfferPayload)
|
.filter(data -> data.getProtectedStoragePayload() instanceof OfferPayloadBase)
|
||||||
.map(data -> {
|
.map(data -> {
|
||||||
OfferPayload offerPayload = (OfferPayload) data.getProtectedStoragePayload();
|
OfferPayloadBase offerPayloadBase = (OfferPayloadBase) data.getProtectedStoragePayload();
|
||||||
Offer offer = new Offer(offerPayload);
|
Offer offer = new Offer(offerPayloadBase);
|
||||||
offer.setPriceFeedService(priceFeedService);
|
offer.setPriceFeedService(priceFeedService);
|
||||||
return offer;
|
return offer;
|
||||||
})
|
})
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeOfferAtShutDown(OfferPayload offerPayload) {
|
public void removeOfferAtShutDown(OfferPayloadBase offerPayloadBase) {
|
||||||
removeOffer(offerPayload, null, null);
|
removeOffer(offerPayloadBase, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isBootstrapped() {
|
public boolean isBootstrapped() {
|
||||||
|
@ -243,6 +243,6 @@ public class OfferBookService {
|
||||||
})
|
})
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
jsonFileManager.writeToDiscThreaded(Utilities.objectToJson(offerForJsonList), "offers_statistics");
|
jsonFileManager.writeToDiscThreaded(JsonUtil.objectToJson(offerForJsonList), "offers_statistics");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
33
core/src/main/java/bisq/core/offer/OfferDirection.java
Normal file
33
core/src/main/java/bisq/core/offer/OfferDirection.java
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,6 +25,7 @@ import bisq.core.payment.PaymentAccountUtil;
|
||||||
import bisq.core.user.Preferences;
|
import bisq.core.user.Preferences;
|
||||||
import bisq.core.user.User;
|
import bisq.core.user.User;
|
||||||
|
|
||||||
|
import bisq.common.app.DevEnv;
|
||||||
import bisq.common.app.Version;
|
import bisq.common.app.Version;
|
||||||
|
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.Coin;
|
||||||
|
@ -43,7 +44,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Singleton
|
@Singleton
|
||||||
public class OfferFilter {
|
public class OfferFilterService {
|
||||||
private final User user;
|
private final User user;
|
||||||
private final Preferences preferences;
|
private final Preferences preferences;
|
||||||
private final FilterManager filterManager;
|
private final FilterManager filterManager;
|
||||||
|
@ -52,7 +53,7 @@ public class OfferFilter {
|
||||||
private final Map<String, Boolean> myInsufficientTradeLimitCache = new HashMap<>();
|
private final Map<String, Boolean> myInsufficientTradeLimitCache = new HashMap<>();
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public OfferFilter(User user,
|
public OfferFilterService(User user,
|
||||||
Preferences preferences,
|
Preferences preferences,
|
||||||
FilterManager filterManager,
|
FilterManager filterManager,
|
||||||
AccountAgeWitnessService accountAgeWitnessService) {
|
AccountAgeWitnessService accountAgeWitnessService) {
|
||||||
|
@ -80,7 +81,8 @@ public class OfferFilter {
|
||||||
IS_NODE_ADDRESS_BANNED,
|
IS_NODE_ADDRESS_BANNED,
|
||||||
REQUIRE_UPDATE_TO_NEW_VERSION,
|
REQUIRE_UPDATE_TO_NEW_VERSION,
|
||||||
IS_INSUFFICIENT_COUNTERPARTY_TRADE_LIMIT,
|
IS_INSUFFICIENT_COUNTERPARTY_TRADE_LIMIT,
|
||||||
IS_MY_INSUFFICIENT_TRADE_LIMIT;
|
IS_MY_INSUFFICIENT_TRADE_LIMIT,
|
||||||
|
HIDE_BSQ_SWAPS_DUE_DAO_DEACTIVATED;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final boolean isValid;
|
private final boolean isValid;
|
||||||
|
@ -128,6 +130,9 @@ public class OfferFilter {
|
||||||
if (isMyInsufficientTradeLimit(offer)) {
|
if (isMyInsufficientTradeLimit(offer)) {
|
||||||
return Result.IS_MY_INSUFFICIENT_TRADE_LIMIT;
|
return Result.IS_MY_INSUFFICIENT_TRADE_LIMIT;
|
||||||
}
|
}
|
||||||
|
if (!DevEnv.isDaoActivated() && offer.isBsqSwapOffer()) {
|
||||||
|
return Result.HIDE_BSQ_SWAPS_DUE_DAO_DEACTIVATED;
|
||||||
|
}
|
||||||
|
|
||||||
return Result.VALID;
|
return Result.VALID;
|
||||||
}
|
}
|
|
@ -40,7 +40,7 @@ import javax.annotation.Nullable;
|
||||||
public class OfferForJson {
|
public class OfferForJson {
|
||||||
private static final Logger log = LoggerFactory.getLogger(OfferForJson.class);
|
private static final Logger log = LoggerFactory.getLogger(OfferForJson.class);
|
||||||
|
|
||||||
public final OfferPayload.Direction direction;
|
public final OfferDirection direction;
|
||||||
public final String currencyCode;
|
public final String currencyCode;
|
||||||
public final long minAmount;
|
public final long minAmount;
|
||||||
public final long amount;
|
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)
|
// 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 String currencyPair;
|
||||||
public OfferPayload.Direction primaryMarketDirection;
|
public OfferDirection primaryMarketDirection;
|
||||||
|
|
||||||
public String priceDisplayString;
|
public String priceDisplayString;
|
||||||
public String primaryMarketAmountDisplayString;
|
public String primaryMarketAmountDisplayString;
|
||||||
|
@ -75,7 +75,7 @@ public class OfferForJson {
|
||||||
transient private final MonetaryFormat coinFormat = MonetaryFormat.BTC;
|
transient private final MonetaryFormat coinFormat = MonetaryFormat.BTC;
|
||||||
|
|
||||||
|
|
||||||
public OfferForJson(OfferPayload.Direction direction,
|
public OfferForJson(OfferDirection direction,
|
||||||
String currencyCode,
|
String currencyCode,
|
||||||
Coin minAmount,
|
Coin minAmount,
|
||||||
Coin amount,
|
Coin amount,
|
||||||
|
@ -104,7 +104,7 @@ public class OfferForJson {
|
||||||
try {
|
try {
|
||||||
final Price price = getPrice();
|
final Price price = getPrice();
|
||||||
if (CurrencyUtil.isCryptoCurrency(currencyCode)) {
|
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();
|
currencyPair = currencyCode + "/" + Res.getBaseCurrencyCode();
|
||||||
|
|
||||||
// int precision = 8;
|
// int precision = 8;
|
||||||
|
|
147
core/src/main/java/bisq/core/offer/OfferPayloadBase.java
Normal file
147
core/src/main/java/bisq/core/offer/OfferPayloadBase.java
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<String, String> 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<String, String> 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}";
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,8 @@
|
||||||
|
|
||||||
package bisq.core.offer;
|
package bisq.core.offer;
|
||||||
|
|
||||||
|
import bisq.core.offer.bisq_v1.OfferPayload;
|
||||||
|
|
||||||
import bisq.common.app.Capabilities;
|
import bisq.common.app.Capabilities;
|
||||||
import bisq.common.app.Capability;
|
import bisq.common.app.Capability;
|
||||||
import bisq.common.config.Config;
|
import bisq.common.config.Config;
|
||||||
|
@ -40,7 +42,7 @@ public class OfferRestrictions {
|
||||||
public static Coin TOLERATED_SMALL_TRADE_AMOUNT = Coin.parseCoin("0.01");
|
public static Coin TOLERATED_SMALL_TRADE_AMOUNT = Coin.parseCoin("0.01");
|
||||||
|
|
||||||
static boolean hasOfferMandatoryCapability(Offer offer, Capability mandatoryCapability) {
|
static boolean hasOfferMandatoryCapability(Offer offer, Capability mandatoryCapability) {
|
||||||
Map<String, String> extraDataMap = offer.getOfferPayload().getExtraDataMap();
|
Map<String, String> extraDataMap = offer.getExtraDataMap();
|
||||||
if (extraDataMap != null && extraDataMap.containsKey(OfferPayload.CAPABILITIES)) {
|
if (extraDataMap != null && extraDataMap.containsKey(OfferPayload.CAPABILITIES)) {
|
||||||
String commaSeparatedOrdinals = extraDataMap.get(OfferPayload.CAPABILITIES);
|
String commaSeparatedOrdinals = extraDataMap.get(OfferPayload.CAPABILITIES);
|
||||||
Capabilities capabilities = Capabilities.fromStringList(commaSeparatedOrdinals);
|
Capabilities capabilities = Capabilities.fromStringList(commaSeparatedOrdinals);
|
||||||
|
|
|
@ -25,9 +25,12 @@ import bisq.core.locale.CurrencyUtil;
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
import bisq.core.monetary.Price;
|
import bisq.core.monetary.Price;
|
||||||
import bisq.core.monetary.Volume;
|
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.CashByMailAccount;
|
||||||
import bisq.core.payment.F2FAccount;
|
import bisq.core.payment.F2FAccount;
|
||||||
import bisq.core.payment.PaymentAccount;
|
import bisq.core.payment.PaymentAccount;
|
||||||
|
import bisq.core.payment.payload.PaymentMethod;
|
||||||
import bisq.core.provider.fee.FeeService;
|
import bisq.core.provider.fee.FeeService;
|
||||||
import bisq.core.provider.price.MarketPrice;
|
import bisq.core.provider.price.MarketPrice;
|
||||||
import bisq.core.provider.price.PriceFeedService;
|
import bisq.core.provider.price.PriceFeedService;
|
||||||
|
@ -42,8 +45,10 @@ import bisq.core.util.coin.CoinUtil;
|
||||||
import bisq.network.p2p.P2PService;
|
import bisq.network.p2p.P2PService;
|
||||||
|
|
||||||
import bisq.common.app.Capabilities;
|
import bisq.common.app.Capabilities;
|
||||||
|
import bisq.common.app.Version;
|
||||||
import bisq.common.util.MathUtils;
|
import bisq.common.util.MathUtils;
|
||||||
import bisq.common.util.Tuple2;
|
import bisq.common.util.Tuple2;
|
||||||
|
import bisq.common.util.Utilities;
|
||||||
|
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.Coin;
|
||||||
import org.bitcoinj.core.Transaction;
|
import org.bitcoinj.core.Transaction;
|
||||||
|
@ -57,6 +62,7 @@ import javax.inject.Singleton;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
@ -69,7 +75,7 @@ import static bisq.core.btc.wallet.Restrictions.getMaxBuyerSecurityDepositAsPerc
|
||||||
import static bisq.core.btc.wallet.Restrictions.getMinBuyerSecurityDepositAsPercent;
|
import static bisq.core.btc.wallet.Restrictions.getMinBuyerSecurityDepositAsPercent;
|
||||||
import static bisq.core.btc.wallet.Restrictions.getMinNonDustOutput;
|
import static bisq.core.btc.wallet.Restrictions.getMinNonDustOutput;
|
||||||
import static bisq.core.btc.wallet.Restrictions.isDust;
|
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.checkArgument;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static java.lang.String.format;
|
import static java.lang.String.format;
|
||||||
|
@ -112,6 +118,40 @@ public class OfferUtil {
|
||||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
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) {
|
public void maybeSetFeePaymentCurrencyPreference(String feeCurrencyCode) {
|
||||||
if (!feeCurrencyCode.isEmpty()) {
|
if (!feeCurrencyCode.isEmpty()) {
|
||||||
if (!isValidFeePaymentCurrencyCode.test(feeCurrencyCode))
|
if (!isValidFeePaymentCurrencyCode.test(feeCurrencyCode))
|
||||||
|
@ -132,13 +172,13 @@ public class OfferUtil {
|
||||||
* @return {@code true} for an offer to buy BTC from the taker, {@code false} for an
|
* @return {@code true} for an offer to buy BTC from the taker, {@code false} for an
|
||||||
* offer to sell BTC to the taker
|
* offer to sell BTC to the taker
|
||||||
*/
|
*/
|
||||||
public boolean isBuyOffer(Direction direction) {
|
public boolean isBuyOffer(OfferDirection direction) {
|
||||||
return direction == Direction.BUY;
|
return direction == OfferDirection.BUY;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getMaxTradeLimit(PaymentAccount paymentAccount,
|
public long getMaxTradeLimit(PaymentAccount paymentAccount,
|
||||||
String currencyCode,
|
String currencyCode,
|
||||||
Direction direction) {
|
OfferDirection direction) {
|
||||||
return paymentAccount != null
|
return paymentAccount != null
|
||||||
? accountAgeWitnessService.getMyTradeLimit(paymentAccount, currencyCode, direction)
|
? accountAgeWitnessService.getMyTradeLimit(paymentAccount, currencyCode, direction)
|
||||||
: 0;
|
: 0;
|
||||||
|
@ -181,7 +221,7 @@ public class OfferUtil {
|
||||||
// We have to keep a minimum amount of BSQ == bitcoin dust limit, otherwise there
|
// 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
|
// would be dust violations for change UTXOs; essentially means the minimum usable
|
||||||
// balance of BSQ is 5.46.
|
// balance of BSQ is 5.46.
|
||||||
Coin usableBsqBalance = bsqWalletService.getAvailableConfirmedBalance().subtract(getMinNonDustOutput());
|
Coin usableBsqBalance = bsqWalletService.getAvailableBalance().subtract(getMinNonDustOutput());
|
||||||
return usableBsqBalance.isNegative() ? Coin.ZERO : usableBsqBalance;
|
return usableBsqBalance.isNegative() ? Coin.ZERO : usableBsqBalance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,7 +280,7 @@ public class OfferUtil {
|
||||||
* @return {@code true} if the balance is sufficient, {@code false} otherwise
|
* @return {@code true} if the balance is sufficient, {@code false} otherwise
|
||||||
*/
|
*/
|
||||||
public boolean isBsqForMakerFeeAvailable(@Nullable Coin amount) {
|
public boolean isBsqForMakerFeeAvailable(@Nullable Coin amount) {
|
||||||
Coin availableBalance = bsqWalletService.getAvailableConfirmedBalance();
|
Coin availableBalance = bsqWalletService.getAvailableBalance();
|
||||||
Coin makerFee = CoinUtil.getMakerFee(false, amount);
|
Coin makerFee = CoinUtil.getMakerFee(false, amount);
|
||||||
|
|
||||||
// If we don't know yet the maker fee (amount is not set) we return true,
|
// If we don't know yet the maker fee (amount is not set) we return true,
|
||||||
|
@ -274,7 +314,7 @@ public class OfferUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isBsqForTakerFeeAvailable(@Nullable Coin amount) {
|
public boolean isBsqForTakerFeeAvailable(@Nullable Coin amount) {
|
||||||
Coin availableBalance = bsqWalletService.getAvailableConfirmedBalance();
|
Coin availableBalance = bsqWalletService.getAvailableBalance();
|
||||||
Coin takerFee = getTakerFee(false, amount);
|
Coin takerFee = getTakerFee(false, amount);
|
||||||
|
|
||||||
// If we don't know yet the maker fee (amount is not set) we return true,
|
// If we don't know yet the maker fee (amount is not set) we return true,
|
||||||
|
@ -291,7 +331,7 @@ public class OfferUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isBlockChainPaymentMethod(Offer offer) {
|
public boolean isBlockChainPaymentMethod(Offer offer) {
|
||||||
return offer != null && offer.getPaymentMethod().isAsset();
|
return offer != null && offer.getPaymentMethod().isBlockchain();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<Volume> getFeeInUserFiatCurrency(Coin makerFee,
|
public Optional<Volume> getFeeInUserFiatCurrency(Coin makerFee,
|
||||||
|
@ -313,7 +353,7 @@ public class OfferUtil {
|
||||||
|
|
||||||
public Map<String, String> getExtraDataMap(PaymentAccount paymentAccount,
|
public Map<String, String> getExtraDataMap(PaymentAccount paymentAccount,
|
||||||
String currencyCode,
|
String currencyCode,
|
||||||
Direction direction) {
|
OfferDirection direction) {
|
||||||
Map<String, String> extraDataMap = new HashMap<>();
|
Map<String, String> extraDataMap = new HashMap<>();
|
||||||
if (CurrencyUtil.isFiatCurrency(currencyCode)) {
|
if (CurrencyUtil.isFiatCurrency(currencyCode)) {
|
||||||
String myWitnessHashAsHex = accountAgeWitnessService
|
String myWitnessHashAsHex = accountAgeWitnessService
|
||||||
|
@ -336,7 +376,7 @@ public class OfferUtil {
|
||||||
|
|
||||||
extraDataMap.put(CAPABILITIES, Capabilities.app.toStringList());
|
extraDataMap.put(CAPABILITIES, Capabilities.app.toStringList());
|
||||||
|
|
||||||
if (currencyCode.equals("XMR") && direction == Direction.SELL) {
|
if (currencyCode.equals("XMR") && direction == OfferDirection.SELL) {
|
||||||
preferences.getAutoConfirmSettingsList().stream()
|
preferences.getAutoConfirmSettingsList().stream()
|
||||||
.filter(e -> e.getCurrencyCode().equals("XMR"))
|
.filter(e -> e.getCurrencyCode().equals("XMR"))
|
||||||
.filter(AutoConfirmSettings::isEnabled)
|
.filter(AutoConfirmSettings::isEnabled)
|
||||||
|
@ -350,17 +390,21 @@ public class OfferUtil {
|
||||||
PaymentAccount paymentAccount,
|
PaymentAccount paymentAccount,
|
||||||
String currencyCode,
|
String currencyCode,
|
||||||
Coin makerFeeAsCoin) {
|
Coin makerFeeAsCoin) {
|
||||||
|
validateBasicOfferData(paymentAccount.getPaymentMethod(), currencyCode);
|
||||||
checkNotNull(makerFeeAsCoin, "makerFee must not be null");
|
checkNotNull(makerFeeAsCoin, "makerFee must not be null");
|
||||||
checkNotNull(p2PService.getAddress(), "Address must not be null");
|
|
||||||
checkArgument(buyerSecurityDeposit <= getMaxBuyerSecurityDepositAsPercent(),
|
checkArgument(buyerSecurityDeposit <= getMaxBuyerSecurityDepositAsPercent(),
|
||||||
"securityDeposit must not exceed " +
|
"securityDeposit must not exceed " +
|
||||||
getMaxBuyerSecurityDepositAsPercent());
|
getMaxBuyerSecurityDepositAsPercent());
|
||||||
checkArgument(buyerSecurityDeposit >= getMinBuyerSecurityDepositAsPercent(),
|
checkArgument(buyerSecurityDeposit >= getMinBuyerSecurityDepositAsPercent(),
|
||||||
"securityDeposit must not be less than " +
|
"securityDeposit must not be less than " +
|
||||||
getMinBuyerSecurityDepositAsPercent());
|
getMinBuyerSecurityDepositAsPercent());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validateBasicOfferData(PaymentMethod paymentMethod, String currencyCode) {
|
||||||
|
checkNotNull(p2PService.getAddress(), "Address must not be null");
|
||||||
checkArgument(!filterManager.isCurrencyBanned(currencyCode),
|
checkArgument(!filterManager.isCurrencyBanned(currencyCode),
|
||||||
Res.get("offerbook.warning.currencyBanned"));
|
Res.get("offerbook.warning.currencyBanned"));
|
||||||
checkArgument(!filterManager.isPaymentMethodBanned(paymentAccount.getPaymentMethod()),
|
checkArgument(!filterManager.isPaymentMethodBanned(paymentMethod),
|
||||||
Res.get("offerbook.warning.paymentMethodBanned"));
|
Res.get("offerbook.warning.paymentMethodBanned"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,45 +414,45 @@ public class OfferUtil {
|
||||||
// Immutable fields are sourced from the original openOffer param.
|
// Immutable fields are sourced from the original openOffer param.
|
||||||
public OfferPayload getMergedOfferPayload(OpenOffer openOffer,
|
public OfferPayload getMergedOfferPayload(OpenOffer openOffer,
|
||||||
MutableOfferPayloadFields mutableOfferPayloadFields) {
|
MutableOfferPayloadFields mutableOfferPayloadFields) {
|
||||||
OfferPayload originalOfferPayload = openOffer.getOffer().getOfferPayload();
|
OfferPayload original = openOffer.getOffer().getOfferPayload().orElseThrow();
|
||||||
return new OfferPayload(originalOfferPayload.getId(),
|
return new OfferPayload(original.getId(),
|
||||||
originalOfferPayload.getDate(),
|
original.getDate(),
|
||||||
originalOfferPayload.getOwnerNodeAddress(),
|
original.getOwnerNodeAddress(),
|
||||||
originalOfferPayload.getPubKeyRing(),
|
original.getPubKeyRing(),
|
||||||
originalOfferPayload.getDirection(),
|
original.getDirection(),
|
||||||
mutableOfferPayloadFields.getPrice(),
|
mutableOfferPayloadFields.getPrice(),
|
||||||
mutableOfferPayloadFields.getMarketPriceMargin(),
|
mutableOfferPayloadFields.getMarketPriceMargin(),
|
||||||
mutableOfferPayloadFields.isUseMarketBasedPrice(),
|
mutableOfferPayloadFields.isUseMarketBasedPrice(),
|
||||||
originalOfferPayload.getAmount(),
|
original.getAmount(),
|
||||||
originalOfferPayload.getMinAmount(),
|
original.getMinAmount(),
|
||||||
mutableOfferPayloadFields.getBaseCurrencyCode(),
|
mutableOfferPayloadFields.getBaseCurrencyCode(),
|
||||||
mutableOfferPayloadFields.getCounterCurrencyCode(),
|
mutableOfferPayloadFields.getCounterCurrencyCode(),
|
||||||
originalOfferPayload.getArbitratorNodeAddresses(),
|
original.getArbitratorNodeAddresses(),
|
||||||
originalOfferPayload.getMediatorNodeAddresses(),
|
original.getMediatorNodeAddresses(),
|
||||||
mutableOfferPayloadFields.getPaymentMethodId(),
|
mutableOfferPayloadFields.getPaymentMethodId(),
|
||||||
mutableOfferPayloadFields.getMakerPaymentAccountId(),
|
mutableOfferPayloadFields.getMakerPaymentAccountId(),
|
||||||
originalOfferPayload.getOfferFeePaymentTxId(),
|
original.getOfferFeePaymentTxId(),
|
||||||
mutableOfferPayloadFields.getCountryCode(),
|
mutableOfferPayloadFields.getCountryCode(),
|
||||||
mutableOfferPayloadFields.getAcceptedCountryCodes(),
|
mutableOfferPayloadFields.getAcceptedCountryCodes(),
|
||||||
mutableOfferPayloadFields.getBankId(),
|
mutableOfferPayloadFields.getBankId(),
|
||||||
mutableOfferPayloadFields.getAcceptedBankIds(),
|
mutableOfferPayloadFields.getAcceptedBankIds(),
|
||||||
originalOfferPayload.getVersionNr(),
|
original.getVersionNr(),
|
||||||
originalOfferPayload.getBlockHeightAtOfferCreation(),
|
original.getBlockHeightAtOfferCreation(),
|
||||||
originalOfferPayload.getTxFee(),
|
original.getTxFee(),
|
||||||
originalOfferPayload.getMakerFee(),
|
original.getMakerFee(),
|
||||||
originalOfferPayload.isCurrencyForMakerFeeBtc(),
|
original.isCurrencyForMakerFeeBtc(),
|
||||||
originalOfferPayload.getBuyerSecurityDeposit(),
|
original.getBuyerSecurityDeposit(),
|
||||||
originalOfferPayload.getSellerSecurityDeposit(),
|
original.getSellerSecurityDeposit(),
|
||||||
originalOfferPayload.getMaxTradeLimit(),
|
original.getMaxTradeLimit(),
|
||||||
originalOfferPayload.getMaxTradePeriod(),
|
original.getMaxTradePeriod(),
|
||||||
originalOfferPayload.isUseAutoClose(),
|
original.isUseAutoClose(),
|
||||||
originalOfferPayload.isUseReOpenAfterAutoClose(),
|
original.isUseReOpenAfterAutoClose(),
|
||||||
originalOfferPayload.getLowerClosePrice(),
|
original.getLowerClosePrice(),
|
||||||
originalOfferPayload.getUpperClosePrice(),
|
original.getUpperClosePrice(),
|
||||||
originalOfferPayload.isPrivateOffer(),
|
original.isPrivateOffer(),
|
||||||
originalOfferPayload.getHashOfChallenge(),
|
original.getHashOfChallenge(),
|
||||||
mutableOfferPayloadFields.getExtraDataMap(),
|
mutableOfferPayloadFields.getExtraDataMap(),
|
||||||
originalOfferPayload.getProtocolVersion());
|
original.getProtocolVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<Volume> getFeeInUserFiatCurrency(Coin makerFee,
|
private Optional<Volume> getFeeInUserFiatCurrency(Coin makerFee,
|
||||||
|
@ -482,7 +526,7 @@ public class OfferUtil {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errorMsg = "The maker fee tx is invalid as it does not has at least 2 outputs." + extraString +
|
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) {
|
if (errorMsg == null) {
|
||||||
|
|
|
@ -17,7 +17,8 @@
|
||||||
|
|
||||||
package bisq.core.offer;
|
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;
|
import bisq.network.p2p.NodeAddress;
|
||||||
|
|
||||||
|
@ -35,12 +36,13 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@EqualsAndHashCode
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
|
||||||
|
@EqualsAndHashCode(exclude = {"bsqSwapOfferHasMissingFunds", "state", "timeoutTimer", "mempoolStatus"})
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public final class OpenOffer implements Tradable {
|
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.
|
// 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;
|
private static final long TIMEOUT = 60;
|
||||||
transient private Timer timeoutTimer;
|
|
||||||
|
|
||||||
public enum State {
|
public enum State {
|
||||||
AVAILABLE,
|
AVAILABLE,
|
||||||
|
@ -54,10 +56,11 @@ public final class OpenOffer implements Tradable {
|
||||||
private final Offer offer;
|
private final Offer offer;
|
||||||
@Getter
|
@Getter
|
||||||
private State state;
|
private State state;
|
||||||
|
|
||||||
|
// TODO Not used. Could be removed?
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private NodeAddress arbitratorNodeAddress;
|
private final NodeAddress arbitratorNodeAddress;
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -76,15 +79,29 @@ public final class OpenOffer implements Tradable {
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
transient private long mempoolStatus = -1;
|
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) {
|
public OpenOffer(Offer offer) {
|
||||||
this(offer, 0);
|
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) {
|
public OpenOffer(Offer offer, long triggerPrice) {
|
||||||
this.offer = offer;
|
this.offer = offer;
|
||||||
this.triggerPrice = triggerPrice;
|
this.triggerPrice = triggerPrice;
|
||||||
state = State.AVAILABLE;
|
state = State.AVAILABLE;
|
||||||
|
arbitratorNodeAddress = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -131,9 +148,8 @@ public final class OpenOffer implements Tradable {
|
||||||
proto.getTriggerPrice());
|
proto.getTriggerPrice());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Getters
|
// Tradable
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -151,6 +167,11 @@ public final class OpenOffer implements Tradable {
|
||||||
return offer.getShortId();
|
return offer.getShortId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Misc
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public void setState(State state) {
|
public void setState(State state) {
|
||||||
this.state = state;
|
this.state = state;
|
||||||
|
|
||||||
|
@ -166,6 +187,12 @@ public final class OpenOffer implements Tradable {
|
||||||
return state == State.DEACTIVATED;
|
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() {
|
private void startTimeout() {
|
||||||
stopTimeout();
|
stopTimeout();
|
||||||
|
|
||||||
|
@ -185,7 +212,6 @@ public final class OpenOffer implements Tradable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "OpenOffer{" +
|
return "OpenOffer{" +
|
||||||
|
@ -195,6 +221,7 @@ public final class OpenOffer implements Tradable {
|
||||||
",\n mediatorNodeAddress=" + mediatorNodeAddress +
|
",\n mediatorNodeAddress=" + mediatorNodeAddress +
|
||||||
",\n refundAgentNodeAddress=" + refundAgentNodeAddress +
|
",\n refundAgentNodeAddress=" + refundAgentNodeAddress +
|
||||||
",\n triggerPrice=" + triggerPrice +
|
",\n triggerPrice=" + triggerPrice +
|
||||||
|
",\n bsqSwapOfferHasMissingFunds=" + bsqSwapOfferHasMissingFunds +
|
||||||
"\n}";
|
"\n}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,18 +25,22 @@ import bisq.core.dao.DaoFacade;
|
||||||
import bisq.core.exceptions.TradePriceOutOfToleranceException;
|
import bisq.core.exceptions.TradePriceOutOfToleranceException;
|
||||||
import bisq.core.filter.FilterManager;
|
import bisq.core.filter.FilterManager;
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
|
import bisq.core.offer.availability.AvailabilityResult;
|
||||||
import bisq.core.offer.availability.DisputeAgentSelection;
|
import bisq.core.offer.availability.DisputeAgentSelection;
|
||||||
import bisq.core.offer.messages.OfferAvailabilityRequest;
|
import bisq.core.offer.availability.messages.OfferAvailabilityRequest;
|
||||||
import bisq.core.offer.messages.OfferAvailabilityResponse;
|
import bisq.core.offer.availability.messages.OfferAvailabilityResponse;
|
||||||
import bisq.core.offer.placeoffer.PlaceOfferModel;
|
import bisq.core.offer.bisq_v1.CreateOfferService;
|
||||||
import bisq.core.offer.placeoffer.PlaceOfferProtocol;
|
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.provider.price.PriceFeedService;
|
||||||
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
||||||
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
|
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
|
||||||
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
|
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
|
||||||
import bisq.core.trade.TradableList;
|
import bisq.core.trade.ClosedTradableManager;
|
||||||
import bisq.core.trade.closed.ClosedTradableManager;
|
import bisq.core.trade.bisq_v1.TransactionResultHandler;
|
||||||
import bisq.core.trade.handlers.TransactionResultHandler;
|
import bisq.core.trade.model.TradableList;
|
||||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||||
import bisq.core.user.Preferences;
|
import bisq.core.user.Preferences;
|
||||||
import bisq.core.user.User;
|
import bisq.core.user.User;
|
||||||
|
@ -84,19 +88,16 @@ import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
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.checkNotNull;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMessageListener, PersistedDataHost {
|
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 RETRY_REPUBLISH_DELAY_SEC = 10;
|
||||||
private static final long REPUBLISH_AGAIN_AT_STARTUP_DELAY_SEC = 30;
|
private static final long REPUBLISH_AGAIN_AT_STARTUP_DELAY_SEC = 30;
|
||||||
|
@ -207,7 +208,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
cleanUpAddressEntries();
|
cleanUpAddressEntries();
|
||||||
|
|
||||||
openOffers.stream()
|
openOffers.stream()
|
||||||
.forEach(openOffer -> OfferUtil.getInvalidMakerFeeTxErrorMessage(openOffer.getOffer(), btcWalletService)
|
.forEach(openOffer ->
|
||||||
|
OfferUtil.getInvalidMakerFeeTxErrorMessage(openOffer.getOffer(), btcWalletService)
|
||||||
.ifPresent(errorMsg -> invalidOffers.add(new Tuple2<>(openOffer, errorMsg))));
|
.ifPresent(errorMsg -> invalidOffers.add(new Tuple2<>(openOffer, errorMsg))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,7 +240,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
log.info("Remove open offers at shutDown. Number of open offers: {}", size);
|
log.info("Remove open offers at shutDown. Number of open offers: {}", size);
|
||||||
if (offerBookService.isBootstrapped() && size > 0) {
|
if (offerBookService.isBootstrapped() && size > 0) {
|
||||||
UserThread.execute(() -> openOffers.forEach(
|
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
|
// Force broadcaster to send out immediately, otherwise we could have a 2 sec delay until the
|
||||||
|
@ -265,11 +267,9 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
int size = openOffers.size();
|
int size = openOffers.size();
|
||||||
// Copy list as we remove in the loop
|
// Copy list as we remove in the loop
|
||||||
List<OpenOffer> openOffersList = new ArrayList<>(openOffers);
|
List<OpenOffer> openOffersList = new ArrayList<>(openOffers);
|
||||||
openOffersList.forEach(openOffer -> removeOpenOffer(openOffer, () -> {
|
openOffersList.forEach(this::removeOpenOffer);
|
||||||
}, errorMessage -> {
|
|
||||||
}));
|
|
||||||
if (completeHandler != null)
|
if (completeHandler != null)
|
||||||
UserThread.runAfter(completeHandler, size * 200 + 500, TimeUnit.MILLISECONDS);
|
UserThread.runAfter(completeHandler, size * 200L + 500, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -372,6 +372,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
TransactionResultHandler resultHandler,
|
TransactionResultHandler resultHandler,
|
||||||
ErrorMessageHandler errorMessageHandler) {
|
ErrorMessageHandler errorMessageHandler) {
|
||||||
checkNotNull(offer.getMakerFee(), "makerFee must not be null");
|
checkNotNull(offer.getMakerFee(), "makerFee must not be null");
|
||||||
|
checkArgument(!offer.isBsqSwapOffer());
|
||||||
|
|
||||||
Coin reservedFundsForOffer = createOfferService.getReservedFundsForOffer(offer.getDirection(),
|
Coin reservedFundsForOffer = createOfferService.getReservedFundsForOffer(offer.getDirection(),
|
||||||
offer.getAmount(),
|
offer.getAmount(),
|
||||||
|
@ -394,21 +395,30 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
model,
|
model,
|
||||||
transaction -> {
|
transaction -> {
|
||||||
OpenOffer openOffer = new OpenOffer(offer, triggerPrice);
|
OpenOffer openOffer = new OpenOffer(offer, triggerPrice);
|
||||||
openOffers.add(openOffer);
|
addOpenOfferToList(openOffer);
|
||||||
requestPersistence();
|
|
||||||
resultHandler.handleResult(transaction);
|
|
||||||
if (!stopped) {
|
if (!stopped) {
|
||||||
startPeriodicRepublishOffersTimer();
|
startPeriodicRepublishOffersTimer();
|
||||||
startPeriodicRefreshOffersTimer();
|
startPeriodicRefreshOffersTimer();
|
||||||
} else {
|
} else {
|
||||||
log.debug("We have stopped already. We ignore that placeOfferProtocol.placeOffer.onResult call.");
|
log.debug("We have stopped already. We ignore that placeOfferProtocol.placeOffer.onResult call.");
|
||||||
}
|
}
|
||||||
|
resultHandler.handleResult(transaction);
|
||||||
},
|
},
|
||||||
errorMessageHandler
|
errorMessageHandler
|
||||||
);
|
);
|
||||||
placeOfferProtocol.placeOffer();
|
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
|
// Remove from offerbook
|
||||||
public void removeOffer(Offer offer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
public void removeOffer(Offer offer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||||
Optional<OpenOffer> openOfferOptional = getOpenOfferById(offer.getId());
|
Optional<OpenOffer> openOfferOptional = getOpenOfferById(offer.getId());
|
||||||
|
@ -418,7 +428,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
log.warn("Offer was not found in our list of open offers. We still try to remove it from the offerbook.");
|
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. " +
|
errorMessageHandler.handleErrorMessage("Offer was not found in our list of open offers. " +
|
||||||
"We still try to remove it from the offerbook.");
|
"We still try to remove it from the offerbook.");
|
||||||
offerBookService.removeOffer(offer.getOfferPayload(),
|
offerBookService.removeOffer(offer.getOfferPayloadBase(),
|
||||||
() -> offer.setState(Offer.State.REMOVED),
|
() -> offer.setState(Offer.State.REMOVED),
|
||||||
null);
|
null);
|
||||||
}
|
}
|
||||||
|
@ -427,7 +437,20 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
public void activateOpenOffer(OpenOffer openOffer,
|
public void activateOpenOffer(OpenOffer openOffer,
|
||||||
ResultHandler resultHandler,
|
ResultHandler resultHandler,
|
||||||
ErrorMessageHandler errorMessageHandler) {
|
ErrorMessageHandler errorMessageHandler) {
|
||||||
if (!offersToBeEdited.containsKey(openOffer.getId())) {
|
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();
|
Offer offer = openOffer.getOffer();
|
||||||
offerBookService.activateOffer(offer,
|
offerBookService.activateOffer(offer,
|
||||||
() -> {
|
() -> {
|
||||||
|
@ -437,16 +460,13 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
resultHandler.handleResult();
|
resultHandler.handleResult();
|
||||||
},
|
},
|
||||||
errorMessageHandler);
|
errorMessageHandler);
|
||||||
} else {
|
|
||||||
errorMessageHandler.handleErrorMessage("You can't activate an offer that is currently edited.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deactivateOpenOffer(OpenOffer openOffer,
|
public void deactivateOpenOffer(OpenOffer openOffer,
|
||||||
ResultHandler resultHandler,
|
ResultHandler resultHandler,
|
||||||
ErrorMessageHandler errorMessageHandler) {
|
ErrorMessageHandler errorMessageHandler) {
|
||||||
Offer offer = openOffer.getOffer();
|
Offer offer = openOffer.getOffer();
|
||||||
offerBookService.deactivateOffer(offer.getOfferPayload(),
|
offerBookService.deactivateOffer(offer.getOfferPayloadBase(),
|
||||||
() -> {
|
() -> {
|
||||||
openOffer.setState(OpenOffer.State.DEACTIVATED);
|
openOffer.setState(OpenOffer.State.DEACTIVATED);
|
||||||
requestPersistence();
|
requestPersistence();
|
||||||
|
@ -456,6 +476,12 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
errorMessageHandler);
|
errorMessageHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeOpenOffer(OpenOffer openOffer) {
|
||||||
|
removeOpenOffer(openOffer, () -> {
|
||||||
|
}, error -> {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public void removeOpenOffer(OpenOffer openOffer,
|
public void removeOpenOffer(OpenOffer openOffer,
|
||||||
ResultHandler resultHandler,
|
ResultHandler resultHandler,
|
||||||
ErrorMessageHandler errorMessageHandler) {
|
ErrorMessageHandler errorMessageHandler) {
|
||||||
|
@ -464,7 +490,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
if (openOffer.isDeactivated()) {
|
if (openOffer.isDeactivated()) {
|
||||||
onRemoved(openOffer, resultHandler, offer);
|
onRemoved(openOffer, resultHandler, offer);
|
||||||
} else {
|
} else {
|
||||||
offerBookService.removeOffer(offer.getOfferPayload(),
|
offerBookService.removeOffer(offer.getOfferPayloadBase(),
|
||||||
() -> onRemoved(openOffer, resultHandler, offer),
|
() -> onRemoved(openOffer, resultHandler, offer),
|
||||||
errorMessageHandler);
|
errorMessageHandler);
|
||||||
}
|
}
|
||||||
|
@ -508,18 +534,17 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
|
|
||||||
openOffer.getOffer().setState(Offer.State.REMOVED);
|
openOffer.getOffer().setState(Offer.State.REMOVED);
|
||||||
openOffer.setState(OpenOffer.State.CANCELED);
|
openOffer.setState(OpenOffer.State.CANCELED);
|
||||||
openOffers.remove(openOffer);
|
removeOpenOfferFromList(openOffer);
|
||||||
|
|
||||||
OpenOffer editedOpenOffer = new OpenOffer(editedOffer, triggerPrice);
|
OpenOffer editedOpenOffer = new OpenOffer(editedOffer, triggerPrice);
|
||||||
editedOpenOffer.setState(originalState);
|
editedOpenOffer.setState(originalState);
|
||||||
|
|
||||||
openOffers.add(editedOpenOffer);
|
addOpenOfferToList(editedOpenOffer);
|
||||||
|
|
||||||
if (!editedOpenOffer.isDeactivated())
|
if (!editedOpenOffer.isDeactivated())
|
||||||
republishOffer(editedOpenOffer);
|
maybeRepublishOffer(editedOpenOffer);
|
||||||
|
|
||||||
offersToBeEdited.remove(openOffer.getId());
|
offersToBeEdited.remove(openOffer.getId());
|
||||||
requestPersistence();
|
|
||||||
resultHandler.handleResult();
|
resultHandler.handleResult();
|
||||||
} else {
|
} else {
|
||||||
errorMessageHandler.handleErrorMessage("There is no offer with this id existing to be published.");
|
errorMessageHandler.handleErrorMessage("There is no offer with this id existing to be published.");
|
||||||
|
@ -542,26 +567,26 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onRemoved(@NotNull OpenOffer openOffer, ResultHandler resultHandler, Offer offer) {
|
private void onRemoved(OpenOffer openOffer, ResultHandler resultHandler, Offer offer) {
|
||||||
offer.setState(Offer.State.REMOVED);
|
offer.setState(Offer.State.REMOVED);
|
||||||
openOffer.setState(OpenOffer.State.CANCELED);
|
openOffer.setState(OpenOffer.State.CANCELED);
|
||||||
openOffers.remove(openOffer);
|
removeOpenOfferFromList(openOffer);
|
||||||
|
if (!openOffer.getOffer().isBsqSwapOffer()) {
|
||||||
closedTradableManager.add(openOffer);
|
closedTradableManager.add(openOffer);
|
||||||
log.info("onRemoved offerId={}", offer.getId());
|
|
||||||
btcWalletService.resetAddressEntriesForOpenOffer(offer.getId());
|
btcWalletService.resetAddressEntriesForOpenOffer(offer.getId());
|
||||||
requestPersistence();
|
}
|
||||||
|
log.info("onRemoved offerId={}", offer.getId());
|
||||||
resultHandler.handleResult();
|
resultHandler.handleResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close openOffer after deposit published
|
// Close openOffer after deposit published
|
||||||
public void closeOpenOffer(Offer offer) {
|
public void closeOpenOffer(Offer offer) {
|
||||||
getOpenOfferById(offer.getId()).ifPresent(openOffer -> {
|
getOpenOfferById(offer.getId()).ifPresent(openOffer -> {
|
||||||
openOffers.remove(openOffer);
|
removeOpenOfferFromList(openOffer);
|
||||||
openOffer.setState(OpenOffer.State.CLOSED);
|
openOffer.setState(OpenOffer.State.CLOSED);
|
||||||
offerBookService.removeOffer(openOffer.getOffer().getOfferPayload(),
|
offerBookService.removeOffer(openOffer.getOffer().getOfferPayloadBase(),
|
||||||
() -> log.trace("Successful removed offer"),
|
() -> log.trace("Successful removed offer"),
|
||||||
log::error);
|
log::error);
|
||||||
requestPersistence();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -632,7 +657,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
Validator.nonEmptyStringOf(request.offerId);
|
Validator.nonEmptyStringOf(request.offerId);
|
||||||
checkNotNull(request.getPubKeyRing());
|
checkNotNull(request.getPubKeyRing());
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
errorMessage = "Message validation failed. Error=" + t.toString() + ", Message=" + request.toString();
|
errorMessage = "Message validation failed. Error=" + t + ", Message=" + request;
|
||||||
log.warn(errorMessage);
|
log.warn(errorMessage);
|
||||||
sendAckMessage(request, peer, false, errorMessage);
|
sendAckMessage(request, peer, false, errorMessage);
|
||||||
return;
|
return;
|
||||||
|
@ -787,23 +812,27 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
ArrayList<OpenOffer> openOffersClone = new ArrayList<>(openOffers.getList());
|
ArrayList<OpenOffer> openOffersClone = new ArrayList<>(openOffers.getList());
|
||||||
openOffersClone.forEach(originalOpenOffer -> {
|
openOffersClone.forEach(originalOpenOffer -> {
|
||||||
Offer originalOffer = originalOpenOffer.getOffer();
|
Offer originalOffer = originalOpenOffer.getOffer();
|
||||||
|
if (originalOffer.isBsqSwapOffer()) {
|
||||||
OfferPayload originalOfferPayload = originalOffer.getOfferPayload();
|
// 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
|
// 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
|
// 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.
|
// 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.MEDIATION) ||
|
||||||
!OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.REFUND_AGENT) ||
|
!OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.REFUND_AGENT) ||
|
||||||
!originalOfferPayload.getOwnerNodeAddress().equals(p2PService.getAddress())) {
|
!original.getOwnerNodeAddress().equals(p2PService.getAddress())) {
|
||||||
|
|
||||||
// - Capabilities changed?
|
// - Capabilities changed?
|
||||||
// We rewrite our offer with the additional capabilities entry
|
// We rewrite our offer with the additional capabilities entry
|
||||||
Map<String, String> updatedExtraDataMap = new HashMap<>();
|
Map<String, String> updatedExtraDataMap = new HashMap<>();
|
||||||
if (!OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.MEDIATION) ||
|
if (!OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.MEDIATION) ||
|
||||||
!OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.REFUND_AGENT)) {
|
!OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.REFUND_AGENT)) {
|
||||||
Map<String, String> originalExtraDataMap = originalOfferPayload.getExtraDataMap();
|
Map<String, String> originalExtraDataMap = original.getExtraDataMap();
|
||||||
|
|
||||||
if (originalExtraDataMap != null) {
|
if (originalExtraDataMap != null) {
|
||||||
updatedExtraDataMap.putAll(originalExtraDataMap);
|
updatedExtraDataMap.putAll(originalExtraDataMap);
|
||||||
|
@ -814,11 +843,11 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
|
|
||||||
log.info("Converted offer to support new Capability.MEDIATION and Capability.REFUND_AGENT capability. id={}", originalOffer.getId());
|
log.info("Converted offer to support new Capability.MEDIATION and Capability.REFUND_AGENT capability. id={}", originalOffer.getId());
|
||||||
} else {
|
} else {
|
||||||
updatedExtraDataMap = originalOfferPayload.getExtraDataMap();
|
updatedExtraDataMap = original.getExtraDataMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// - Protocol version changed?
|
// - Protocol version changed?
|
||||||
int protocolVersion = originalOfferPayload.getProtocolVersion();
|
int protocolVersion = original.getProtocolVersion();
|
||||||
if (protocolVersion < Version.TRADE_PROTOCOL_VERSION) {
|
if (protocolVersion < Version.TRADE_PROTOCOL_VERSION) {
|
||||||
// We update the trade protocol version
|
// We update the trade protocol version
|
||||||
protocolVersion = Version.TRADE_PROTOCOL_VERSION;
|
protocolVersion = Version.TRADE_PROTOCOL_VERSION;
|
||||||
|
@ -826,48 +855,48 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
}
|
}
|
||||||
|
|
||||||
// - node address changed? (due to a faulty tor dir)
|
// - node address changed? (due to a faulty tor dir)
|
||||||
NodeAddress ownerNodeAddress = originalOfferPayload.getOwnerNodeAddress();
|
NodeAddress ownerNodeAddress = original.getOwnerNodeAddress();
|
||||||
if (!ownerNodeAddress.equals(p2PService.getAddress())) {
|
if (!ownerNodeAddress.equals(p2PService.getAddress())) {
|
||||||
ownerNodeAddress = p2PService.getAddress();
|
ownerNodeAddress = p2PService.getAddress();
|
||||||
log.info("Updated the owner nodeaddress of offer id={}", originalOffer.getId());
|
log.info("Updated the owner nodeaddress of offer id={}", originalOffer.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
OfferPayload updatedPayload = new OfferPayload(originalOfferPayload.getId(),
|
OfferPayload updatedPayload = new OfferPayload(original.getId(),
|
||||||
originalOfferPayload.getDate(),
|
original.getDate(),
|
||||||
ownerNodeAddress,
|
ownerNodeAddress,
|
||||||
originalOfferPayload.getPubKeyRing(),
|
original.getPubKeyRing(),
|
||||||
originalOfferPayload.getDirection(),
|
original.getDirection(),
|
||||||
originalOfferPayload.getPrice(),
|
original.getPrice(),
|
||||||
originalOfferPayload.getMarketPriceMargin(),
|
original.getMarketPriceMargin(),
|
||||||
originalOfferPayload.isUseMarketBasedPrice(),
|
original.isUseMarketBasedPrice(),
|
||||||
originalOfferPayload.getAmount(),
|
original.getAmount(),
|
||||||
originalOfferPayload.getMinAmount(),
|
original.getMinAmount(),
|
||||||
originalOfferPayload.getBaseCurrencyCode(),
|
original.getBaseCurrencyCode(),
|
||||||
originalOfferPayload.getCounterCurrencyCode(),
|
original.getCounterCurrencyCode(),
|
||||||
originalOfferPayload.getArbitratorNodeAddresses(),
|
original.getArbitratorNodeAddresses(),
|
||||||
originalOfferPayload.getMediatorNodeAddresses(),
|
original.getMediatorNodeAddresses(),
|
||||||
originalOfferPayload.getPaymentMethodId(),
|
original.getPaymentMethodId(),
|
||||||
originalOfferPayload.getMakerPaymentAccountId(),
|
original.getMakerPaymentAccountId(),
|
||||||
originalOfferPayload.getOfferFeePaymentTxId(),
|
original.getOfferFeePaymentTxId(),
|
||||||
originalOfferPayload.getCountryCode(),
|
original.getCountryCode(),
|
||||||
originalOfferPayload.getAcceptedCountryCodes(),
|
original.getAcceptedCountryCodes(),
|
||||||
originalOfferPayload.getBankId(),
|
original.getBankId(),
|
||||||
originalOfferPayload.getAcceptedBankIds(),
|
original.getAcceptedBankIds(),
|
||||||
originalOfferPayload.getVersionNr(),
|
original.getVersionNr(),
|
||||||
originalOfferPayload.getBlockHeightAtOfferCreation(),
|
original.getBlockHeightAtOfferCreation(),
|
||||||
originalOfferPayload.getTxFee(),
|
original.getTxFee(),
|
||||||
originalOfferPayload.getMakerFee(),
|
original.getMakerFee(),
|
||||||
originalOfferPayload.isCurrencyForMakerFeeBtc(),
|
original.isCurrencyForMakerFeeBtc(),
|
||||||
originalOfferPayload.getBuyerSecurityDeposit(),
|
original.getBuyerSecurityDeposit(),
|
||||||
originalOfferPayload.getSellerSecurityDeposit(),
|
original.getSellerSecurityDeposit(),
|
||||||
originalOfferPayload.getMaxTradeLimit(),
|
original.getMaxTradeLimit(),
|
||||||
originalOfferPayload.getMaxTradePeriod(),
|
original.getMaxTradePeriod(),
|
||||||
originalOfferPayload.isUseAutoClose(),
|
original.isUseAutoClose(),
|
||||||
originalOfferPayload.isUseReOpenAfterAutoClose(),
|
original.isUseReOpenAfterAutoClose(),
|
||||||
originalOfferPayload.getLowerClosePrice(),
|
original.getLowerClosePrice(),
|
||||||
originalOfferPayload.getUpperClosePrice(),
|
original.getUpperClosePrice(),
|
||||||
originalOfferPayload.isPrivateOffer(),
|
original.isPrivateOffer(),
|
||||||
originalOfferPayload.getHashOfChallenge(),
|
original.getHashOfChallenge(),
|
||||||
updatedExtraDataMap,
|
updatedExtraDataMap,
|
||||||
protocolVersion);
|
protocolVersion);
|
||||||
|
|
||||||
|
@ -878,7 +907,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
// remove old offer
|
// remove old offer
|
||||||
originalOffer.setState(Offer.State.REMOVED);
|
originalOffer.setState(Offer.State.REMOVED);
|
||||||
originalOpenOffer.setState(OpenOffer.State.CANCELED);
|
originalOpenOffer.setState(OpenOffer.State.CANCELED);
|
||||||
openOffers.remove(originalOpenOffer);
|
removeOpenOfferFromList(originalOpenOffer);
|
||||||
|
|
||||||
// Create new Offer
|
// Create new Offer
|
||||||
Offer updatedOffer = new Offer(updatedPayload);
|
Offer updatedOffer = new Offer(updatedPayload);
|
||||||
|
@ -887,8 +916,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
|
|
||||||
OpenOffer updatedOpenOffer = new OpenOffer(updatedOffer, originalOpenOffer.getTriggerPrice());
|
OpenOffer updatedOpenOffer = new OpenOffer(updatedOffer, originalOpenOffer.getTriggerPrice());
|
||||||
updatedOpenOffer.setState(originalOpenOfferState);
|
updatedOpenOffer.setState(originalOpenOfferState);
|
||||||
openOffers.add(updatedOpenOffer);
|
addOpenOfferToList(updatedOpenOffer);
|
||||||
requestPersistence();
|
|
||||||
|
|
||||||
log.info("Updating offer completed. id={}", originalOffer.getId());
|
log.info("Updating offer completed. id={}", originalOffer.getId());
|
||||||
}
|
}
|
||||||
|
@ -904,7 +932,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
if (stopped) {
|
if (stopped) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
stopPeriodicRefreshOffersTimer();
|
stopPeriodicRefreshOffersTimer();
|
||||||
|
|
||||||
List<OpenOffer> openOffersList = new ArrayList<>(openOffers.getList());
|
List<OpenOffer> openOffersList = new ArrayList<>(openOffers.getList());
|
||||||
|
@ -917,15 +944,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
}
|
}
|
||||||
|
|
||||||
OpenOffer openOffer = list.remove(0);
|
OpenOffer openOffer = list.remove(0);
|
||||||
if (openOffers.contains(openOffer) && !openOffer.isDeactivated()) {
|
if (openOffers.contains(openOffer)) {
|
||||||
// TODO It is not clear yet if it is better for the node and the network to send out all add offer
|
maybeRepublishOffer(openOffer, () -> processListForRepublishOffers(list));
|
||||||
// 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));*/
|
|
||||||
} else {
|
} else {
|
||||||
// If the offer was removed in the meantime or if its deactivated we skip and call
|
// 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.
|
// processListForRepublishOffers again with the list where we removed the offer already.
|
||||||
|
@ -933,11 +953,18 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void republishOffer(OpenOffer openOffer) {
|
public void maybeRepublishOffer(OpenOffer openOffer) {
|
||||||
republishOffer(openOffer, null);
|
maybeRepublishOffer(openOffer, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeRepublishOffer(OpenOffer openOffer, @Nullable Runnable completeHandler) {
|
||||||
|
if (preventedFromPublishing(openOffer)) {
|
||||||
|
if (completeHandler != null) {
|
||||||
|
completeHandler.run();
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void republishOffer(OpenOffer openOffer, @Nullable Runnable completeHandler) {
|
|
||||||
offerBookService.addOffer(openOffer.getOffer(),
|
offerBookService.addOffer(openOffer.getOffer(),
|
||||||
() -> {
|
() -> {
|
||||||
if (!stopped) {
|
if (!stopped) {
|
||||||
|
@ -996,8 +1023,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
final OpenOffer openOffer = openOffersList.get(i);
|
final OpenOffer openOffer = openOffersList.get(i);
|
||||||
UserThread.runAfterRandomDelay(() -> {
|
UserThread.runAfterRandomDelay(() -> {
|
||||||
// we need to check if in the meantime the offer has been removed
|
// we need to check if in the meantime the offer has been removed
|
||||||
if (openOffers.contains(openOffer) && !openOffer.isDeactivated())
|
if (openOffers.contains(openOffer))
|
||||||
refreshOffer(openOffer);
|
maybeRefreshOffer(openOffer);
|
||||||
}, minDelay, maxDelay, TimeUnit.MILLISECONDS);
|
}, minDelay, maxDelay, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1010,8 +1037,11 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
log.trace("periodicRefreshOffersTimer already stated");
|
log.trace("periodicRefreshOffersTimer already stated");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refreshOffer(OpenOffer openOffer) {
|
private void maybeRefreshOffer(OpenOffer openOffer) {
|
||||||
offerBookService.refreshTTL(openOffer.getOffer().getOfferPayload(),
|
if (preventedFromPublishing(openOffer)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
offerBookService.refreshTTL(openOffer.getOffer().getOfferPayloadBase(),
|
||||||
() -> log.debug("Successful refreshed TTL for offer"),
|
() -> log.debug("Successful refreshed TTL for offer"),
|
||||||
log::warn);
|
log::warn);
|
||||||
}
|
}
|
||||||
|
@ -1028,7 +1058,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
startPeriodicRepublishOffersTimer();
|
startPeriodicRepublishOffersTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void requestPersistence() {
|
public void requestPersistence() {
|
||||||
persistenceManager.requestPersistence();
|
persistenceManager.requestPersistence();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1057,4 +1087,23 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
retryRepublishOffersTimer = null;
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package bisq.core.offer;
|
package bisq.core.offer.availability;
|
||||||
|
|
||||||
public enum AvailabilityResult {
|
public enum AvailabilityResult {
|
||||||
UNKNOWN_FAILURE("cannot take offer for unknown reason"),
|
UNKNOWN_FAILURE("cannot take offer for unknown reason"),
|
|
@ -18,7 +18,7 @@
|
||||||
package bisq.core.offer.availability;
|
package bisq.core.offer.availability;
|
||||||
|
|
||||||
import bisq.core.offer.Offer;
|
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.support.dispute.mediation.mediator.MediatorManager;
|
||||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||||
import bisq.core.user.User;
|
import bisq.core.user.User;
|
||||||
|
|
|
@ -18,10 +18,10 @@
|
||||||
package bisq.core.offer.availability;
|
package bisq.core.offer.availability;
|
||||||
|
|
||||||
import bisq.core.offer.Offer;
|
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.ProcessOfferAvailabilityResponse;
|
||||||
import bisq.core.offer.availability.tasks.SendOfferAvailabilityRequest;
|
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.core.util.Validator;
|
||||||
|
|
||||||
import bisq.network.p2p.AckMessage;
|
import bisq.network.p2p.AckMessage;
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue