mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-24 15:10:44 +01:00
Fix BSQ algo, add tx view
This commit is contained in:
parent
db966ca01c
commit
0386f1e739
31 changed files with 951 additions and 322 deletions
|
@ -610,6 +610,7 @@ funds.locked.locked=Locked in MultiSig for trade with ID: {0}
|
|||
|
||||
funds.tx.direction.sentTo=Sent to:
|
||||
funds.tx.direction.receivedWith=Received with:
|
||||
funds.tx.txFeePaymentForBsqTx=Tx fee payment for BSQ tx
|
||||
funds.tx.createOfferFee=Maker fee: {0}
|
||||
funds.tx.takeOfferFee=Taker fee: {0}
|
||||
funds.tx.multiSigDeposit=MultiSig deposit: {0}
|
||||
|
@ -1514,6 +1515,7 @@ validation.bic.letters=Bank and Country code must be letters
|
|||
validation.bic.invalidLocationCode=BIC contains invalid location code
|
||||
validation.bic.invalidBranchCode=BIC contains invalid branch code
|
||||
validation.btc.invalidFormat=Invalid format of the bitcoin address.
|
||||
validation.bsq.invalidFormat=Invalid format of the BSQ address.
|
||||
validation.email.invalidAddress=Invalid address=
|
||||
validation.iban.invalidCountryCode=Country code invalid
|
||||
validation.iban.checkSumNotNumeric=Checksum must be numeric
|
||||
|
|
|
@ -19,21 +19,25 @@ package io.bisq.core.btc.wallet;
|
|||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import io.bisq.common.handlers.ErrorMessageHandler;
|
||||
import io.bisq.common.handlers.ResultHandler;
|
||||
import io.bisq.core.btc.Restrictions;
|
||||
import io.bisq.core.btc.exceptions.TransactionVerificationException;
|
||||
import io.bisq.core.btc.exceptions.WalletException;
|
||||
import io.bisq.core.dao.blockchain.BsqBlock;
|
||||
import io.bisq.core.dao.blockchain.BsqBlockchainManager;
|
||||
import io.bisq.core.dao.blockchain.BsqUTXOMap;
|
||||
import io.bisq.core.dao.blockchain.Tx;
|
||||
import io.bisq.core.provider.fee.FeeService;
|
||||
import io.bisq.core.user.Preferences;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bitcoinj.core.*;
|
||||
import org.bitcoinj.script.Script;
|
||||
import org.bitcoinj.wallet.CoinSelection;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.List;
|
||||
|
@ -42,11 +46,13 @@ import java.util.stream.Collectors;
|
|||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
@Slf4j
|
||||
public class BsqWalletService extends WalletService {
|
||||
private static final Logger log = LoggerFactory.getLogger(BsqWalletService.class);
|
||||
|
||||
private final BsqBlockchainManager bsqBlockchainManager;
|
||||
private final BsqCoinSelector bsqCoinSelector;
|
||||
@Getter
|
||||
private final ObservableList<Transaction> walletBsqTransactions = FXCollections.observableArrayList();
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -73,44 +79,41 @@ public class BsqWalletService extends WalletService {
|
|||
wallet.addEventListener(new AbstractWalletEventListener() {
|
||||
@Override
|
||||
public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
|
||||
onChange();
|
||||
//TODO do we need updateWalletBsqTransactions(); here?
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCoinsSent(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
|
||||
onChange();
|
||||
//TODO do we need updateWalletBsqTransactions(); here?
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReorganize(Wallet wallet) {
|
||||
onChange();
|
||||
updateWalletBsqTransactions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransactionConfidenceChanged(Wallet wallet, Transaction tx) {
|
||||
onChange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onKeysAdded(List<ECKey> keys) {
|
||||
onChange();
|
||||
updateWalletBsqTransactions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScriptsChanged(Wallet wallet, List<Script> scripts, boolean isAddingScripts) {
|
||||
onChange();
|
||||
updateWalletBsqTransactions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWalletChanged(Wallet wallet) {
|
||||
onChange();
|
||||
updateWalletBsqTransactions();
|
||||
}
|
||||
|
||||
public void onChange() {
|
||||
// TODO
|
||||
}
|
||||
});
|
||||
});
|
||||
bsqBlockchainManager.getBsqBlocks().addListener((ListChangeListener<BsqBlock>) c -> updateWalletBsqTransactions());
|
||||
}
|
||||
|
||||
|
||||
|
@ -141,7 +144,7 @@ public class BsqWalletService extends WalletService {
|
|||
// UTXO
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void requestBsqUtxo(@Nullable ResultHandler resultHandler, @Nullable ErrorMessageHandler errorMessageHandler) {
|
||||
public void requestBsqUtxo(@Nullable ResultHandler resultHandler) {
|
||||
if (bsqBlockchainManager.isUtxoAvailable()) {
|
||||
applyUtxoSetToUTXOProvider(bsqBlockchainManager.getUtxoByTxIdMap());
|
||||
if (resultHandler != null)
|
||||
|
@ -159,6 +162,17 @@ public class BsqWalletService extends WalletService {
|
|||
bsqCoinSelector.setUtxoMap(bsqUTXOMap);
|
||||
}
|
||||
|
||||
private void updateWalletBsqTransactions() {
|
||||
Set<String> txIdsFromUTXOSet = bsqBlockchainManager.getBsqBlocks().stream()
|
||||
.flatMap(bsqBlock -> bsqBlock.getTxByTxIdMap().values().stream())
|
||||
.map(Tx::getId)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
walletBsqTransactions.setAll(getTransactions(true).stream()
|
||||
.filter(t -> txIdsFromUTXOSet.contains(t.getHashAsString()))
|
||||
.collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Sign tx
|
||||
|
|
|
@ -27,6 +27,7 @@ import io.bisq.core.btc.exceptions.WalletException;
|
|||
import io.bisq.core.btc.listeners.AddressConfidenceListener;
|
||||
import io.bisq.core.btc.listeners.BalanceListener;
|
||||
import io.bisq.core.btc.listeners.TxConfidenceListener;
|
||||
import io.bisq.core.dao.blockchain.TxOutput;
|
||||
import io.bisq.core.provider.fee.FeeService;
|
||||
import io.bisq.core.user.Preferences;
|
||||
import org.bitcoinj.core.*;
|
||||
|
@ -319,6 +320,7 @@ public abstract class WalletService {
|
|||
@Nullable
|
||||
public TransactionConfidence getConfidenceForTxId(String txId) {
|
||||
if (wallet != null) {
|
||||
// TODO includeDead txs?
|
||||
Set<Transaction> transactions = wallet.getTransactions(true);
|
||||
for (Transaction tx : transactions) {
|
||||
if (tx.getHashAsString().equals(txId))
|
||||
|
@ -543,6 +545,26 @@ public abstract class WalletService {
|
|||
return transactionOutput.isMine(wallet);
|
||||
}
|
||||
|
||||
public boolean isTxOutputMine(TxOutput txOutput) {
|
||||
try {
|
||||
Script script = txOutput.getScript();
|
||||
if (script.isSentToRawPubKey()) {
|
||||
byte[] pubkey = script.getPubKey();
|
||||
return wallet.isPubKeyMine(pubkey);
|
||||
}
|
||||
if (script.isPayToScriptHash()) {
|
||||
return wallet.isPayToScriptHashMine(script.getPubKeyHash());
|
||||
} else {
|
||||
byte[] pubkeyHash = script.getPubKeyHash();
|
||||
return wallet.isPubKeyHashMine(pubkeyHash);
|
||||
}
|
||||
} catch (ScriptException e) {
|
||||
// Just means we didn't understand the output of this transaction: ignore it.
|
||||
log.debug("Could not parse tx output script: {}", e.toString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Coin getValueSentFromMeForTransaction(Transaction transaction) throws ScriptException {
|
||||
return transaction.getValueSentFromMe(wallet);
|
||||
}
|
||||
|
@ -589,7 +611,6 @@ public abstract class WalletService {
|
|||
TransactionConfidence transactionConfidence = getMostRecentConfidence(transactionConfidenceList);
|
||||
addressConfidenceListener.onTransactionConfidenceChanged(transactionConfidence);
|
||||
}
|
||||
|
||||
txConfidenceListeners.stream()
|
||||
.filter(txConfidenceListener -> tx != null &&
|
||||
tx.getHashAsString() != null &&
|
||||
|
|
|
@ -18,14 +18,12 @@
|
|||
package io.bisq.core.dao.blockchain;
|
||||
|
||||
import lombok.Value;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Value
|
||||
@Slf4j
|
||||
public class BsqBlock {
|
||||
private final int height;
|
||||
private final List<String> txIds;
|
||||
|
@ -43,4 +41,13 @@ public class BsqBlock {
|
|||
public Tx getTxByTxId(String txId) {
|
||||
return txByTxIdMap.get(txId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BsqBlock{" +
|
||||
"\nheight=" + height +
|
||||
",\ntxIds=" + txIds +
|
||||
",\ntxByTxIdMap=" + txByTxIdMap +
|
||||
"}\n";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,13 +23,17 @@ import com.google.common.util.concurrent.ListenableFuture;
|
|||
import com.google.inject.Inject;
|
||||
import io.bisq.common.UserThread;
|
||||
import io.bisq.common.handlers.ErrorMessageHandler;
|
||||
import io.bisq.common.util.Tuple2;
|
||||
import io.bisq.common.util.Tuple3;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class BsqBlockchainManager {
|
||||
private static final Logger log = LoggerFactory.getLogger(BsqBlockchainManager.class);
|
||||
|
@ -37,13 +41,18 @@ public class BsqBlockchainManager {
|
|||
private final BsqBlockchainService blockchainService;
|
||||
|
||||
// regtest
|
||||
public static final String GENESIS_TX_ID = "c0ddf75202579fd43e0d5a41ac5bae05a6e8295633695af6c350b9100878c5e7";
|
||||
public static final String GENESIS_TX_ID = "3bc7bc9484e112ec8ddd1a1c984379819245ac463b9ce40fa8b5bf771c0f9236";
|
||||
public static final int GENESIS_BLOCK_HEIGHT = 102;
|
||||
|
||||
protected BsqUTXOMap utxoByTxIdMap;
|
||||
private BsqUTXOMap utxoByTxIdMap;
|
||||
private final List<BsqUTXOListener> bsqUTXOListeners = new ArrayList<>();
|
||||
private boolean isUtxoAvailable;
|
||||
protected int chainHeadHeight;
|
||||
@Getter
|
||||
private int chainHeadHeight;
|
||||
|
||||
// We prefer a list over a set. See: http://stackoverflow.com/questions/24799125/what-is-the-observableset-equivalent-for-setall-method-from-observablelist
|
||||
@Getter
|
||||
private final ObservableList<BsqBlock> bsqBlocks = FXCollections.observableArrayList();
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -83,18 +92,19 @@ public class BsqBlockchainManager {
|
|||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Protected methods
|
||||
// private methods
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
protected void setupComplete() {
|
||||
ListenableFuture<Tuple2<BsqUTXOMap, Integer>> future =
|
||||
private void setupComplete() {
|
||||
ListenableFuture<Tuple3<Set<BsqBlock>, BsqUTXOMap, Integer>> future =
|
||||
blockchainService.syncFromGenesis(GENESIS_BLOCK_HEIGHT, GENESIS_TX_ID);
|
||||
Futures.addCallback(future, new FutureCallback<Tuple2<BsqUTXOMap, Integer>>() {
|
||||
Futures.addCallback(future, new FutureCallback<Tuple3<Set<BsqBlock>, BsqUTXOMap, Integer>>() {
|
||||
@Override
|
||||
public void onSuccess(Tuple2<BsqUTXOMap, Integer> tuple) {
|
||||
public void onSuccess(Tuple3<Set<BsqBlock>, BsqUTXOMap, Integer> tuple) {
|
||||
UserThread.execute(() -> {
|
||||
BsqBlockchainManager.this.utxoByTxIdMap = tuple.first;
|
||||
chainHeadHeight = tuple.second;
|
||||
BsqBlockchainManager.this.bsqBlocks.setAll(tuple.first);
|
||||
BsqBlockchainManager.this.utxoByTxIdMap = tuple.second;
|
||||
chainHeadHeight = tuple.third;
|
||||
isUtxoAvailable = true;
|
||||
bsqUTXOListeners.stream().forEach(e -> e.onBsqUTXOChanged(utxoByTxIdMap));
|
||||
blockchainService.syncFromGenesisCompete(GENESIS_TX_ID,
|
||||
|
@ -103,15 +113,17 @@ public class BsqBlockchainManager {
|
|||
if (btcdBlock != null) {
|
||||
UserThread.execute(() -> {
|
||||
try {
|
||||
blockchainService.parseBlock(new BsqBlock(btcdBlock.getTx(), btcdBlock.getHeight()),
|
||||
final BsqBlock bsqBlock = new BsqBlock(btcdBlock.getTx(), btcdBlock.getHeight());
|
||||
blockchainService.parseBlock(bsqBlock,
|
||||
GENESIS_BLOCK_HEIGHT,
|
||||
GENESIS_TX_ID,
|
||||
utxoByTxIdMap);
|
||||
if (!BsqBlockchainManager.this.bsqBlocks.contains(bsqBlock))
|
||||
BsqBlockchainManager.this.bsqBlocks.add(bsqBlock);
|
||||
} catch (BsqBlockchainException e) {
|
||||
//TODO
|
||||
e.printStackTrace();
|
||||
}
|
||||
blockchainService.printUtxoMap(utxoByTxIdMap);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -124,25 +136,4 @@ public class BsqBlockchainManager {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Getters
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public int getChainHeadHeight() {
|
||||
return chainHeadHeight;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Setters
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private methods
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -33,12 +33,12 @@ import io.bisq.common.UserThread;
|
|||
import io.bisq.common.handlers.ErrorMessageHandler;
|
||||
import io.bisq.common.handlers.ResultHandler;
|
||||
import io.bisq.common.util.Tuple2;
|
||||
import io.bisq.common.util.Tuple3;
|
||||
import io.bisq.common.util.Utilities;
|
||||
import io.bisq.core.dao.RpcOptionKeys;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.script.Script;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -47,7 +47,9 @@ import org.slf4j.LoggerFactory;
|
|||
import javax.inject.Named;
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -137,13 +139,13 @@ public class BsqBlockchainRpcService extends BsqBlockchainService {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected ListenableFuture<Tuple2<BsqUTXOMap, Integer>> syncFromGenesis(int genesisBlockHeight, String genesisTxId) {
|
||||
protected ListenableFuture<Tuple3<Set<BsqBlock>, BsqUTXOMap, Integer>> syncFromGenesis(int genesisBlockHeight, String genesisTxId) {
|
||||
return rpcRequestsExecutor.submit(() -> {
|
||||
long startTs = System.currentTimeMillis();
|
||||
int chainHeadHeight = requestChainHeadHeight();
|
||||
BsqUTXOMap bsqUTXOMap = parseAllBlocksFromGenesis(chainHeadHeight, genesisBlockHeight, genesisTxId);
|
||||
Tuple2<Set<BsqBlock>, BsqUTXOMap> tuple2 = parseAllBlocksFromGenesis(chainHeadHeight, genesisBlockHeight, genesisTxId);
|
||||
log.info("syncFromGenesis took {} ms", System.currentTimeMillis() - startTs);
|
||||
return new Tuple2<>(bsqUTXOMap, chainHeadHeight);
|
||||
return new Tuple3<>(tuple2.first, tuple2.second, chainHeadHeight);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -186,20 +188,25 @@ public class BsqBlockchainRpcService extends BsqBlockchainService {
|
|||
Tx requestTransaction(String txId) throws BsqBlockchainException {
|
||||
try {
|
||||
RawTransaction rawTransaction = getRawTransaction(txId);
|
||||
return new Tx(txId,
|
||||
rawTransaction.getVIn()
|
||||
final List<TxInput> txInputs = rawTransaction.getVIn()
|
||||
.stream()
|
||||
.filter(rawInput -> rawInput != null && rawInput.getVOut() != null && rawInput.getTxId() != null)
|
||||
.map(rawInput -> new TxInput(rawInput.getVOut(), rawInput.getTxId(), rawTransaction.getHex()))
|
||||
.collect(Collectors.toList()),
|
||||
rawTransaction.getVOut()
|
||||
.collect(Collectors.toList());
|
||||
final List<TxOutput> txOutputs = rawTransaction.getVOut()
|
||||
.stream()
|
||||
.filter(e -> e != null && e.getN() != null && e.getValue() != null && e.getScriptPubKey() != null)
|
||||
.map(e -> new TxOutput(e.getN(),
|
||||
Coin.valueOf(e.getValue().movePointRight(8).longValue()),
|
||||
e.getValue().movePointRight(8).longValue(),
|
||||
e.getScriptPubKey().getAddresses(),
|
||||
e.getScriptPubKey().getHex() != null ? new Script(HEX.decode(e.getScriptPubKey().getHex())) : null))
|
||||
.collect(Collectors.toList()));
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// rawTransaction.getTime() is in seconds but we keep it in ms internally
|
||||
return new Tx(txId,
|
||||
txInputs,
|
||||
txOutputs,
|
||||
rawTransaction.getTime() * 1000);
|
||||
} catch (BitcoindException | CommunicationException e) {
|
||||
throw new BsqBlockchainException(e.getMessage(), e);
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ import io.bisq.common.app.DevEnv;
|
|||
import io.bisq.common.handlers.ErrorMessageHandler;
|
||||
import io.bisq.common.handlers.ResultHandler;
|
||||
import io.bisq.common.util.Tuple2;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import io.bisq.common.util.Tuple3;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -56,7 +56,7 @@ abstract public class BsqBlockchainService {
|
|||
|
||||
abstract void setup(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler);
|
||||
|
||||
abstract ListenableFuture<Tuple2<BsqUTXOMap, Integer>> syncFromGenesis(int genesisBlockHeight, String genesisTxId);
|
||||
abstract ListenableFuture<Tuple3<Set<BsqBlock>, BsqUTXOMap, Integer>> syncFromGenesis(int genesisBlockHeight, String genesisTxId);
|
||||
|
||||
abstract void syncFromGenesisCompete(String genesisTxId, int genesisBlockHeight, Consumer<Block> onNewBlockHandler);
|
||||
|
||||
|
@ -67,24 +67,27 @@ abstract public class BsqBlockchainService {
|
|||
abstract Tx requestTransaction(String txId) throws BsqBlockchainException;
|
||||
|
||||
@VisibleForTesting
|
||||
BsqUTXOMap parseAllBlocksFromGenesis(int chainHeadHeight,
|
||||
Tuple2<Set<BsqBlock>, BsqUTXOMap> parseAllBlocksFromGenesis(int chainHeadHeight,
|
||||
int genesisBlockHeight,
|
||||
String genesisTxId) throws BsqBlockchainException {
|
||||
try {
|
||||
BsqUTXOMap bsqUTXOMap = new BsqUTXOMap();
|
||||
Set<BsqBlock> blocks = new HashSet<>();
|
||||
log.info("chainHeadHeight=" + chainHeadHeight);
|
||||
long startTs = System.currentTimeMillis();
|
||||
for (int height = genesisBlockHeight; height <= chainHeadHeight; height++) {
|
||||
Block btcdBlock = requestBlock(height);
|
||||
log.info("Current block height=" + height);
|
||||
parseBlock(new BsqBlock(btcdBlock.getTx(), btcdBlock.getHeight()),
|
||||
final BsqBlock bsqBlock = new BsqBlock(btcdBlock.getTx(), btcdBlock.getHeight());
|
||||
blocks.add(bsqBlock);
|
||||
parseBlock(bsqBlock,
|
||||
genesisBlockHeight,
|
||||
genesisTxId,
|
||||
bsqUTXOMap);
|
||||
log.info(bsqUTXOMap.toString());
|
||||
}
|
||||
printUtxoMap(bsqUTXOMap);
|
||||
log.info("Took {} ms", System.currentTimeMillis() - startTs);
|
||||
return bsqUTXOMap;
|
||||
return new Tuple2<>(blocks, bsqUTXOMap);
|
||||
} catch (Throwable t) {
|
||||
throw new BsqBlockchainException(t.getMessage(), t);
|
||||
}
|
||||
|
@ -110,9 +113,6 @@ abstract public class BsqBlockchainService {
|
|||
txByTxIdMap.entrySet().stream()
|
||||
.filter(entry -> entry.getKey().equals(genesisTxId))
|
||||
.forEach(entry -> parseGenesisTx(entry.getValue(), blockHeight, bsqUTXOMap));
|
||||
|
||||
// We need to remove the genesis tx from further parsing (would be treated as intraBlockInputTx)
|
||||
txByTxIdMap.remove(genesisTxId);
|
||||
}
|
||||
|
||||
// Worst case is that all txs in a block are depending on another, so only once get resolved at each iteration.
|
||||
|
@ -134,58 +134,50 @@ abstract public class BsqBlockchainService {
|
|||
// The set of txIds of txs which are used for inputs of another tx in same block
|
||||
Set<String> intraBlockSpendingTxIdSet = getIntraBlockSpendingTxIdSet(transactions);
|
||||
|
||||
Set<Tx> nonIntraBlockInputTxs = new HashSet<>();
|
||||
Set<Tx> intraBlockInputTxs = new HashSet<>();
|
||||
Set<Tx> txsWithoutInputsFromSameBlock = new HashSet<>();
|
||||
Set<Tx> txsWithInputsFromSameBlock = new HashSet<>();
|
||||
// First we find the txs which have no intra-block inputs
|
||||
outerLoop:
|
||||
for (Tx tx : transactions) {
|
||||
for (TxInput input : tx.getInputs()) {
|
||||
if (intraBlockSpendingTxIdSet.contains(input.getSpendingTxId())) {
|
||||
// We have an input from one of the intra-block-transactions, so we cannot process that tx now.
|
||||
// We add the tx for later parsing to the intraBlockInputTxs and move to the next
|
||||
// We add the tx for later parsing to the txsWithInputsFromSameBlock and move to the next
|
||||
// outerLoop iteration .
|
||||
intraBlockInputTxs.add(tx);
|
||||
txsWithInputsFromSameBlock.add(tx);
|
||||
|
||||
continue outerLoop;
|
||||
}
|
||||
}
|
||||
// If we have not found any tx input pointing to anther tx in the same block we add it to our
|
||||
// nonIntraBlockInputTxs for first pass of BSQ validation
|
||||
nonIntraBlockInputTxs.add(tx);
|
||||
// txsWithoutInputsFromSameBlock for first pass of BSQ validation
|
||||
txsWithoutInputsFromSameBlock.add(tx);
|
||||
}
|
||||
|
||||
log.debug("intraBlockInputTxs " + intraBlockInputTxs.size());
|
||||
log.debug("nonIntraBlockInputTxs " + nonIntraBlockInputTxs.size());
|
||||
|
||||
// we check if we have any valid BSQ utxo from that tx set
|
||||
boolean utxoChanged = false;
|
||||
if (!nonIntraBlockInputTxs.isEmpty()) {
|
||||
for (Tx tx : nonIntraBlockInputTxs) {
|
||||
utxoChanged = utxoChanged || updateBsqUtxoMapFromTx(tx, blockHeight, bsqUTXOMap);
|
||||
if (!txsWithoutInputsFromSameBlock.isEmpty()) {
|
||||
for (Tx tx : txsWithoutInputsFromSameBlock) {
|
||||
updateBsqUtxoMapFromTx(tx, blockHeight, bsqUTXOMap);
|
||||
}
|
||||
}
|
||||
|
||||
// we check if we have any valid BSQ utxo from that tx set
|
||||
if (!intraBlockInputTxs.isEmpty()) {
|
||||
if (utxoChanged) {
|
||||
// We might have InputsFromSameBlock whcih are BTC only but not BSQ, so we cannot
|
||||
// optimize here and need to iterate further.
|
||||
// TODO recursion risk?
|
||||
if (!txsWithInputsFromSameBlock.isEmpty()) {
|
||||
if (recursionCounter < maxRecursions) {
|
||||
updateBsqUtxoMapFromBlock(intraBlockInputTxs, bsqUTXOMap, blockHeight, ++recursionCounter, maxRecursions);
|
||||
updateBsqUtxoMapFromBlock(txsWithInputsFromSameBlock, bsqUTXOMap, blockHeight, ++recursionCounter, maxRecursions);
|
||||
} else {
|
||||
final String msg = "We exceeded our max. recursions for resolveConnectedTxs.\n" +
|
||||
"intraBlockInputTxs=" + intraBlockInputTxs.toString() + "\n" +
|
||||
"nonIntraBlockInputTxs=" + nonIntraBlockInputTxs;
|
||||
"txsWithInputsFromSameBlock=" + txsWithInputsFromSameBlock.toString() + "\n" +
|
||||
"txsWithoutInputsFromSameBlock=" + txsWithoutInputsFromSameBlock;
|
||||
log.warn(msg);
|
||||
if (DevEnv.DEV_MODE)
|
||||
throw new RuntimeException(msg);
|
||||
}
|
||||
} else {
|
||||
final String msg = "If we have intraBlockInputTxs we must have had got the utxoChanged, otherwise we cannot " +
|
||||
"satisfy the open intraBlockInputTxs.\n" +
|
||||
"intraBlockInputTxs=" + intraBlockInputTxs.toString() + "\n" +
|
||||
"nonIntraBlockInputTxs=" + nonIntraBlockInputTxs;
|
||||
log.warn(msg);
|
||||
if (DevEnv.DEV_MODE)
|
||||
throw new RuntimeException(msg);
|
||||
}
|
||||
log.info("We have no more txsWithInputsFromSameBlock.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -205,14 +197,14 @@ abstract public class BsqBlockchainService {
|
|||
String txId = tx.getId();
|
||||
List<TxOutput> outputs = tx.getOutputs();
|
||||
boolean utxoChanged = false;
|
||||
Coin availableValue = Coin.ZERO;
|
||||
long availableValue = 0;
|
||||
for (TxInput input : tx.getInputs()) {
|
||||
String spendingTxId = input.getSpendingTxId();
|
||||
final int spendingTxOutputIndex = input.getSpendingTxOutputIndex();
|
||||
if (bsqUTXOMap.containsTuple(spendingTxId, spendingTxOutputIndex)) {
|
||||
BsqUTXO bsqUTXO = bsqUTXOMap.getByTuple(spendingTxId, spendingTxOutputIndex);
|
||||
log.debug("input value " + bsqUTXO.getValue());
|
||||
availableValue = availableValue.add(bsqUTXO.getValue());
|
||||
availableValue = availableValue + bsqUTXO.getValue();
|
||||
|
||||
bsqUTXOMap.removeByTuple(spendingTxId, spendingTxOutputIndex);
|
||||
utxoChanged = true;
|
||||
|
@ -223,18 +215,13 @@ abstract public class BsqBlockchainService {
|
|||
}
|
||||
|
||||
// If we have an input spending tokens we iterate the outputs
|
||||
if (availableValue.isPositive()) {
|
||||
if (availableValue > 0) {
|
||||
|
||||
// We use order of output index. An output is a BSQ utxo as long there is enough input value
|
||||
for (int outputIndex = 0; outputIndex < outputs.size(); outputIndex++) {
|
||||
TxOutput txOutput = outputs.get(outputIndex);
|
||||
|
||||
if (availableValue.isZero()) {
|
||||
log.debug("We don't have anymore BSQ to spend");
|
||||
break;
|
||||
} else {
|
||||
availableValue = availableValue.subtract(txOutput.getValue());
|
||||
if (!availableValue.isNegative()) {
|
||||
availableValue = availableValue - txOutput.getValue();
|
||||
if (availableValue >= 0) {
|
||||
// We are spending available tokens
|
||||
BsqUTXO bsqUTXO = new BsqUTXO(txId,
|
||||
blockHeight,
|
||||
|
@ -242,7 +229,7 @@ abstract public class BsqBlockchainService {
|
|||
txOutput);
|
||||
bsqUTXOMap.putByTuple(txId, outputIndex, bsqUTXO);
|
||||
|
||||
if (availableValue.isZero()) {
|
||||
if (availableValue == 0) {
|
||||
log.debug("We don't have anymore BSQ to spend");
|
||||
break;
|
||||
}
|
||||
|
@ -255,12 +242,11 @@ abstract public class BsqBlockchainService {
|
|||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO write that warning to a handler
|
||||
if (availableValue.isPositive()) {
|
||||
if (availableValue > 0) {
|
||||
log.warn("BSQ have been left which was not spent. Burned BSQ amount={}, tx={}",
|
||||
availableValue.value,
|
||||
availableValue,
|
||||
tx.toString());
|
||||
}
|
||||
}
|
||||
|
@ -285,16 +271,4 @@ abstract public class BsqBlockchainService {
|
|||
}
|
||||
checkArgument(!bsqUTXOMap.isEmpty(), "Genesis tx need to have BSQ utxo when parsing genesis block");
|
||||
}
|
||||
|
||||
void printUtxoMap(BsqUTXOMap bsqUTXOMap) {
|
||||
if (log.isInfoEnabled() || log.isDebugEnabled() || log.isTraceEnabled()) {
|
||||
StringBuilder sb = new StringBuilder("bsqUTXOMap:\n");
|
||||
bsqUTXOMap.entrySet().stream().forEach(e -> {
|
||||
sb.append("TxId:Index = ").append(e.getKey()).append("\n");
|
||||
sb.append(" UTXO = ").append(e.getValue().toString()).append("\n");
|
||||
}
|
||||
);
|
||||
log.error(sb.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
package io.bisq.core.dao.blockchain;
|
||||
|
||||
import lombok.Value;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.script.Script;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -30,7 +29,7 @@ import java.util.List;
|
|||
public class BsqUTXO {
|
||||
private final String txId;
|
||||
private final long index;
|
||||
private final Coin value;
|
||||
private final long value;
|
||||
private final int height;
|
||||
private final boolean isBsqCoinBase;
|
||||
private final Script script;
|
||||
|
@ -40,7 +39,7 @@ public class BsqUTXO {
|
|||
// We do not support raw MS for BSQ but lets see if is needed anyway, might be removed
|
||||
private final List<String> addresses;
|
||||
|
||||
private BsqUTXO(String txId, int index, Coin value, int height, boolean isBsqCoinBase, Script script, List<String> addresses) {
|
||||
private BsqUTXO(String txId, int index, long value, int height, boolean isBsqCoinBase, Script script, List<String> addresses) {
|
||||
this.txId = txId;
|
||||
this.index = index;
|
||||
this.value = value;
|
||||
|
@ -61,4 +60,18 @@ public class BsqUTXO {
|
|||
output.getScript(),
|
||||
output.getAddresses());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BsqUTXO{" +
|
||||
"\n txId='" + txId + '\'' +
|
||||
",\n index=" + index +
|
||||
",\n value=" + value +
|
||||
",\n height=" + height +
|
||||
",\n isBsqCoinBase=" + isBsqCoinBase +
|
||||
",\n script=" + script +
|
||||
",\n utxoId='" + utxoId + '\'' +
|
||||
",\n addresses=" + addresses +
|
||||
"\n}";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,12 @@ public class BsqUTXOMap extends HashMap<BsqUTXOMap.TxIdIndexTuple, BsqUTXO> {
|
|||
return super.remove(new TxIdIndexTuple(txId, index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BsqUTXOMap " + super.toString();
|
||||
}
|
||||
|
||||
|
||||
@Value
|
||||
static class TxIdIndexTuple {
|
||||
private final String txId;
|
||||
|
@ -45,7 +51,7 @@ public class BsqUTXOMap extends HashMap<BsqUTXOMap.TxIdIndexTuple, BsqUTXO> {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return txId + ":" + index;
|
||||
return "\nTxIdIndexTuple(" + txId + ":" + index + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,4 +26,15 @@ public class Tx {
|
|||
private final String id;
|
||||
private final List<TxInput> inputs;
|
||||
private final List<TxOutput> outputs;
|
||||
private final long time; // in ms from epoche
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Tx{" +
|
||||
"\nid='" + id + '\'' +
|
||||
",\ninputs=" + inputs +
|
||||
",\noutputs=" + outputs +
|
||||
",\ntime=" + time +
|
||||
"}\n";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,4 +24,13 @@ public class TxInput {
|
|||
private final int spendingTxOutputIndex;
|
||||
private final String spendingTxId;
|
||||
private final String txId;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TxInput{" +
|
||||
"\nspendingTxOutputIndex=" + spendingTxOutputIndex +
|
||||
",\nspendingTxId='" + spendingTxId + '\'' +
|
||||
",\ntxId='" + txId + '\'' +
|
||||
"}\n";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
package io.bisq.core.dao.blockchain;
|
||||
|
||||
import lombok.Value;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.script.Script;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -26,7 +25,17 @@ import java.util.List;
|
|||
@Value
|
||||
public class TxOutput {
|
||||
private final int index;
|
||||
private final Coin value;
|
||||
private final long value;
|
||||
private final List<String> addresses;
|
||||
private final Script script;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TxOutput{" +
|
||||
"\nindex=" + index +
|
||||
",\nvalue=" + value +
|
||||
",\naddresses=" + addresses +
|
||||
",\nscript=" + script +
|
||||
"}\n";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,15 +19,11 @@ package io.bisq.core.user;
|
|||
|
||||
import io.bisq.common.app.Version;
|
||||
import io.bisq.common.persistance.Persistable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public final class BlockChainExplorer implements Persistable {
|
||||
// That object is saved to disc. We need to take care of changes to not break deserialization.
|
||||
private static final long serialVersionUID = Version.LOCAL_DB_VERSION;
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(BlockChainExplorer.class);
|
||||
|
||||
public final String name;
|
||||
public final String txUrl;
|
||||
public final String addressUrl;
|
||||
|
|
|
@ -128,6 +128,8 @@ public final class Preferences implements Persistable {
|
|||
private final ArrayList<CryptoCurrency> cryptoCurrencies;
|
||||
private BlockChainExplorer blockChainExplorerMainNet;
|
||||
private BlockChainExplorer blockChainExplorerTestNet;
|
||||
private BlockChainExplorer bsqBlockChainExplorer = new BlockChainExplorer("bisq", "https://explorer.bisq.io/tx.html?tx=",
|
||||
"https://explorer.bisq.io/Address.html?addr=");
|
||||
private String backupDirectory;
|
||||
private boolean autoSelectArbitrators = true;
|
||||
private final Map<String, Boolean> dontShowAgainMap;
|
||||
|
@ -135,7 +137,6 @@ public final class Preferences implements Persistable {
|
|||
private boolean useTorForBitcoinJ = true;
|
||||
|
||||
private boolean showOwnOffersInOfferBook = true;
|
||||
private Locale preferredLocale;
|
||||
private TradeCurrency preferredTradeCurrency;
|
||||
private long withdrawalTxFeeInBytes = 100;
|
||||
private boolean useCustomWithdrawalTxFee = false;
|
||||
|
@ -231,16 +232,11 @@ public final class Preferences implements Persistable {
|
|||
|
||||
setBlockChainExplorerTestNet(persisted.getBlockChainExplorerTestNet());
|
||||
setBlockChainExplorerMainNet(persisted.getBlockChainExplorerMainNet());
|
||||
setBsqBlockChainExplorer(persisted.getBsqBlockChainExplorer());
|
||||
|
||||
setUseCustomWithdrawalTxFee(persisted.useCustomWithdrawalTxFee);
|
||||
setWithdrawalTxFeeInBytes(persisted.withdrawalTxFeeInBytes);
|
||||
|
||||
// In case of an older version without that data we set it to defaults
|
||||
if (blockChainExplorerTestNet == null)
|
||||
setBlockChainExplorerTestNet(blockChainExplorersTestNet.get(0));
|
||||
if (blockChainExplorerMainNet == null)
|
||||
setBlockChainExplorerTestNet(blockChainExplorersMainNet.get(0));
|
||||
|
||||
backupDirectory = persisted.getBackupDirectory();
|
||||
autoSelectArbitrators = persisted.getAutoSelectArbitrators();
|
||||
dontShowAgainMap = persisted.getDontShowAgainMap();
|
||||
|
@ -297,7 +293,6 @@ public final class Preferences implements Persistable {
|
|||
setBlockChainExplorerMainNet(blockChainExplorersMainNet.get(0));
|
||||
|
||||
dontShowAgainMap = new HashMap<>();
|
||||
preferredLocale = getDefaultLocale();
|
||||
preferredTradeCurrency = getDefaultTradeCurrency();
|
||||
maxPriceDistanceInPercent = 0.1;
|
||||
|
||||
|
@ -519,6 +514,11 @@ public final class Preferences implements Persistable {
|
|||
storage.queueUpForSave();
|
||||
}
|
||||
|
||||
public void setBsqBlockChainExplorer(BlockChainExplorer bsqBlockChainExplorer) {
|
||||
this.bsqBlockChainExplorer = bsqBlockChainExplorer;
|
||||
storage.queueUpForSave();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Getter
|
||||
|
@ -709,6 +709,10 @@ public final class Preferences implements Persistable {
|
|||
return selectedPaymentAccountForCreateOffer;
|
||||
}
|
||||
|
||||
public BlockChainExplorer getBsqBlockChainExplorer() {
|
||||
return bsqBlockChainExplorer;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
|
|
|
@ -20,6 +20,7 @@ package io.bisq.core.dao.blockchain;
|
|||
import com.neemre.btcdcli4j.core.BitcoindException;
|
||||
import com.neemre.btcdcli4j.core.CommunicationException;
|
||||
import com.neemre.btcdcli4j.core.domain.*;
|
||||
import io.bisq.common.util.Tuple2;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.junit.After;
|
||||
|
@ -87,14 +88,14 @@ public class BsqBlockchainServiceTest {
|
|||
|
||||
service.buildBlocks(BLOCK_0, BLOCK_0);
|
||||
|
||||
BsqUTXOMap bsqUTXOMap = parseAllBlocksFromGenesis();
|
||||
BsqUTXOMap bsqUTXOMap = parseAllBlocksFromGenesis().second;
|
||||
|
||||
BsqUTXO bsqUTXO1 = bsqUTXOMap.getByTuple(GEN_TX_ID, 0);
|
||||
BsqUTXO bsqUTXO2 = bsqUTXOMap.getByTuple(GEN_TX_ID, 1);
|
||||
assertEquals(bsqUTXO1.getUtxoId(), getUTXOId(GEN_TX_ID, 0));
|
||||
assertEquals(bsqUTXO2.getUtxoId(), getUTXOId(GEN_TX_ID, 1));
|
||||
assertEquals(ADDRESS_GEN_1_VALUE, bsqUTXO1.getValue().value);
|
||||
assertEquals(ADDRESS_GEN_2_VALUE, bsqUTXO2.getValue().value);
|
||||
assertEquals(ADDRESS_GEN_1_VALUE, bsqUTXO1.getValue());
|
||||
assertEquals(ADDRESS_GEN_2_VALUE, bsqUTXO2.getValue());
|
||||
assertEquals(2, bsqUTXOMap.size());
|
||||
}
|
||||
|
||||
|
@ -126,14 +127,14 @@ public class BsqBlockchainServiceTest {
|
|||
|
||||
service.buildBlocks(BLOCK_0, BLOCK_1);
|
||||
|
||||
BsqUTXOMap bsqUTXOMap = parseAllBlocksFromGenesis();
|
||||
BsqUTXOMap bsqUTXOMap = parseAllBlocksFromGenesis().second;
|
||||
|
||||
BsqUTXO bsqUTXO1 = bsqUTXOMap.getByTuple(GEN_TX_ID, 0);
|
||||
BsqUTXO bsqUTXO2 = bsqUTXOMap.getByTuple(TX1_ID, 0);
|
||||
assertEquals(bsqUTXO1.getUtxoId(), getUTXOId(GEN_TX_ID, 0));
|
||||
assertEquals(bsqUTXO2.getUtxoId(), getUTXOId(TX1_ID, 0));
|
||||
assertEquals(ADDRESS_GEN_1_VALUE, bsqUTXO1.getValue().value);
|
||||
assertEquals(ADDRESS_TX_1_VALUE, bsqUTXO2.getValue().value);
|
||||
assertEquals(ADDRESS_GEN_1_VALUE, bsqUTXO1.getValue());
|
||||
assertEquals(ADDRESS_TX_1_VALUE, bsqUTXO2.getValue());
|
||||
assertEquals(2, bsqUTXOMap.size());
|
||||
}
|
||||
|
||||
|
@ -178,14 +179,14 @@ public class BsqBlockchainServiceTest {
|
|||
|
||||
service.buildBlocks(BLOCK_0, BLOCK_1);
|
||||
|
||||
BsqUTXOMap bsqUTXOMap = parseAllBlocksFromGenesis();
|
||||
BsqUTXOMap bsqUTXOMap = parseAllBlocksFromGenesis().second;
|
||||
|
||||
BsqUTXO bsqUTXO1 = bsqUTXOMap.getByTuple(GEN_TX_ID, 0);
|
||||
BsqUTXO bsqUTXO2 = bsqUTXOMap.getByTuple(TX2_ID, 0);
|
||||
assertEquals(bsqUTXO1.getUtxoId(), getUTXOId(GEN_TX_ID, 0));
|
||||
assertEquals(bsqUTXO2.getUtxoId(), getUTXOId(TX2_ID, 0));
|
||||
assertEquals(ADDRESS_GEN_1_VALUE, bsqUTXO1.getValue().value);
|
||||
assertEquals(ADDRESS_TX_2_VALUE, bsqUTXO2.getValue().value);
|
||||
assertEquals(ADDRESS_GEN_1_VALUE, bsqUTXO1.getValue());
|
||||
assertEquals(ADDRESS_TX_2_VALUE, bsqUTXO2.getValue());
|
||||
assertEquals(2, bsqUTXOMap.size());
|
||||
}
|
||||
|
||||
|
@ -230,14 +231,14 @@ public class BsqBlockchainServiceTest {
|
|||
|
||||
service.buildBlocks(BLOCK_0, BLOCK_2);
|
||||
|
||||
BsqUTXOMap bsqUTXOMap = parseAllBlocksFromGenesis();
|
||||
BsqUTXOMap bsqUTXOMap = parseAllBlocksFromGenesis().second;
|
||||
|
||||
BsqUTXO bsqUTXO1 = bsqUTXOMap.getByTuple(GEN_TX_ID, 0);
|
||||
BsqUTXO bsqUTXO2 = bsqUTXOMap.getByTuple(TX2_ID, 0);
|
||||
assertEquals(bsqUTXO1.getUtxoId(), getUTXOId(GEN_TX_ID, 0));
|
||||
assertEquals(bsqUTXO2.getUtxoId(), getUTXOId(TX2_ID, 0));
|
||||
assertEquals(ADDRESS_GEN_1_VALUE, bsqUTXO1.getValue().value);
|
||||
assertEquals(ADDRESS_TX_2_VALUE, bsqUTXO2.getValue().value);
|
||||
assertEquals(ADDRESS_GEN_1_VALUE, bsqUTXO1.getValue());
|
||||
assertEquals(ADDRESS_TX_2_VALUE, bsqUTXO2.getValue());
|
||||
assertEquals(2, bsqUTXOMap.size());
|
||||
}
|
||||
|
||||
|
@ -284,11 +285,11 @@ public class BsqBlockchainServiceTest {
|
|||
|
||||
service.buildBlocks(BLOCK_0, BLOCK_1);
|
||||
|
||||
BsqUTXOMap bsqUTXOMap = parseAllBlocksFromGenesis();
|
||||
BsqUTXOMap bsqUTXOMap = parseAllBlocksFromGenesis().second;
|
||||
|
||||
BsqUTXO bsqUTXO1 = bsqUTXOMap.getByTuple(TX2_ID, 0);
|
||||
assertEquals(bsqUTXO1.getUtxoId(), getUTXOId(TX2_ID, 0));
|
||||
assertEquals(ADDRESS_GEN_1_VALUE + ADDRESS_GEN_2_VALUE, bsqUTXO1.getValue().value);
|
||||
assertEquals(ADDRESS_GEN_1_VALUE + ADDRESS_GEN_2_VALUE, bsqUTXO1.getValue());
|
||||
assertEquals(1, bsqUTXOMap.size());
|
||||
}
|
||||
|
||||
|
@ -301,6 +302,7 @@ public class BsqBlockchainServiceTest {
|
|||
RawTransaction genesisRawTransaction = new RawTransaction();
|
||||
genesisRawTransaction.setBlockHash("BlockHash" + height);
|
||||
genesisRawTransaction.setTxId(txId);
|
||||
genesisRawTransaction.setTime(new Date().getTime() / 1000);
|
||||
return genesisRawTransaction;
|
||||
}
|
||||
|
||||
|
@ -376,7 +378,7 @@ public class BsqBlockchainServiceTest {
|
|||
}
|
||||
|
||||
|
||||
private BsqUTXOMap parseAllBlocksFromGenesis()
|
||||
private Tuple2<Set<BsqBlock>, BsqUTXOMap> parseAllBlocksFromGenesis()
|
||||
throws BitcoindException, CommunicationException, BsqBlockchainException {
|
||||
return service.parseAllBlocksFromGenesis(service.requestChainHeadHeight(),
|
||||
BLOCK_0,
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
package io.bisq.gui.main.dao.wallet;
|
||||
|
||||
import io.bisq.core.btc.wallet.BsqWalletService;
|
||||
import io.bisq.gui.main.overlays.popups.Popup;
|
||||
import io.bisq.gui.util.BsqFormatter;
|
||||
import javafx.scene.control.TextField;
|
||||
import org.bitcoinj.core.*;
|
||||
|
@ -49,6 +48,7 @@ public class BalanceUtil {
|
|||
|
||||
public void setBalanceTextField(TextField balanceTextField) {
|
||||
this.balanceTextField = balanceTextField;
|
||||
balanceTextField.setMouseTransparent(false);
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
|
@ -91,8 +91,6 @@ public class BalanceUtil {
|
|||
private void requestUtxo() {
|
||||
bsqWalletService.requestBsqUtxo(() -> {
|
||||
balanceTextField.setText(formatter.formatCoinWithCode(bsqWalletService.getAvailableBalance()));
|
||||
}, errorMessage -> {
|
||||
new Popup<>().warning(errorMessage);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ import io.bisq.gui.main.dao.DaoView;
|
|||
import io.bisq.gui.main.dao.wallet.dashboard.BsqDashboardView;
|
||||
import io.bisq.gui.main.dao.wallet.receive.BsqReceiveView;
|
||||
import io.bisq.gui.main.dao.wallet.send.BsqSendView;
|
||||
import io.bisq.gui.main.dao.wallet.tx.BsqTransactionsView;
|
||||
import io.bisq.gui.main.dao.wallet.tx.BsqTxView;
|
||||
import io.bisq.gui.util.Colors;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.fxml.FXML;
|
||||
|
@ -78,7 +78,7 @@ public class BsqWalletView extends ActivatableViewAndModel {
|
|||
dashboard = new MenuItem(navigation, toggleGroup, Res.get("shared.dashboard"), BsqDashboardView.class, AwesomeIcon.DASHBOARD);
|
||||
send = new MenuItem(navigation, toggleGroup, Res.get("dao.wallet.menuItem.send"), BsqSendView.class, AwesomeIcon.SIGNOUT);
|
||||
receive = new MenuItem(navigation, toggleGroup, Res.get("dao.wallet.menuItem.receive"), BsqReceiveView.class, AwesomeIcon.SIGNIN);
|
||||
transactions = new MenuItem(navigation, toggleGroup, Res.get("dao.wallet.menuItem.transactions"), BsqTransactionsView.class, AwesomeIcon.TABLE);
|
||||
transactions = new MenuItem(navigation, toggleGroup, Res.get("dao.wallet.menuItem.transactions"), BsqTxView.class, AwesomeIcon.TABLE);
|
||||
leftVBox.getChildren().addAll(dashboard, send, receive, transactions);
|
||||
}
|
||||
|
||||
|
@ -121,7 +121,7 @@ public class BsqWalletView extends ActivatableViewAndModel {
|
|||
if (view instanceof BsqDashboardView) dashboard.setSelected(true);
|
||||
else if (view instanceof BsqSendView) send.setSelected(true);
|
||||
else if (view instanceof BsqReceiveView) receive.setSelected(true);
|
||||
else if (view instanceof BsqTransactionsView) transactions.setSelected(true);
|
||||
else if (view instanceof BsqTxView) transactions.setSelected(true);
|
||||
}
|
||||
|
||||
public Class<? extends View> getSelectedViewClass() {
|
||||
|
|
|
@ -20,12 +20,10 @@ package io.bisq.gui.main.dao.wallet.send;
|
|||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import io.bisq.common.app.DevEnv;
|
||||
import io.bisq.common.locale.Res;
|
||||
import io.bisq.core.btc.InsufficientFundsException;
|
||||
import io.bisq.core.btc.exceptions.TransactionVerificationException;
|
||||
import io.bisq.core.btc.exceptions.WalletException;
|
||||
import io.bisq.core.btc.wallet.BsqWalletService;
|
||||
import io.bisq.core.btc.wallet.BtcWalletService;
|
||||
import io.bisq.core.provider.fee.FeeService;
|
||||
import io.bisq.core.util.CoinUtil;
|
||||
import io.bisq.gui.common.view.ActivatableView;
|
||||
import io.bisq.gui.common.view.FxmlView;
|
||||
|
@ -35,12 +33,13 @@ import io.bisq.gui.main.overlays.popups.Popup;
|
|||
import io.bisq.gui.util.BSFormatter;
|
||||
import io.bisq.gui.util.BsqFormatter;
|
||||
import io.bisq.gui.util.Layout;
|
||||
import io.bisq.gui.util.validation.BsqAddressValidator;
|
||||
import io.bisq.gui.util.validation.BsqValidator;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import org.bitcoinj.core.AddressFormatException;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.InsufficientMoneyException;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
@ -52,56 +51,65 @@ import static io.bisq.gui.util.FormBuilder.*;
|
|||
@FxmlView
|
||||
public class BsqSendView extends ActivatableView<GridPane, Void> {
|
||||
|
||||
|
||||
private TextField balanceTextField;
|
||||
|
||||
private final BsqWalletService bsqWalletService;
|
||||
private final BtcWalletService btcWalletService;
|
||||
private final FeeService feeService;
|
||||
private final BSFormatter bsqFormatter;
|
||||
private final BSFormatter btcFormatter;
|
||||
private final BalanceUtil balanceUtil;
|
||||
private BsqValidator bsqValidator;
|
||||
private BsqAddressValidator bsqAddressValidator;
|
||||
|
||||
private int gridRow = 0;
|
||||
private InputTextField amountInputTextField;
|
||||
private Button sendButton;
|
||||
private InputTextField receiversAddressInputTextField;
|
||||
private ChangeListener<Boolean> focusOutListener;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor, lifecycle
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
private BsqSendView(BsqWalletService bsqWalletService, BtcWalletService btcWalletService, FeeService feeService, BsqFormatter bsqFormatter, BSFormatter btcFormatter, BalanceUtil balanceUtil) {
|
||||
private BsqSendView(BsqWalletService bsqWalletService, BtcWalletService btcWalletService,
|
||||
BsqFormatter bsqFormatter, BSFormatter btcFormatter,
|
||||
BalanceUtil balanceUtil, BsqValidator bsqValidator, BsqAddressValidator bsqAddressValidator) {
|
||||
this.bsqWalletService = bsqWalletService;
|
||||
this.btcWalletService = btcWalletService;
|
||||
this.feeService = feeService;
|
||||
|
||||
this.bsqFormatter = bsqFormatter;
|
||||
this.btcFormatter = btcFormatter;
|
||||
this.balanceUtil = balanceUtil;
|
||||
this.bsqValidator = bsqValidator;
|
||||
this.bsqAddressValidator = bsqAddressValidator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
addTitledGroupBg(root, gridRow, 1, Res.get("shared.balance"));
|
||||
balanceTextField = addLabelTextField(root, gridRow, Res.get("shared.bsqBalance"), Layout.FIRST_ROW_DISTANCE).second;
|
||||
TextField balanceTextField = addLabelTextField(root, gridRow, Res.get("shared.bsqBalance"), Layout.FIRST_ROW_DISTANCE).second;
|
||||
balanceUtil.setBalanceTextField(balanceTextField);
|
||||
balanceUtil.initialize();
|
||||
|
||||
addTitledGroupBg(root, ++gridRow, 3, Res.get("dao.wallet.send.sendFunds"), Layout.GROUP_DISTANCE);
|
||||
amountInputTextField = addLabelInputTextField(root, gridRow, Res.get("dao.wallet.send.amount"), Layout.FIRST_ROW_AND_GROUP_DISTANCE).second;
|
||||
amountInputTextField.setPromptText(Res.get("dao.wallet.send.setAmount", Transaction.MIN_NONDUST_OUTPUT.value));
|
||||
amountInputTextField.setValidator(bsqValidator);
|
||||
|
||||
receiversAddressInputTextField = addLabelInputTextField(root, ++gridRow,
|
||||
Res.get("dao.wallet.send.receiverAddress")).second;
|
||||
receiversAddressInputTextField.setPromptText(Res.get("dao.wallet.send.setDestinationAddress"));
|
||||
receiversAddressInputTextField.setValidator(bsqAddressValidator);
|
||||
|
||||
focusOutListener = (observable, oldValue, newValue) -> {
|
||||
if (!newValue)
|
||||
verifyInputs();
|
||||
};
|
||||
|
||||
sendButton = addButtonAfterGroup(root, ++gridRow, Res.get("dao.wallet.send.send"));
|
||||
|
||||
if (DevEnv.DEV_MODE) {
|
||||
amountInputTextField.setText("2.730"); // 2730 is dust limit
|
||||
receiversAddressInputTextField.setText("mpaZiEh8gSr4LcH11FrLdRY57aArt88qtg");
|
||||
receiversAddressInputTextField.setText("mqLgu2GH1pZntcsuugNtZuczLfZx8P5sdK");
|
||||
}
|
||||
|
||||
sendButton.setOnAction((event) -> {
|
||||
|
@ -150,11 +158,10 @@ public class BsqSendView extends ActivatableView<GridPane, Void> {
|
|||
})
|
||||
.closeButtonText(Res.get("shared.cancel"))
|
||||
.show();
|
||||
} catch (AddressFormatException | InsufficientFundsException |
|
||||
TransactionVerificationException | WalletException | InsufficientMoneyException e) {
|
||||
log.error(e.toString());
|
||||
e.printStackTrace();
|
||||
new Popup<>().warning(e.toString()).show();
|
||||
} catch (Throwable t) {
|
||||
log.error(t.toString());
|
||||
t.printStackTrace();
|
||||
new Popup<>().warning(t.getMessage()).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -162,11 +169,22 @@ public class BsqSendView extends ActivatableView<GridPane, Void> {
|
|||
@Override
|
||||
protected void activate() {
|
||||
balanceUtil.activate();
|
||||
receiversAddressInputTextField.focusedProperty().addListener(focusOutListener);
|
||||
amountInputTextField.focusedProperty().addListener(focusOutListener);
|
||||
verifyInputs();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deactivate() {
|
||||
balanceUtil.deactivate();
|
||||
receiversAddressInputTextField.focusedProperty().removeListener(focusOutListener);
|
||||
amountInputTextField.focusedProperty().removeListener(focusOutListener);
|
||||
}
|
||||
|
||||
private void verifyInputs() {
|
||||
boolean isValid = bsqAddressValidator.validate(receiversAddressInputTextField.getText()).isValid &&
|
||||
bsqValidator.validate(amountInputTextField.getText()).isValid;
|
||||
sendButton.setDisable(!isValid);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
* This file is part of bisq.
|
||||
*
|
||||
* bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.bisq.gui.main.dao.wallet.tx;
|
||||
|
||||
import io.bisq.gui.common.view.ActivatableView;
|
||||
import io.bisq.gui.common.view.FxmlView;
|
||||
import javafx.scene.layout.GridPane;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@FxmlView
|
||||
public class BsqTransactionsView extends ActivatableView<GridPane, Void> {
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor, lifecycle
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
private BsqTransactionsView() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activate() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deactivate() {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* This file is part of bisq.
|
||||
*
|
||||
* bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General 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 io.bisq.gui.main.dao.wallet.tx;
|
||||
|
||||
import io.bisq.common.locale.Res;
|
||||
import io.bisq.core.btc.listeners.TxConfidenceListener;
|
||||
import io.bisq.core.btc.wallet.BsqWalletService;
|
||||
import io.bisq.gui.components.indicator.TxConfidenceIndicator;
|
||||
import io.bisq.gui.util.GUIUtil;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.core.TransactionConfidence;
|
||||
import org.bitcoinj.core.TransactionOutput;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
|
||||
@ToString
|
||||
@Slf4j
|
||||
class BsqTxListItem {
|
||||
@Getter
|
||||
private final Transaction transaction;
|
||||
private BsqWalletService bsqWalletService;
|
||||
@Getter
|
||||
private final Date date;
|
||||
@Getter
|
||||
private final String txId;
|
||||
@Getter
|
||||
private int confirmations = 0;
|
||||
@Getter
|
||||
private final String address;
|
||||
@Getter
|
||||
private final String direction;
|
||||
@Getter
|
||||
private final Coin amount;
|
||||
@Getter
|
||||
private boolean received;
|
||||
@Getter
|
||||
private TxConfidenceIndicator txConfidenceIndicator;
|
||||
|
||||
private TxConfidenceListener txConfidenceListener;
|
||||
|
||||
public BsqTxListItem(Transaction transaction, BsqWalletService bsqWalletService) {
|
||||
this.transaction = transaction;
|
||||
this.bsqWalletService = bsqWalletService;
|
||||
|
||||
txId = transaction.getHashAsString();
|
||||
date = transaction.getUpdateTime();
|
||||
|
||||
setupConfidence(bsqWalletService);
|
||||
|
||||
checkNotNull(transaction, "transaction must not be null as we only have list items from transactions " +
|
||||
"which are available in the wallet");
|
||||
|
||||
Coin valueSentToMe = bsqWalletService.getValueSentToMeForTransaction(transaction);
|
||||
Coin valueSentFromMe = bsqWalletService.getValueSentFromMeForTransaction(transaction);
|
||||
|
||||
if (valueSentToMe.compareTo(valueSentFromMe) > 0) {
|
||||
amount = valueSentToMe.subtract(valueSentFromMe);
|
||||
direction = Res.get("funds.tx.direction.receivedWith");
|
||||
received = true;
|
||||
} else if (valueSentToMe.compareTo(valueSentFromMe) < 0) {
|
||||
amount = valueSentFromMe.subtract(valueSentToMe);
|
||||
direction = Res.get("funds.tx.direction.sentTo");
|
||||
received = false;
|
||||
} else {
|
||||
amount = Coin.ZERO;
|
||||
direction = "";
|
||||
}
|
||||
String result = null;
|
||||
for (TransactionOutput output : transaction.getOutputs()) {
|
||||
if (!bsqWalletService.isTransactionOutputMine(output)) {
|
||||
if (output.getScriptPubKey().isSentToAddress()
|
||||
|| output.getScriptPubKey().isPayToScriptHash()) {
|
||||
result = output.getScriptPubKey().getToAddress(bsqWalletService.getParams()).toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
address = result != null ? result : "";
|
||||
}
|
||||
|
||||
private void setupConfidence(BsqWalletService bsqWalletService) {
|
||||
txConfidenceIndicator = new TxConfidenceIndicator();
|
||||
txConfidenceIndicator.setId("funds-confidence");
|
||||
Tooltip tooltip = new Tooltip();
|
||||
txConfidenceIndicator.setProgress(0);
|
||||
txConfidenceIndicator.setPrefHeight(30);
|
||||
txConfidenceIndicator.setPrefWidth(30);
|
||||
Tooltip.install(txConfidenceIndicator, tooltip);
|
||||
|
||||
txConfidenceListener = new TxConfidenceListener(txId) {
|
||||
@Override
|
||||
public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
|
||||
updateConfidence(confidence, tooltip);
|
||||
}
|
||||
};
|
||||
bsqWalletService.addTxConfidenceListener(txConfidenceListener);
|
||||
updateConfidence(bsqWalletService.getConfidenceForTxId(txId), tooltip);
|
||||
}
|
||||
|
||||
private void updateConfidence(TransactionConfidence confidence, Tooltip tooltip) {
|
||||
if (confidence != null) {
|
||||
GUIUtil.updateConfidence(confidence, tooltip, txConfidenceIndicator);
|
||||
confirmations = confidence.getDepthInBlocks();
|
||||
}
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
bsqWalletService.removeTxConfidenceListener(txConfidenceListener);
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
-->
|
||||
|
||||
<?import javafx.scene.layout.*?>
|
||||
<GridPane fx:id="root" fx:controller="io.bisq.gui.main.dao.wallet.tx.BsqTransactionsView"
|
||||
<GridPane fx:id="root" fx:controller="io.bisq.gui.main.dao.wallet.tx.BsqTxView"
|
||||
hgap="5.0" vgap="5.0"
|
||||
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
|
||||
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="-10.0"
|
287
gui/src/main/java/io/bisq/gui/main/dao/wallet/tx/BsqTxView.java
Normal file
287
gui/src/main/java/io/bisq/gui/main/dao/wallet/tx/BsqTxView.java
Normal file
|
@ -0,0 +1,287 @@
|
|||
/*
|
||||
* This file is part of bisq.
|
||||
*
|
||||
* bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General 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 io.bisq.gui.main.dao.wallet.tx;
|
||||
|
||||
import de.jensd.fx.fontawesome.AwesomeIcon;
|
||||
import io.bisq.common.locale.Res;
|
||||
import io.bisq.core.btc.wallet.BsqWalletService;
|
||||
import io.bisq.core.user.Preferences;
|
||||
import io.bisq.gui.common.view.ActivatableView;
|
||||
import io.bisq.gui.common.view.FxmlView;
|
||||
import io.bisq.gui.components.AddressWithIconAndDirection;
|
||||
import io.bisq.gui.components.HyperlinkWithIcon;
|
||||
import io.bisq.gui.main.dao.wallet.BalanceUtil;
|
||||
import io.bisq.gui.util.BsqFormatter;
|
||||
import io.bisq.gui.util.GUIUtil;
|
||||
import io.bisq.gui.util.Layout;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.transformation.SortedList;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.util.Callback;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static io.bisq.gui.util.FormBuilder.addLabelTextField;
|
||||
import static io.bisq.gui.util.FormBuilder.addTitledGroupBg;
|
||||
|
||||
@FxmlView
|
||||
public class BsqTxView extends ActivatableView<GridPane, Void> {
|
||||
|
||||
TableView<BsqTxListItem> tableView;
|
||||
private int gridRow = 0;
|
||||
private BsqFormatter bsqFormatter;
|
||||
private BsqWalletService bsqWalletService;
|
||||
private BalanceUtil balanceUtil;
|
||||
private Preferences preferences;
|
||||
private ListChangeListener<Transaction> walletBsqTransactionsListener;
|
||||
private final ObservableList<BsqTxListItem> observableList = FXCollections.observableArrayList();
|
||||
private final SortedList<BsqTxListItem> sortedList = new SortedList<>(observableList);
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor, lifecycle
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
private BsqTxView(BsqFormatter bsqFormatter, BsqWalletService bsqWalletService,
|
||||
BalanceUtil balanceUtil, Preferences preferences) {
|
||||
this.bsqFormatter = bsqFormatter;
|
||||
this.bsqWalletService = bsqWalletService;
|
||||
this.balanceUtil = balanceUtil;
|
||||
this.preferences = preferences;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
addTitledGroupBg(root, gridRow, 1, Res.get("shared.balance"));
|
||||
TextField balanceTextField = addLabelTextField(root, gridRow, Res.get("shared.bsqBalance"),
|
||||
Layout.FIRST_ROW_DISTANCE).second;
|
||||
balanceUtil.setBalanceTextField(balanceTextField);
|
||||
balanceUtil.initialize();
|
||||
|
||||
tableView = new TableView<>();
|
||||
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
GridPane.setRowIndex(tableView, ++gridRow);
|
||||
GridPane.setColumnSpan(tableView, 2);
|
||||
GridPane.setMargin(tableView, new Insets(40, -10, 5, -10));
|
||||
root.getChildren().add(tableView);
|
||||
|
||||
addDateColumn();
|
||||
addTxIdColumn();
|
||||
addAddressColumn();
|
||||
addAmountColumn();
|
||||
addConfidenceColumn();
|
||||
|
||||
walletBsqTransactionsListener = change -> updateList();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activate() {
|
||||
balanceUtil.activate();
|
||||
bsqWalletService.getWalletBsqTransactions().addListener(walletBsqTransactionsListener);
|
||||
updateList();
|
||||
|
||||
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
|
||||
tableView.setItems(sortedList);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deactivate() {
|
||||
balanceUtil.deactivate();
|
||||
sortedList.comparatorProperty().unbind();
|
||||
bsqWalletService.getWalletBsqTransactions().removeListener(walletBsqTransactionsListener);
|
||||
observableList.forEach(BsqTxListItem::cleanup);
|
||||
}
|
||||
|
||||
private void updateList() {
|
||||
observableList.forEach(BsqTxListItem::cleanup);
|
||||
|
||||
Set<BsqTxListItem> list = bsqWalletService.getWalletBsqTransactions().stream()
|
||||
.map(t -> new BsqTxListItem(t, bsqWalletService))
|
||||
.collect(Collectors.toSet());
|
||||
observableList.setAll(list);
|
||||
}
|
||||
|
||||
private void addDateColumn() {
|
||||
TableColumn<BsqTxListItem, BsqTxListItem> column = new TableColumn<>(Res.get("shared.dateTime"));
|
||||
column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue()));
|
||||
column.setMinWidth(180);
|
||||
column.setMaxWidth(180);
|
||||
column.setCellValueFactory((addressListItem) ->
|
||||
new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
|
||||
column.setCellFactory(
|
||||
new Callback<TableColumn<BsqTxListItem, BsqTxListItem>, TableCell<BsqTxListItem,
|
||||
BsqTxListItem>>() {
|
||||
|
||||
@Override
|
||||
public TableCell<BsqTxListItem, BsqTxListItem> call(TableColumn<BsqTxListItem,
|
||||
BsqTxListItem> column) {
|
||||
return new TableCell<BsqTxListItem, BsqTxListItem>() {
|
||||
|
||||
@Override
|
||||
public void updateItem(final BsqTxListItem item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (item != null && !empty) {
|
||||
setText(bsqFormatter.formatDateTime(item.getDate()));
|
||||
} else {
|
||||
setText("");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
tableView.getColumns().add(column);
|
||||
column.setComparator((o1, o2) -> o1.getDate().compareTo(o2.getDate()));
|
||||
column.setSortType(TableColumn.SortType.DESCENDING);
|
||||
tableView.getSortOrder().add(column);
|
||||
}
|
||||
|
||||
private void addTxIdColumn() {
|
||||
TableColumn<BsqTxListItem, BsqTxListItem> column = new TableColumn<>(Res.get("shared.txId"));
|
||||
column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue()));
|
||||
column.setMinWidth(60);
|
||||
column.setCellFactory(
|
||||
new Callback<TableColumn<BsqTxListItem, BsqTxListItem>, TableCell<BsqTxListItem,
|
||||
BsqTxListItem>>() {
|
||||
|
||||
@Override
|
||||
public TableCell<BsqTxListItem, BsqTxListItem> call(TableColumn<BsqTxListItem,
|
||||
BsqTxListItem> column) {
|
||||
return new TableCell<BsqTxListItem, BsqTxListItem>() {
|
||||
private HyperlinkWithIcon hyperlinkWithIcon;
|
||||
|
||||
@Override
|
||||
public void updateItem(final BsqTxListItem item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (item != null && !empty) {
|
||||
String transactionId = item.getTxId();
|
||||
hyperlinkWithIcon = new HyperlinkWithIcon(transactionId, AwesomeIcon.EXTERNAL_LINK);
|
||||
hyperlinkWithIcon.setOnAction(event -> openTxInBlockExplorer(item));
|
||||
hyperlinkWithIcon.setTooltip(new Tooltip(Res.get("tooltip.openBlockchainForTx", transactionId)));
|
||||
setGraphic(hyperlinkWithIcon);
|
||||
} else {
|
||||
setGraphic(null);
|
||||
if (hyperlinkWithIcon != null)
|
||||
hyperlinkWithIcon.setOnAction(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
tableView.getColumns().add(column);
|
||||
}
|
||||
|
||||
private void addAddressColumn() {
|
||||
TableColumn<BsqTxListItem, BsqTxListItem> column = new TableColumn<>(Res.get("shared.address"));
|
||||
column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue()));
|
||||
column.setMinWidth(140);
|
||||
column.setCellFactory(
|
||||
new Callback<TableColumn<BsqTxListItem, BsqTxListItem>, TableCell<BsqTxListItem,
|
||||
BsqTxListItem>>() {
|
||||
|
||||
@Override
|
||||
public TableCell<BsqTxListItem, BsqTxListItem> call(TableColumn<BsqTxListItem,
|
||||
BsqTxListItem> column) {
|
||||
return new TableCell<BsqTxListItem, BsqTxListItem>() {
|
||||
|
||||
private AddressWithIconAndDirection field;
|
||||
|
||||
@Override
|
||||
public void updateItem(final BsqTxListItem item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (item != null && !empty) {
|
||||
String addressString = item.getAddress();
|
||||
field = new AddressWithIconAndDirection(item.getDirection(), addressString,
|
||||
AwesomeIcon.EXTERNAL_LINK, item.isReceived());
|
||||
field.setOnAction(event -> openAddressInBlockExplorer(item));
|
||||
field.setTooltip(new Tooltip(Res.get("tooltip.openBlockchainForAddress", addressString)));
|
||||
setGraphic(field);
|
||||
} else {
|
||||
setGraphic(null);
|
||||
if (field != null)
|
||||
field.setOnAction(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
tableView.getColumns().add(column);
|
||||
}
|
||||
|
||||
private void addAmountColumn() {
|
||||
TableColumn<BsqTxListItem, String> column = new TableColumn<>(Res.get("shared.amountWithCur", "BSQ"));
|
||||
column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(bsqFormatter.formatCoin(item.getValue().getAmount())));
|
||||
column.setMinWidth(130);
|
||||
tableView.getColumns().add(column);
|
||||
}
|
||||
|
||||
private void addConfidenceColumn() {
|
||||
TableColumn<BsqTxListItem, BsqTxListItem> column = new TableColumn<>(Res.get("shared.confirmations"));
|
||||
column.setMinWidth(130);
|
||||
column.setMaxWidth(130);
|
||||
|
||||
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
|
||||
column.setCellFactory(new Callback<TableColumn<BsqTxListItem, BsqTxListItem>,
|
||||
TableCell<BsqTxListItem, BsqTxListItem>>() {
|
||||
|
||||
@Override
|
||||
public TableCell<BsqTxListItem, BsqTxListItem> call(TableColumn<BsqTxListItem,
|
||||
BsqTxListItem> column) {
|
||||
return new TableCell<BsqTxListItem, BsqTxListItem>() {
|
||||
|
||||
@Override
|
||||
public void updateItem(final BsqTxListItem item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (item != null && !empty) {
|
||||
setGraphic(item.getTxConfidenceIndicator());
|
||||
} else {
|
||||
setGraphic(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
tableView.getColumns().add(column);
|
||||
}
|
||||
|
||||
private void openTxInBlockExplorer(BsqTxListItem item) {
|
||||
if (item.getTxId() != null)
|
||||
GUIUtil.openWebPage(preferences.getBsqBlockChainExplorer().txUrl + item.getTxId());
|
||||
}
|
||||
|
||||
private void openAddressInBlockExplorer(BsqTxListItem item) {
|
||||
if (item.getAddress() != null) {
|
||||
GUIUtil.openWebPage(preferences.getBsqBlockChainExplorer().addressUrl + item.getAddress());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ package io.bisq.gui.main.funds.transactions;
|
|||
|
||||
import io.bisq.common.locale.Res;
|
||||
import io.bisq.core.btc.listeners.TxConfidenceListener;
|
||||
import io.bisq.core.btc.wallet.BsqWalletService;
|
||||
import io.bisq.core.btc.wallet.BtcWalletService;
|
||||
import io.bisq.core.offer.Offer;
|
||||
import io.bisq.core.offer.OpenOffer;
|
||||
|
@ -28,21 +29,22 @@ import io.bisq.gui.components.indicator.TxConfidenceIndicator;
|
|||
import io.bisq.gui.util.BSFormatter;
|
||||
import io.bisq.gui.util.GUIUtil;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import org.bitcoinj.core.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.core.TransactionConfidence;
|
||||
import org.bitcoinj.core.TransactionOutput;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Date;
|
||||
import java.util.Optional;
|
||||
|
||||
@Slf4j
|
||||
class TransactionsListItem {
|
||||
private final Logger log = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
private String dateString;
|
||||
private final Date date;
|
||||
private final String txId;
|
||||
private final BtcWalletService walletService;
|
||||
private final BtcWalletService btcWalletService;
|
||||
private final TxConfidenceIndicator txConfidenceIndicator;
|
||||
private final Tooltip tooltip;
|
||||
@Nullable
|
||||
|
@ -53,65 +55,74 @@ class TransactionsListItem {
|
|||
private TxConfidenceListener txConfidenceListener;
|
||||
private boolean received;
|
||||
private boolean detailsAvailable;
|
||||
private boolean txFeeForBsqPayment = false;
|
||||
private Coin amountAsCoin = Coin.ZERO;
|
||||
private BSFormatter formatter;
|
||||
private int confirmations = 0;
|
||||
|
||||
public TransactionsListItem() {
|
||||
date = null;
|
||||
walletService = null;
|
||||
btcWalletService = null;
|
||||
txConfidenceIndicator = null;
|
||||
tooltip = null;
|
||||
txId = null;
|
||||
}
|
||||
|
||||
public TransactionsListItem(Transaction transaction, BtcWalletService walletService, Optional<Tradable> tradableOptional, BSFormatter formatter) {
|
||||
public TransactionsListItem(Transaction transaction,
|
||||
BtcWalletService btcWalletService,
|
||||
BsqWalletService bsqWalletService,
|
||||
Optional<Tradable> tradableOptional,
|
||||
BSFormatter formatter) {
|
||||
this.formatter = formatter;
|
||||
txId = transaction.getHashAsString();
|
||||
this.walletService = walletService;
|
||||
this.btcWalletService = btcWalletService;
|
||||
|
||||
Coin valueSentToMe = walletService.getValueSentToMeForTransaction(transaction);
|
||||
Coin valueSentFromMe = walletService.getValueSentFromMeForTransaction(transaction);
|
||||
Address address;
|
||||
Coin valueSentToMe = btcWalletService.getValueSentToMeForTransaction(transaction);
|
||||
Coin valueSentFromMe = btcWalletService.getValueSentFromMeForTransaction(transaction);
|
||||
|
||||
// TODO check and refactor
|
||||
if (valueSentToMe.isZero()) {
|
||||
amountAsCoin = valueSentFromMe.multiply(-1);
|
||||
|
||||
for (TransactionOutput transactionOutput : transaction.getOutputs()) {
|
||||
if (!walletService.isTransactionOutputMine(transactionOutput)) {
|
||||
direction = Res.get("funds.tx.direction.sentTo");
|
||||
for (TransactionOutput output : transaction.getOutputs()) {
|
||||
if (!btcWalletService.isTransactionOutputMine(output)) {
|
||||
received = false;
|
||||
if (transactionOutput.getScriptPubKey().isSentToAddress()
|
||||
|| transactionOutput.getScriptPubKey().isPayToScriptHash()) {
|
||||
address = transactionOutput.getScriptPubKey().getToAddress(walletService.getParams());
|
||||
addressString = address.toString();
|
||||
if (bsqWalletService.isTransactionOutputMine(output)) {
|
||||
txFeeForBsqPayment = true;
|
||||
} else {
|
||||
direction = Res.get("funds.tx.direction.sentTo");
|
||||
if (output.getScriptPubKey().isSentToAddress()
|
||||
|| output.getScriptPubKey().isPayToScriptHash()) {
|
||||
addressString = output.getScriptPubKey().getToAddress(btcWalletService.getParams()).toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (valueSentFromMe.isZero()) {
|
||||
amountAsCoin = valueSentToMe;
|
||||
|
||||
direction = Res.get("funds.tx.direction.receivedWith");
|
||||
received = true;
|
||||
|
||||
for (TransactionOutput transactionOutput : transaction.getOutputs()) {
|
||||
if (!walletService.isTransactionOutputMine(transactionOutput)) {
|
||||
if (transactionOutput.getScriptPubKey().isSentToAddress() ||
|
||||
transactionOutput.getScriptPubKey().isPayToScriptHash()) {
|
||||
address = transactionOutput.getScriptPubKey().getToAddress(walletService.getParams());
|
||||
addressString = address.toString();
|
||||
for (TransactionOutput output : transaction.getOutputs()) {
|
||||
if (!btcWalletService.isTransactionOutputMine(output)) {
|
||||
if (output.getScriptPubKey().isSentToAddress() ||
|
||||
output.getScriptPubKey().isPayToScriptHash()) {
|
||||
addressString = output.getScriptPubKey().getToAddress(btcWalletService.getParams()).toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
amountAsCoin = valueSentToMe.subtract(valueSentFromMe);
|
||||
boolean outgoing = false;
|
||||
for (TransactionOutput transactionOutput : transaction.getOutputs()) {
|
||||
if (!walletService.isTransactionOutputMine(transactionOutput)) {
|
||||
for (TransactionOutput output : transaction.getOutputs()) {
|
||||
if (!btcWalletService.isTransactionOutputMine(output)) {
|
||||
if (bsqWalletService.isTransactionOutputMine(output)) {
|
||||
outgoing = false;
|
||||
txFeeForBsqPayment = true;
|
||||
} else {
|
||||
outgoing = true;
|
||||
if (transactionOutput.getScriptPubKey().isSentToAddress() ||
|
||||
transactionOutput.getScriptPubKey().isPayToScriptHash()) {
|
||||
address = transactionOutput.getScriptPubKey().getToAddress(walletService.getParams());
|
||||
addressString = address.toString();
|
||||
if (output.getScriptPubKey().isSentToAddress()
|
||||
|| output.getScriptPubKey().isPayToScriptHash()) {
|
||||
addressString = output.getScriptPubKey().getToAddress(btcWalletService.getParams()).toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -121,6 +132,10 @@ class TransactionsListItem {
|
|||
received = false;
|
||||
}
|
||||
}
|
||||
if (txFeeForBsqPayment) {
|
||||
direction = Res.get("funds.tx.txFeePaymentForBsqTx");
|
||||
addressString = "";
|
||||
}
|
||||
|
||||
// confidence
|
||||
txConfidenceIndicator = new TxConfidenceIndicator();
|
||||
|
@ -138,7 +153,7 @@ class TransactionsListItem {
|
|||
confirmations = confidence.getDepthInBlocks();
|
||||
}
|
||||
};
|
||||
walletService.addTxConfidenceListener(txConfidenceListener);
|
||||
btcWalletService.addTxConfidenceListener(txConfidenceListener);
|
||||
TransactionConfidence confidence = transaction.getConfidence();
|
||||
GUIUtil.updateConfidence(confidence, tooltip, txConfidenceIndicator);
|
||||
confirmations = confidence.getDepthInBlocks();
|
||||
|
@ -182,8 +197,10 @@ class TransactionsListItem {
|
|||
} else {
|
||||
if (amountAsCoin.isZero())
|
||||
details = Res.get("funds.tx.noFundsFromDispute");
|
||||
else
|
||||
else if (!txFeeForBsqPayment)
|
||||
details = received ? Res.get("funds.tx.receivedFunds") : Res.get("funds.tx.withdrawnFromWallet");
|
||||
else
|
||||
details = Res.get("funds.tx.txFeePaymentForBsqTx");
|
||||
}
|
||||
|
||||
date = transaction.getUpdateTime();
|
||||
|
@ -192,7 +209,7 @@ class TransactionsListItem {
|
|||
|
||||
|
||||
public void cleanup() {
|
||||
walletService.removeTxConfidenceListener(txConfidenceListener);
|
||||
btcWalletService.removeTxConfidenceListener(txConfidenceListener);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import io.bisq.common.util.Tuple2;
|
|||
import io.bisq.common.util.Tuple4;
|
||||
import io.bisq.common.util.Utilities;
|
||||
import io.bisq.core.arbitration.DisputeManager;
|
||||
import io.bisq.core.btc.wallet.BsqWalletService;
|
||||
import io.bisq.core.btc.wallet.BtcWalletService;
|
||||
import io.bisq.core.offer.OpenOffer;
|
||||
import io.bisq.core.offer.OpenOfferManager;
|
||||
|
@ -80,7 +81,8 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
|
|||
private final ObservableList<TransactionsListItem> observableList = FXCollections.observableArrayList();
|
||||
private final SortedList<TransactionsListItem> sortedList = new SortedList<>(observableList);
|
||||
|
||||
private final BtcWalletService walletService;
|
||||
private final BtcWalletService btcWalletService;
|
||||
private BsqWalletService bsqWalletService;
|
||||
private final TradeManager tradeManager;
|
||||
private final OpenOfferManager openOfferManager;
|
||||
private final ClosedTradableManager closedTradableManager;
|
||||
|
@ -100,12 +102,14 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
private TransactionsView(BtcWalletService walletService, TradeManager tradeManager, OpenOfferManager openOfferManager,
|
||||
private TransactionsView(BtcWalletService btcWalletService, BsqWalletService bsqWalletService,
|
||||
TradeManager tradeManager, OpenOfferManager openOfferManager,
|
||||
ClosedTradableManager closedTradableManager, FailedTradesManager failedTradesManager,
|
||||
BSFormatter formatter, Preferences preferences, TradeDetailsWindow tradeDetailsWindow,
|
||||
DisputeManager disputeManager, Stage stage,
|
||||
OfferDetailsWindow offerDetailsWindow) {
|
||||
this.walletService = walletService;
|
||||
this.btcWalletService = btcWalletService;
|
||||
this.bsqWalletService = bsqWalletService;
|
||||
this.tradeManager = tradeManager;
|
||||
this.openOfferManager = openOfferManager;
|
||||
this.closedTradableManager = closedTradableManager;
|
||||
|
@ -206,7 +210,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
|
|||
tableView.setItems(sortedList);
|
||||
updateList();
|
||||
|
||||
walletService.addEventListener(walletEventListener);
|
||||
btcWalletService.addEventListener(walletEventListener);
|
||||
|
||||
scene = root.getScene();
|
||||
if (scene != null)
|
||||
|
@ -241,7 +245,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
|
|||
protected void deactivate() {
|
||||
sortedList.comparatorProperty().unbind();
|
||||
observableList.forEach(TransactionsListItem::cleanup);
|
||||
walletService.removeEventListener(walletEventListener);
|
||||
btcWalletService.removeEventListener(walletEventListener);
|
||||
|
||||
if (scene != null)
|
||||
scene.removeEventHandler(KeyEvent.KEY_RELEASED, keyEventEventHandler);
|
||||
|
@ -260,7 +264,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
|
|||
Stream<Tradable> concat3 = Stream.concat(concat2, failedTradesManager.getFailedTrades().stream());
|
||||
Set<Tradable> all = concat3.collect(Collectors.toSet());
|
||||
|
||||
Set<Transaction> transactions = walletService.getTransactions(true);
|
||||
Set<Transaction> transactions = btcWalletService.getTransactions(true);
|
||||
List<TransactionsListItem> transactionsListItems = transactions.stream()
|
||||
.map(transaction -> {
|
||||
Optional<Tradable> tradableOptional = all.stream()
|
||||
|
@ -289,7 +293,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
|
|||
return false;
|
||||
})
|
||||
.findAny();
|
||||
return new TransactionsListItem(transaction, walletService, tradableOptional, formatter);
|
||||
return new TransactionsListItem(transaction, btcWalletService, bsqWalletService, tradableOptional, formatter);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
|
@ -524,7 +528,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
|
|||
public void updateItem(final TransactionsListItem item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null && !empty) {
|
||||
TransactionConfidence confidence = walletService.getConfidenceForTxId(item.getTxId());
|
||||
TransactionConfidence confidence = btcWalletService.getConfidenceForTxId(item.getTxId());
|
||||
if (confidence != null) {
|
||||
if (confidence.getConfidenceType() == TransactionConfidence.ConfidenceType.PENDING) {
|
||||
if (button == null) {
|
||||
|
@ -555,9 +559,9 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
|
|||
|
||||
private void revertTransaction(String txId, @Nullable Tradable tradable) {
|
||||
try {
|
||||
walletService.doubleSpendTransaction(txId, () -> {
|
||||
btcWalletService.doubleSpendTransaction(txId, () -> {
|
||||
if (tradable != null)
|
||||
walletService.swapAnyTradeEntryContextToAvailableEntry(tradable.getId());
|
||||
btcWalletService.swapAnyTradeEntryContextToAvailableEntry(tradable.getId());
|
||||
|
||||
new Popup().information(Res.get("funds.tx.txSent")).show();
|
||||
}, errorMessage -> new Popup().warning(errorMessage).show());
|
||||
|
|
|
@ -491,7 +491,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
|||
boolean initWithData(Offer.Direction direction, TradeCurrency tradeCurrency) {
|
||||
boolean result = dataModel.initWithData(direction, tradeCurrency);
|
||||
if (dataModel.paymentAccount != null)
|
||||
btcValidator.setMaxValueInBitcoin(dataModel.paymentAccount.getPaymentMethod().getMaxTradeLimitAsCoin());
|
||||
btcValidator.setMaxValue(dataModel.paymentAccount.getPaymentMethod().getMaxTradeLimitAsCoin());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -547,7 +547,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
|||
}
|
||||
|
||||
public void onPaymentAccountSelected(PaymentAccount paymentAccount) {
|
||||
btcValidator.setMaxValueInBitcoin(paymentAccount.getPaymentMethod().getMaxTradeLimitAsCoin());
|
||||
btcValidator.setMaxValue(paymentAccount.getPaymentMethod().getMaxTradeLimitAsCoin());
|
||||
dataModel.onPaymentAccountSelected(paymentAccount);
|
||||
if (amount.get() != null)
|
||||
amountValidationResult.set(isBtcInputValid(amount.get()));
|
||||
|
|
|
@ -175,7 +175,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
|||
offer.errorMessageProperty().addListener(offerErrorListener);
|
||||
errorMessage.set(offer.getErrorMessage());
|
||||
|
||||
btcValidator.setMaxValueInBitcoin(offer.getAmount());
|
||||
btcValidator.setMaxValue(offer.getAmount());
|
||||
}
|
||||
|
||||
|
||||
|
@ -203,7 +203,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
|||
public void onPaymentAccountSelected(PaymentAccount paymentAccount) {
|
||||
dataModel.onPaymentAccountSelected(paymentAccount);
|
||||
if (offer != null)
|
||||
btcValidator.setMaxValueInBitcoin(offer.getAmount());
|
||||
btcValidator.setMaxValue(offer.getAmount());
|
||||
}
|
||||
|
||||
public void onShowPayFundsScreen() {
|
||||
|
|
|
@ -29,7 +29,7 @@ public class BsqFormatter extends BSFormatter {
|
|||
@Inject
|
||||
private BsqFormatter() {
|
||||
super();
|
||||
coinFormat = new MonetaryFormat().shift(5).minDecimals(0).code(5, "BSQ").minDecimals(3);
|
||||
coinFormat = new MonetaryFormat().shift(5).code(5, "BSQ").minDecimals(3);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* This file is part of bisq.
|
||||
*
|
||||
* bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.bisq.gui.util.validation;
|
||||
|
||||
import io.bisq.common.locale.Res;
|
||||
import io.bisq.core.user.Preferences;
|
||||
import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.AddressFormatException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public final class BsqAddressValidator extends InputValidator {
|
||||
|
||||
private final Preferences preferences;
|
||||
|
||||
@Inject
|
||||
public BsqAddressValidator(Preferences preferences) {
|
||||
this.preferences = preferences;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationResult validate(String input) {
|
||||
|
||||
ValidationResult result = validateIfNotEmpty(input);
|
||||
if (result.isValid)
|
||||
return validateBsqAddress(input);
|
||||
else
|
||||
return result;
|
||||
}
|
||||
|
||||
private ValidationResult validateBsqAddress(String input) {
|
||||
try {
|
||||
new Address(preferences.getBitcoinNetwork().getParameters(), input);
|
||||
return new ValidationResult(true);
|
||||
} catch (AddressFormatException e) {
|
||||
return new ValidationResult(false, Res.get("validation.bsq.invalidFormat"));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* This file is part of bisq.
|
||||
*
|
||||
* bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.bisq.gui.util.validation;
|
||||
|
||||
import io.bisq.common.locale.Res;
|
||||
import io.bisq.core.btc.Restrictions;
|
||||
import io.bisq.gui.util.BsqFormatter;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class BsqValidator extends NumberValidator {
|
||||
protected final BsqFormatter formatter;
|
||||
|
||||
@Nullable
|
||||
protected Coin maxValue;
|
||||
|
||||
@Inject
|
||||
public BsqValidator(BsqFormatter formatter) {
|
||||
this.formatter = formatter;
|
||||
setMaxValue(formatter.parseToCoin("2300000")); // TODO make it lower
|
||||
}
|
||||
|
||||
public void setMaxValue(@NotNull Coin maxValue) {
|
||||
this.maxValue = maxValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationResult validate(String input) {
|
||||
ValidationResult result = validateIfNotEmpty(input);
|
||||
if (result.isValid) {
|
||||
input = cleanInput(input);
|
||||
result = validateIfNumber(input);
|
||||
}
|
||||
|
||||
if (result.isValid) {
|
||||
result = validateIfNotZero(input)
|
||||
.and(validateIfNotNegative(input))
|
||||
.and(validateIfNotFractionalBtcValue(input))
|
||||
.and(validateIfNotExceedsMaxBtcValue(input))
|
||||
.and(validateIfAboveDust(input));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected ValidationResult validateIfAboveDust(String input) {
|
||||
final Coin coin = formatter.parseToCoin(input);
|
||||
if (Restrictions.isAboveDust(coin))
|
||||
return new ValidationResult(true);
|
||||
else
|
||||
return new ValidationResult(false, Res.get("validation.btc.amountBelowDust"));
|
||||
}
|
||||
|
||||
protected ValidationResult validateIfNotFractionalBtcValue(String input) {
|
||||
BigDecimal bd = new BigDecimal(input);
|
||||
final BigDecimal satoshis = bd.movePointRight(3);
|
||||
if (satoshis.scale() > 0)
|
||||
return new ValidationResult(false, Res.get("validation.btc.toSmall"));
|
||||
else
|
||||
return new ValidationResult(true);
|
||||
}
|
||||
|
||||
protected ValidationResult validateIfNotExceedsMaxBtcValue(String input) {
|
||||
try {
|
||||
final Coin coin = formatter.parseToCoin(input);
|
||||
if (maxValue != null && coin.compareTo(maxValue) > 0)
|
||||
return new ValidationResult(false, Res.get("validation.btc.toLarge", formatter.formatCoinWithCode(maxValue)));
|
||||
else
|
||||
return new ValidationResult(true);
|
||||
} catch (Throwable t) {
|
||||
return new ValidationResult(false, Res.get("validation.invalidInput", t.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,8 +18,10 @@
|
|||
package io.bisq.gui.util.validation;
|
||||
|
||||
import io.bisq.common.locale.Res;
|
||||
import io.bisq.core.btc.Restrictions;
|
||||
import io.bisq.gui.util.BSFormatter;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
@ -31,15 +33,15 @@ public class BtcValidator extends NumberValidator {
|
|||
|
||||
|
||||
@Nullable
|
||||
protected Coin maxValueInBitcoin;
|
||||
protected Coin maxValue;
|
||||
|
||||
@Inject
|
||||
public BtcValidator(BSFormatter formatter) {
|
||||
this.formatter = formatter;
|
||||
}
|
||||
|
||||
public void setMaxValueInBitcoin(Coin maxValueInBitcoin) {
|
||||
this.maxValueInBitcoin = maxValueInBitcoin;
|
||||
public void setMaxValue(@NotNull Coin maxValue) {
|
||||
this.maxValue = maxValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -54,12 +56,21 @@ public class BtcValidator extends NumberValidator {
|
|||
result = validateIfNotZero(input)
|
||||
.and(validateIfNotNegative(input))
|
||||
.and(validateIfNotFractionalBtcValue(input))
|
||||
.and(validateIfNotExceedsMaxBtcValue(input));
|
||||
.and(validateIfNotExceedsMaxBtcValue(input))
|
||||
.and(validateIfAboveDust(input));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected ValidationResult validateIfAboveDust(String input) {
|
||||
final Coin coin = Coin.parseCoin(input);
|
||||
if (Restrictions.isAboveDust(coin))
|
||||
return new ValidationResult(true);
|
||||
else
|
||||
return new ValidationResult(false, Res.get("validation.btc.amountBelowDust"));
|
||||
}
|
||||
|
||||
protected ValidationResult validateIfNotFractionalBtcValue(String input) {
|
||||
BigDecimal bd = new BigDecimal(input);
|
||||
final BigDecimal satoshis = bd.movePointRight(8);
|
||||
|
@ -72,8 +83,8 @@ public class BtcValidator extends NumberValidator {
|
|||
protected ValidationResult validateIfNotExceedsMaxBtcValue(String input) {
|
||||
try {
|
||||
final Coin coin = Coin.parseCoin(input);
|
||||
if (maxValueInBitcoin != null && coin.compareTo(maxValueInBitcoin) > 0)
|
||||
return new ValidationResult(false, Res.get("validation.btc.toLarge", formatter.formatCoinWithCode(maxValueInBitcoin)));
|
||||
if (maxValue != null && coin.compareTo(maxValue) > 0)
|
||||
return new ValidationResult(false, Res.get("validation.btc.toLarge", formatter.formatCoinWithCode(maxValue)));
|
||||
else
|
||||
return new ValidationResult(true);
|
||||
} catch (Throwable t) {
|
||||
|
|
|
@ -29,7 +29,7 @@ public class SecurityDepositValidator extends BtcValidator {
|
|||
@Inject
|
||||
public SecurityDepositValidator(BSFormatter formatter) {
|
||||
super(formatter);
|
||||
setMaxValueInBitcoin(Restrictions.MAX_BUYER_SECURITY_DEPOSIT);
|
||||
setMaxValue(Restrictions.MAX_BUYER_SECURITY_DEPOSIT);
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue