mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-24 07:07:43 +01:00
Add basic support for validation for XMR transfer with tx key
Main part missing is the XMR proof service request processing. I did not get the service compiled yet, so could not test response data and error conditions. Further it is missing a "news badge" and popup to guide the user to the new feature. Only basic dev tested so far. Anyone welcome to pick the project up from here as I might not have time soon to continue.
This commit is contained in:
parent
9ab39c2a8b
commit
43e4809d81
21 changed files with 869 additions and 191 deletions
|
@ -34,12 +34,16 @@ import bisq.core.payment.payload.PaymentMethod;
|
|||
import bisq.core.support.dispute.Dispute;
|
||||
import bisq.core.support.dispute.DisputeResult;
|
||||
import bisq.core.support.dispute.arbitration.TraderDataItem;
|
||||
import bisq.core.trade.Contract;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.messages.TraderSignedWitnessMessage;
|
||||
import bisq.core.trade.protocol.TradingPeer;
|
||||
import bisq.core.user.User;
|
||||
|
||||
import bisq.network.p2p.BootstrapListener;
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.P2PService;
|
||||
import bisq.network.p2p.SendMailboxMessageListener;
|
||||
import bisq.network.p2p.storage.P2PDataStorage;
|
||||
import bisq.network.p2p.storage.persistence.AppendOnlyDataStoreService;
|
||||
|
||||
|
@ -73,6 +77,7 @@ import java.util.Objects;
|
|||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
@ -852,4 +857,58 @@ public class AccountAgeWitnessService {
|
|||
public Set<SignedWitness> getUnsignedSignerPubKeys() {
|
||||
return signedWitnessService.getUnsignedSignerPubKeys();
|
||||
}
|
||||
|
||||
public boolean isSignWitnessTrade(Trade trade) {
|
||||
checkNotNull(trade, "trade must not be null");
|
||||
checkNotNull(trade.getOffer(), "offer must not be null");
|
||||
Contract contract = checkNotNull(trade.getContract());
|
||||
PaymentAccountPayload sellerPaymentAccountPayload = contract.getSellerPaymentAccountPayload();
|
||||
AccountAgeWitness myWitness = getMyWitness(sellerPaymentAccountPayload);
|
||||
|
||||
getAccountAgeWitnessUtils().witnessDebugLog(trade, myWitness);
|
||||
|
||||
return accountIsSigner(myWitness) &&
|
||||
!peerHasSignedWitness(trade) &&
|
||||
tradeAmountIsSufficient(trade.getTradeAmount());
|
||||
}
|
||||
|
||||
public void maybeSignWitness(Trade trade) {
|
||||
if (isSignWitnessTrade(trade)) {
|
||||
var signedWitnessOptional = traderSignPeersAccountAgeWitness(trade);
|
||||
signedWitnessOptional.ifPresent(signedWitness -> sendSignedWitnessToPeer(signedWitness, trade));
|
||||
}
|
||||
}
|
||||
|
||||
private void sendSignedWitnessToPeer(SignedWitness signedWitness, Trade trade) {
|
||||
if (trade == null) return;
|
||||
|
||||
NodeAddress tradingPeerNodeAddress = trade.getTradingPeerNodeAddress();
|
||||
var traderSignedWitnessMessage = new TraderSignedWitnessMessage(UUID.randomUUID().toString(), trade.getId(),
|
||||
tradingPeerNodeAddress, signedWitness);
|
||||
|
||||
p2PService.sendEncryptedMailboxMessage(
|
||||
tradingPeerNodeAddress,
|
||||
trade.getProcessModel().getTradingPeer().getPubKeyRing(),
|
||||
traderSignedWitnessMessage,
|
||||
new SendMailboxMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("SendMailboxMessageListener onArrived tradeId={} at peer {} SignedWitness {}",
|
||||
trade.getId(), tradingPeerNodeAddress, signedWitness);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStoredInMailbox() {
|
||||
log.info("SendMailboxMessageListener onStoredInMailbox tradeId={} at peer {} SignedWitness {}",
|
||||
trade.getId(), tradingPeerNodeAddress, signedWitness);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.error("SendMailboxMessageListener onFault tradeId={} at peer {} SignedWitness {}",
|
||||
trade.getId(), tradingPeerNodeAddress, signedWitness);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -428,6 +428,13 @@ public abstract class Trade implements Tradable, Model {
|
|||
private long refreshInterval;
|
||||
private static final long MAX_REFRESH_INTERVAL = 4 * ChronoUnit.HOURS.getDuration().toMillis();
|
||||
|
||||
// Added in v1.3.7
|
||||
// We use that for the XMR txKey but want to keep it generic to be flexible for other payment methods or assets.
|
||||
@Getter
|
||||
@Setter
|
||||
private String counterCurrencyExtraData;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor, initialization
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -538,6 +545,8 @@ public abstract class Trade implements Tradable, Model {
|
|||
Optional.ofNullable(mediationResultState).ifPresent(e -> builder.setMediationResultState(MediationResultState.toProtoMessage(mediationResultState)));
|
||||
Optional.ofNullable(refundResultState).ifPresent(e -> builder.setRefundResultState(RefundResultState.toProtoMessage(refundResultState)));
|
||||
Optional.ofNullable(delayedPayoutTxBytes).ifPresent(e -> builder.setDelayedPayoutTxBytes(ByteString.copyFrom(delayedPayoutTxBytes)));
|
||||
Optional.ofNullable(counterCurrencyExtraData).ifPresent(e -> builder.setCounterCurrencyExtraData(counterCurrencyExtraData));
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
|
@ -570,6 +579,7 @@ public abstract class Trade implements Tradable, Model {
|
|||
trade.setDelayedPayoutTxBytes(ProtoUtil.byteArrayOrNullFromProto(proto.getDelayedPayoutTxBytes()));
|
||||
trade.setLockTime(proto.getLockTime());
|
||||
trade.setLastRefreshRequestDate(proto.getLastRefreshRequestDate());
|
||||
trade.setCounterCurrencyExtraData(ProtoUtil.stringOrNullFromProto(proto.getCounterCurrencyExtraData()));
|
||||
|
||||
trade.chatMessages.addAll(proto.getChatMessageList().stream()
|
||||
.map(ChatMessage::fromPayloadProto)
|
||||
|
|
|
@ -21,6 +21,7 @@ import bisq.core.account.witness.AccountAgeWitnessService;
|
|||
import bisq.core.btc.exceptions.AddressEntryException;
|
||||
import bisq.core.btc.exceptions.TxBroadcastException;
|
||||
import bisq.core.btc.model.AddressEntry;
|
||||
import bisq.core.btc.setup.WalletsSetup;
|
||||
import bisq.core.btc.wallet.BsqWalletService;
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
import bisq.core.btc.wallet.TradeWalletService;
|
||||
|
@ -34,10 +35,15 @@ import bisq.core.offer.OfferPayload;
|
|||
import bisq.core.offer.OpenOffer;
|
||||
import bisq.core.offer.OpenOfferManager;
|
||||
import bisq.core.offer.availability.OfferAvailabilityModel;
|
||||
import bisq.core.payment.payload.AssetsAccountPayload;
|
||||
import bisq.core.payment.payload.PaymentAccountPayload;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
||||
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
|
||||
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
|
||||
import bisq.core.trade.asset.xmr.XmrProofResult;
|
||||
import bisq.core.trade.asset.xmr.XmrProofResultWithTradeId;
|
||||
import bisq.core.trade.asset.xmr.XmrTransferProofService;
|
||||
import bisq.core.trade.closed.ClosedTradableManager;
|
||||
import bisq.core.trade.failed.FailedTradesManager;
|
||||
import bisq.core.trade.handlers.TradeResultHandler;
|
||||
|
@ -46,6 +52,7 @@ import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage;
|
|||
import bisq.core.trade.messages.TradeMessage;
|
||||
import bisq.core.trade.statistics.ReferralIdService;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.user.User;
|
||||
import bisq.core.util.Validator;
|
||||
|
||||
|
@ -65,8 +72,6 @@ import bisq.common.handlers.ResultHandler;
|
|||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
import bisq.common.proto.persistable.PersistedDataHost;
|
||||
import bisq.common.storage.Storage;
|
||||
import bisq.common.util.Tuple2;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import org.bitcoinj.core.AddressFormatException;
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
@ -81,8 +86,10 @@ import com.google.common.util.concurrent.FutureCallback;
|
|||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.LongProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleLongProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
|
@ -93,7 +100,6 @@ import org.spongycastle.crypto.params.KeyParameter;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
@ -111,6 +117,8 @@ import org.jetbrains.annotations.NotNull;
|
|||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
public class TradeManager implements PersistedDataHost {
|
||||
private static final Logger log = LoggerFactory.getLogger(TradeManager.class);
|
||||
|
||||
|
@ -147,8 +155,14 @@ public class TradeManager implements PersistedDataHost {
|
|||
@Getter
|
||||
private final ObservableList<Trade> tradesWithoutDepositTx = FXCollections.observableArrayList();
|
||||
private final DumpDelayedPayoutTx dumpDelayedPayoutTx;
|
||||
private final XmrTransferProofService xmrTransferProofService;
|
||||
private final WalletsSetup walletsSetup;
|
||||
private final Preferences preferences;
|
||||
@Getter
|
||||
private final boolean allowFaultyDelayedTxs;
|
||||
// This observable property can be used for UI to show a notification to user in case a XMR txKey was reused.
|
||||
@Getter
|
||||
private final ObjectProperty<XmrProofResultWithTradeId> proofResultWithTradeIdProperty = new SimpleObjectProperty<>();
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -177,6 +191,9 @@ public class TradeManager implements PersistedDataHost {
|
|||
ClockWatcher clockWatcher,
|
||||
Storage<TradableList<Trade>> storage,
|
||||
DumpDelayedPayoutTx dumpDelayedPayoutTx,
|
||||
XmrTransferProofService xmrTransferProofService,
|
||||
WalletsSetup walletsSetup,
|
||||
Preferences preferences,
|
||||
@Named(Config.ALLOW_FAULTY_DELAYED_TXS) boolean allowFaultyDelayedTxs) {
|
||||
this.user = user;
|
||||
this.keyRing = keyRing;
|
||||
|
@ -198,6 +215,9 @@ public class TradeManager implements PersistedDataHost {
|
|||
this.daoFacade = daoFacade;
|
||||
this.clockWatcher = clockWatcher;
|
||||
this.dumpDelayedPayoutTx = dumpDelayedPayoutTx;
|
||||
this.xmrTransferProofService = xmrTransferProofService;
|
||||
this.walletsSetup = walletsSetup;
|
||||
this.preferences = preferences;
|
||||
this.allowFaultyDelayedTxs = allowFaultyDelayedTxs;
|
||||
|
||||
tradableListStorage = storage;
|
||||
|
@ -855,4 +875,121 @@ public class TradeManager implements PersistedDataHost {
|
|||
public void persistTrades() {
|
||||
tradableList.persist();
|
||||
}
|
||||
|
||||
public void processCounterCurrencyExtraData(Trade trade) {
|
||||
String counterCurrencyExtraData = trade.getCounterCurrencyExtraData();
|
||||
if (counterCurrencyExtraData == null || counterCurrencyExtraData.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String txHash = trade.getCounterCurrencyTxId();
|
||||
if (txHash == null || txHash.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Contract contract = checkNotNull(trade.getContract(), "contract must not be null");
|
||||
PaymentAccountPayload sellersPaymentAccountPayload = contract.getSellerPaymentAccountPayload();
|
||||
if (!(sellersPaymentAccountPayload instanceof AssetsAccountPayload)) {
|
||||
return;
|
||||
}
|
||||
AssetsAccountPayload sellersAssetsAccountPayload = (AssetsAccountPayload) sellersPaymentAccountPayload;
|
||||
|
||||
if (!(trade instanceof SellerTrade)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Offer offer = checkNotNull(trade.getOffer(), "Offer must not be null");
|
||||
if (offer.getCurrencyCode().equals("XMR")) {
|
||||
String txKey = counterCurrencyExtraData;
|
||||
|
||||
// We need to prevent that a user tries to scam by reusing a txKey and txHash of a previous XMR trade with
|
||||
// the same user (same address) and same amount. We check only for the txKey as a same txHash but different
|
||||
// txKey is not possible to get a valid result at proof.
|
||||
Stream<Trade> failedAndOpenTrades = Stream.concat(tradableList.stream(), failedTradesManager.getFailedTrades().stream());
|
||||
Stream<Trade> closedTrades = closedTradableManager.getClosedTradables().stream()
|
||||
.filter(tradable -> tradable instanceof Trade)
|
||||
.map(tradable -> (Trade) tradable);
|
||||
Stream<Trade> allTrades = Stream.concat(failedAndOpenTrades, closedTrades);
|
||||
boolean txKeyUsedAtAnyOpenTrade = allTrades
|
||||
.filter(t -> !t.getId().equals(trade.getId())) // ignore same trade
|
||||
.anyMatch(t -> {
|
||||
String extra = t.getCounterCurrencyExtraData();
|
||||
if (extra == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean alreadyUsed = extra.equals(txKey);
|
||||
if (alreadyUsed) {
|
||||
String message = "Peer used the XMR tx key already at another trade with trade ID " +
|
||||
t.getId() + ". This might be a scam attempt.";
|
||||
log.warn(message);
|
||||
proofResultWithTradeIdProperty.set(new XmrProofResultWithTradeId(XmrProofResult.TX_KEY_REUSED, trade.getId()));
|
||||
}
|
||||
return alreadyUsed;
|
||||
});
|
||||
|
||||
if (txKeyUsedAtAnyOpenTrade) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (preferences.isAutoConfirmXmr()) {
|
||||
String address = sellersAssetsAccountPayload.getAddress();
|
||||
//TODO for dev testing
|
||||
address = "85q13WDADXE26W6h7cStpPMkn8tWpvWgHbpGWWttFEafGXyjsBTXxxyQms4UErouTY5sdKpYHVjQm6SagiCqytseDkzfgub";
|
||||
// 8.90259736 is dev test value
|
||||
long amount = (long) Float.parseFloat("8.90259736") * 100000000; // todo check XMR denomination
|
||||
xmrTransferProofService.requestProof(trade.getId(),
|
||||
txHash,
|
||||
txKey,
|
||||
address,
|
||||
amount,
|
||||
result -> {
|
||||
switch (result) {
|
||||
case TX_NOT_CONFIRMED:
|
||||
// Repeating the requests is handled in XmrTransferProofRequester
|
||||
break;
|
||||
case PROOF_OK:
|
||||
if (!p2PService.isBootstrapped()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!walletsSetup.hasSufficientPeersForBroadcast()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!walletsSetup.isDownloadComplete()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!trade.isPayoutPublished()) {
|
||||
trade.setState(Trade.State.SELLER_CONFIRMED_IN_UI_FIAT_PAYMENT_RECEIPT);
|
||||
}
|
||||
|
||||
accountAgeWitnessService.maybeSignWitness(trade);
|
||||
|
||||
((SellerTrade) trade).onFiatPaymentReceived(() -> {
|
||||
}, errorMessage -> {
|
||||
});
|
||||
break;
|
||||
case UNKNOWN_ERROR:
|
||||
case TX_KEY_REUSED:
|
||||
case TX_NEVER_FOUND:
|
||||
case TX_HASH_INVALID:
|
||||
case TX_KEY_INVALID:
|
||||
case ADDRESS_INVALID:
|
||||
case AMOUNT_NOT_MATCHING:
|
||||
case PROOF_FAILED:
|
||||
default:
|
||||
log.error("Case not handled. " + result);
|
||||
break;
|
||||
}
|
||||
|
||||
proofResultWithTradeIdProperty.set(new XmrProofResultWithTradeId(result, trade.getId()));
|
||||
},
|
||||
(errorMsg, throwable) -> {
|
||||
log.warn(errorMsg);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.trade.asset.xmr;
|
||||
|
||||
public enum XmrProofResult {
|
||||
TX_NOT_CONFIRMED,
|
||||
PROOF_OK,
|
||||
UNKNOWN_ERROR,
|
||||
TX_KEY_REUSED,
|
||||
TX_NEVER_FOUND,
|
||||
TX_HASH_INVALID,
|
||||
TX_KEY_INVALID,
|
||||
ADDRESS_INVALID,
|
||||
AMOUNT_NOT_MATCHING,
|
||||
PROOF_FAILED
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.trade.asset.xmr;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
@Value
|
||||
public class XmrProofResultWithTradeId {
|
||||
private final XmrProofResult xmrProofResult;
|
||||
private final String tradeId;
|
||||
|
||||
public XmrProofResultWithTradeId(XmrProofResult xmrProofResult, String tradeId) {
|
||||
this.xmrProofResult = xmrProofResult;
|
||||
this.tradeId = tradeId;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.trade.asset.xmr;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.handlers.FaultHandler;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Slf4j
|
||||
@Singleton
|
||||
class XmrTransferProofRequester {
|
||||
|
||||
private final ListeningExecutorService executorService = Utilities.getListeningExecutorService(
|
||||
"XmrTransferProofService", 3, 5, 10 * 60);
|
||||
private final XmrTxProofHttpClient httpClient;
|
||||
private final String txHash;
|
||||
private final String txKey;
|
||||
private final String recipientAddress;
|
||||
private final long amount;
|
||||
private final Consumer<XmrProofResult> resultHandler;
|
||||
private final FaultHandler faultHandler;
|
||||
|
||||
private long firstRequest;
|
||||
//todo dev settings
|
||||
private long REPEAT_REQUEST_SEC = TimeUnit.SECONDS.toMillis(5);
|
||||
private long MAX_REQUEST_PERIOD = TimeUnit.HOURS.toMillis(12);
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
XmrTransferProofRequester(XmrTxProofHttpClient httpClient,
|
||||
String txHash,
|
||||
String txKey,
|
||||
String recipientAddress,
|
||||
long amount,
|
||||
Consumer<XmrProofResult> resultHandler,
|
||||
FaultHandler faultHandler) {
|
||||
this.httpClient = httpClient;
|
||||
this.txHash = txHash;
|
||||
this.txKey = txKey;
|
||||
this.recipientAddress = recipientAddress;
|
||||
this.amount = amount;
|
||||
this.resultHandler = resultHandler;
|
||||
this.faultHandler = faultHandler;
|
||||
firstRequest = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void request() {
|
||||
// todo dev test address for a real tx proof
|
||||
/*
|
||||
txID: 5e665addf6d7c6300670e8a89564ed12b5c1a21c336408e2835668f9a6a0d802
|
||||
txKey: f3ce66c9d395e5e460c8802b2c3c1fff04e508434f9738ee35558aac4678c906
|
||||
address: 85q13WDADXE26W6h7cStpPMkn8tWpvWgHbpGWWttFEafGXyjsBTXxxyQms4UErouTY5sdKpYHVjQm6SagiCqytseDkzfgub
|
||||
ammount : 8.90259736 XMR
|
||||
*/
|
||||
|
||||
ListenableFuture<XmrProofResult> future = executorService.submit(() -> {
|
||||
Thread.currentThread().setName("XmrTransferProofRequest-" + this.toString());
|
||||
String param = "/api/outputs?txhash=" + txHash +
|
||||
"&address=" + recipientAddress +
|
||||
"&viewkey=" + txKey +
|
||||
"&txprove=1";
|
||||
String json = httpClient.requestWithGET(param, "User-Agent", "bisq/" + Version.VERSION);
|
||||
Thread.sleep(3000);
|
||||
|
||||
//
|
||||
|
||||
return parseResult(json);
|
||||
});
|
||||
|
||||
Futures.addCallback(future, new FutureCallback<>() {
|
||||
public void onSuccess(XmrProofResult result) {
|
||||
if (result == XmrProofResult.TX_NOT_CONFIRMED && System.currentTimeMillis() - firstRequest < MAX_REQUEST_PERIOD) {
|
||||
UserThread.runAfter(() -> request(), REPEAT_REQUEST_SEC);
|
||||
} else {
|
||||
UserThread.execute(() -> resultHandler.accept(result));
|
||||
}
|
||||
}
|
||||
|
||||
public void onFailure(@NotNull Throwable throwable) {
|
||||
String errorMessage = "Request to " + httpClient.getBaseUrl() + " failed";
|
||||
faultHandler.handleFault(errorMessage, throwable);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private XmrProofResult parseResult(String json) {
|
||||
//TODO parse json
|
||||
//TODO need service to check diff. error conditions
|
||||
return XmrProofResult.PROOF_OK;
|
||||
// check recipientAddress and amount
|
||||
// json example
|
||||
/*
|
||||
|
||||
{
|
||||
"data": {
|
||||
"address": "42f18fc61586554095b0799b5c4b6f00cdeb26a93b20540d366932c6001617b75db35109fbba7d5f275fef4b9c49e0cc1c84b219ec6ff652fda54f89f7f63c88",
|
||||
"outputs": [
|
||||
{
|
||||
"amount": 34980000000000,
|
||||
"match": true,
|
||||
"output_idx": 0,
|
||||
"output_pubkey": "35d7200229e725c2bce0da3a2f20ef0720d242ecf88bfcb71eff2025c2501fdb"
|
||||
},
|
||||
{
|
||||
"amount": 0,
|
||||
"match": false,
|
||||
"output_idx": 1,
|
||||
"output_pubkey": "44efccab9f9b42e83c12da7988785d6c4eb3ec6e7aa2ae1234e2f0f7cb9ed6dd"
|
||||
}
|
||||
],
|
||||
"tx_hash": "17049bc5f2d9fbca1ce8dae443bbbbed2fc02f1ee003ffdd0571996905faa831",
|
||||
"tx_prove": false,
|
||||
"viewkey": "f359631075708155cc3d92a32b75a7d02a5dcf27756707b47a2b31b21c389501"
|
||||
},
|
||||
"status": "success"
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.trade.asset.xmr;
|
||||
|
||||
import bisq.common.handlers.FaultHandler;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Manages the XMR transfers proof requests for multiple trades.
|
||||
*/
|
||||
@Slf4j
|
||||
public class XmrTransferProofService {
|
||||
private final XmrTxProofHttpClient httpClient;
|
||||
private Map<String, XmrTransferProofRequester> map = new HashMap<>();
|
||||
|
||||
@Inject
|
||||
public XmrTransferProofService(XmrTxProofHttpClient httpClient) {
|
||||
this.httpClient = httpClient;
|
||||
//this.httpClient.setBaseUrl("http://139.59.140.37:8081");
|
||||
this.httpClient.setBaseUrl("http://127.0.0.1:8081");
|
||||
this.httpClient.setIgnoreSocks5Proxy(false);
|
||||
}
|
||||
|
||||
public void requestProof(String tradeId,
|
||||
String txHash,
|
||||
String txKey,
|
||||
String recipientAddress,
|
||||
long amount,
|
||||
Consumer<XmrProofResult> resultHandler,
|
||||
FaultHandler faultHandler) {
|
||||
if (map.containsKey(tradeId)) {
|
||||
log.warn("We started a proof request for trade with ID {} already", tradeId);
|
||||
return;
|
||||
}
|
||||
|
||||
XmrTransferProofRequester requester = new XmrTransferProofRequester(httpClient,
|
||||
txHash,
|
||||
txKey,
|
||||
recipientAddress,
|
||||
amount,
|
||||
result -> {
|
||||
cleanup(tradeId);
|
||||
resultHandler.accept(result);
|
||||
},
|
||||
(errorMsg, throwable) -> {
|
||||
cleanup(tradeId);
|
||||
faultHandler.handleFault(errorMsg, throwable);
|
||||
});
|
||||
map.put(tradeId, requester);
|
||||
requester.request();
|
||||
}
|
||||
|
||||
private void cleanup(String tradeId) {
|
||||
map.remove(tradeId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.trade.asset.xmr;
|
||||
|
||||
import bisq.network.Socks5ProxyProvider;
|
||||
import bisq.network.http.HttpClient;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class XmrTxProofHttpClient extends HttpClient {
|
||||
@Inject
|
||||
public XmrTxProofHttpClient(@Nullable Socks5ProxyProvider socks5ProxyProvider) {
|
||||
super(socks5ProxyProvider);
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ import bisq.network.p2p.MailboxMessage;
|
|||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.proto.ProtoUtil;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
@ -41,17 +42,24 @@ public final class CounterCurrencyTransferStartedMessage extends TradeMessage im
|
|||
@Nullable
|
||||
private final String counterCurrencyTxId;
|
||||
|
||||
// Added in v1.3.7
|
||||
// We use that for the XMR txKey but want to keep it generic to be flexible for data of other payment methods or assets.
|
||||
@Nullable
|
||||
private String counterCurrencyExtraData;
|
||||
|
||||
public CounterCurrencyTransferStartedMessage(String tradeId,
|
||||
String buyerPayoutAddress,
|
||||
NodeAddress senderNodeAddress,
|
||||
byte[] buyerSignature,
|
||||
@Nullable String counterCurrencyTxId,
|
||||
@Nullable String counterCurrencyExtraData,
|
||||
String uid) {
|
||||
this(tradeId,
|
||||
buyerPayoutAddress,
|
||||
senderNodeAddress,
|
||||
buyerSignature,
|
||||
counterCurrencyTxId,
|
||||
counterCurrencyExtraData,
|
||||
uid,
|
||||
Version.getP2PMessageVersion());
|
||||
}
|
||||
|
@ -66,6 +74,7 @@ public final class CounterCurrencyTransferStartedMessage extends TradeMessage im
|
|||
NodeAddress senderNodeAddress,
|
||||
byte[] buyerSignature,
|
||||
@Nullable String counterCurrencyTxId,
|
||||
@Nullable String counterCurrencyExtraData,
|
||||
String uid,
|
||||
int messageVersion) {
|
||||
super(messageVersion, tradeId, uid);
|
||||
|
@ -73,6 +82,7 @@ public final class CounterCurrencyTransferStartedMessage extends TradeMessage im
|
|||
this.senderNodeAddress = senderNodeAddress;
|
||||
this.buyerSignature = buyerSignature;
|
||||
this.counterCurrencyTxId = counterCurrencyTxId;
|
||||
this.counterCurrencyExtraData = counterCurrencyExtraData;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -85,16 +95,19 @@ public final class CounterCurrencyTransferStartedMessage extends TradeMessage im
|
|||
.setUid(uid);
|
||||
|
||||
Optional.ofNullable(counterCurrencyTxId).ifPresent(e -> builder.setCounterCurrencyTxId(counterCurrencyTxId));
|
||||
Optional.ofNullable(counterCurrencyExtraData).ifPresent(e -> builder.setCounterCurrencyExtraData(counterCurrencyExtraData));
|
||||
|
||||
return getNetworkEnvelopeBuilder().setCounterCurrencyTransferStartedMessage(builder).build();
|
||||
}
|
||||
|
||||
public static CounterCurrencyTransferStartedMessage fromProto(protobuf.CounterCurrencyTransferStartedMessage proto, int messageVersion) {
|
||||
public static CounterCurrencyTransferStartedMessage fromProto(protobuf.CounterCurrencyTransferStartedMessage proto,
|
||||
int messageVersion) {
|
||||
return new CounterCurrencyTransferStartedMessage(proto.getTradeId(),
|
||||
proto.getBuyerPayoutAddress(),
|
||||
NodeAddress.fromProto(proto.getSenderNodeAddress()),
|
||||
proto.getBuyerSignature().toByteArray(),
|
||||
proto.getCounterCurrencyTxId().isEmpty() ? null : proto.getCounterCurrencyTxId(),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getCounterCurrencyTxId()),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getCounterCurrencyExtraData()),
|
||||
proto.getUid(),
|
||||
messageVersion);
|
||||
}
|
||||
|
@ -106,6 +119,7 @@ public final class CounterCurrencyTransferStartedMessage extends TradeMessage im
|
|||
"\n buyerPayoutAddress='" + buyerPayoutAddress + '\'' +
|
||||
",\n senderNodeAddress=" + senderNodeAddress +
|
||||
",\n counterCurrencyTxId=" + counterCurrencyTxId +
|
||||
",\n counterCurrencyExtraData=" + counterCurrencyExtraData +
|
||||
",\n uid='" + uid + '\'' +
|
||||
",\n buyerSignature=" + Utilities.bytesAsHexString(buyerSignature) +
|
||||
"\n} " + super.toString();
|
||||
|
|
|
@ -54,6 +54,7 @@ public class BuyerSendCounterCurrencyTransferStartedMessage extends TradeTask {
|
|||
processModel.getMyNodeAddress(),
|
||||
processModel.getPayoutTxSignature(),
|
||||
trade.getCounterCurrencyTxId(),
|
||||
trade.getCounterCurrencyExtraData(),
|
||||
UUID.randomUUID().toString()
|
||||
);
|
||||
NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress();
|
||||
|
|
|
@ -49,7 +49,17 @@ public class SellerProcessCounterCurrencyTransferStartedMessage extends TradeTas
|
|||
|
||||
// update to the latest peer address of our peer if the message is correct
|
||||
trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
|
||||
trade.setCounterCurrencyTxId(message.getCounterCurrencyTxId());
|
||||
|
||||
String counterCurrencyTxId = message.getCounterCurrencyTxId();
|
||||
if (counterCurrencyTxId != null && counterCurrencyTxId.length() < 100) {
|
||||
trade.setCounterCurrencyTxId(counterCurrencyTxId);
|
||||
}
|
||||
|
||||
String counterCurrencyExtraData = message.getCounterCurrencyExtraData();
|
||||
if (counterCurrencyExtraData != null && counterCurrencyExtraData.length() < 100) {
|
||||
trade.setCounterCurrencyExtraData(counterCurrencyExtraData);
|
||||
processModel.getTradeManager().processCounterCurrencyExtraData(trade);
|
||||
}
|
||||
processModel.removeMailboxMessageAfterProcessing(trade);
|
||||
|
||||
trade.setState(Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG);
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
|
||||
package bisq.core.user;
|
||||
|
||||
import bisq.core.btc.nodes.LocalBitcoinNode;
|
||||
import bisq.core.btc.nodes.BtcNodes;
|
||||
import bisq.core.btc.nodes.LocalBitcoinNode;
|
||||
import bisq.core.btc.wallet.Restrictions;
|
||||
import bisq.core.locale.Country;
|
||||
import bisq.core.locale.CountryUtil;
|
||||
|
@ -394,6 +394,11 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
|||
persist();
|
||||
}
|
||||
|
||||
public void setAutoConfirmXmr(boolean autoConfirmXmr) {
|
||||
prefPayload.setAutoConfirmXmr(autoConfirmXmr);
|
||||
persist();
|
||||
}
|
||||
|
||||
private void persist() {
|
||||
if (initialReadDone)
|
||||
storage.queueUpForSave(prefPayload);
|
||||
|
@ -965,5 +970,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
|||
int getBlockNotifyPort();
|
||||
|
||||
void setTacAcceptedV120(boolean tacAccepted);
|
||||
|
||||
void setAutoConfirmXmr(boolean autoConfirmXmr);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -127,6 +127,9 @@ public final class PreferencesPayload implements UserThreadMappedPersistableEnve
|
|||
private int blockNotifyPort;
|
||||
private boolean tacAcceptedV120;
|
||||
|
||||
// Added with 1.3.7 false be default
|
||||
private boolean autoConfirmXmr;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
|
@ -186,7 +189,8 @@ public final class PreferencesPayload implements UserThreadMappedPersistableEnve
|
|||
.setIgnoreDustThreshold(ignoreDustThreshold)
|
||||
.setBuyerSecurityDepositAsPercentForCrypto(buyerSecurityDepositAsPercentForCrypto)
|
||||
.setBlockNotifyPort(blockNotifyPort)
|
||||
.setTacAcceptedV120(tacAcceptedV120);
|
||||
.setTacAcceptedV120(tacAcceptedV120)
|
||||
.setAutoConfirmXmr(autoConfirmXmr);
|
||||
Optional.ofNullable(backupDirectory).ifPresent(builder::setBackupDirectory);
|
||||
Optional.ofNullable(preferredTradeCurrency).ifPresent(e -> builder.setPreferredTradeCurrency((protobuf.TradeCurrency) e.toProtoMessage()));
|
||||
Optional.ofNullable(offerBookChartScreenCurrencyCode).ifPresent(builder::setOfferBookChartScreenCurrencyCode);
|
||||
|
@ -274,6 +278,7 @@ public final class PreferencesPayload implements UserThreadMappedPersistableEnve
|
|||
proto.getIgnoreDustThreshold(),
|
||||
proto.getBuyerSecurityDepositAsPercentForCrypto(),
|
||||
proto.getBlockNotifyPort(),
|
||||
proto.getTacAcceptedV120());
|
||||
proto.getTacAcceptedV120(),
|
||||
proto.getAutoConfirmXmr());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -718,6 +718,9 @@ portfolio.pending.step3_seller.amountToReceive=Amount to receive
|
|||
portfolio.pending.step3_seller.yourAddress=Your {0} address
|
||||
portfolio.pending.step3_seller.buyersAddress=Buyers {0} address
|
||||
portfolio.pending.step3_seller.yourAccount=Your trading account
|
||||
portfolio.pending.step3_seller.xmrTxHash=Tx hash
|
||||
portfolio.pending.step3_seller.xmrTxKey=Tx private key
|
||||
portfolio.pending.step3_seller.xmrTxVerificationError=The XMR transfer validation for trade with ID ''{0}'' failed.\nReason: ''{1}''
|
||||
portfolio.pending.step3_seller.buyersAccount=Buyers trading account
|
||||
portfolio.pending.step3_seller.confirmReceipt=Confirm payment receipt
|
||||
portfolio.pending.step3_seller.buyerStartedPayment=The BTC buyer has started the {0} payment.\n{1}
|
||||
|
@ -1051,6 +1054,7 @@ setting.preferences.explorer=Bitcoin block explorer
|
|||
setting.preferences.explorer.bsq=BSQ block explorer
|
||||
setting.preferences.deviation=Max. deviation from market price
|
||||
setting.preferences.avoidStandbyMode=Avoid standby mode
|
||||
setting.preferences.autoConfirmXMR=Use XMR tx proof tool
|
||||
setting.preferences.deviationToLarge=Values higher than {0}% are not allowed.
|
||||
setting.preferences.txFee=Withdrawal transaction fee (satoshis/byte)
|
||||
setting.preferences.useCustomValue=Use custom value
|
||||
|
@ -2488,6 +2492,10 @@ sendPrivateNotificationWindow.send=Send private notification
|
|||
showWalletDataWindow.walletData=Wallet data
|
||||
showWalletDataWindow.includePrivKeys=Include private keys
|
||||
|
||||
setXMRTxKeyWindow.headline=Prove sending of XMR
|
||||
setXMRTxKeyWindow.txHash=Transaction hash
|
||||
setXMRTxKeyWindow.txKey=Tx private key
|
||||
|
||||
# We do not translate the tac because of the legal nature. We would need translations checked by lawyers
|
||||
# in each language which is too expensive atm.
|
||||
tacWindow.headline=User agreement
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.desktop.main.overlays.windows;
|
||||
|
||||
import bisq.desktop.components.InputTextField;
|
||||
import bisq.desktop.main.overlays.Overlay;
|
||||
|
||||
import bisq.core.locale.Res;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
|
||||
import javafx.scene.layout.ColumnConstraints;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Priority;
|
||||
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Insets;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static bisq.desktop.util.FormBuilder.addInputTextField;
|
||||
import static javafx.beans.binding.Bindings.createBooleanBinding;
|
||||
|
||||
public class SetXmrTxKeyWindow extends Overlay<SetXmrTxKeyWindow> {
|
||||
|
||||
private InputTextField txHashInputTextField, txKeyInputTextField;
|
||||
|
||||
public SetXmrTxKeyWindow() {
|
||||
type = Type.Attention;
|
||||
}
|
||||
|
||||
public void show() {
|
||||
if (headLine == null)
|
||||
headLine = Res.get("setXMRTxKeyWindow.headline");
|
||||
|
||||
width = 868;
|
||||
createGridPane();
|
||||
addHeadLine();
|
||||
addContent();
|
||||
addButtons();
|
||||
|
||||
actionButton.disableProperty().bind(createBooleanBinding(() ->
|
||||
txHashInputTextField.getText().isEmpty() || txKeyInputTextField.getText().isEmpty(),
|
||||
txHashInputTextField.textProperty(), txKeyInputTextField.textProperty()));
|
||||
|
||||
applyStyles();
|
||||
display();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createGridPane() {
|
||||
gridPane = new GridPane();
|
||||
gridPane.setHgap(5);
|
||||
gridPane.setVgap(5);
|
||||
gridPane.setPadding(new Insets(64, 64, 64, 64));
|
||||
gridPane.setPrefWidth(width);
|
||||
|
||||
ColumnConstraints columnConstraints1 = new ColumnConstraints();
|
||||
columnConstraints1.setHalignment(HPos.RIGHT);
|
||||
columnConstraints1.setHgrow(Priority.SOMETIMES);
|
||||
gridPane.getColumnConstraints().addAll(columnConstraints1);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getTxHash() {
|
||||
return txHashInputTextField != null ? txHashInputTextField.getText() : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getTxKey() {
|
||||
return txKeyInputTextField != null ? txKeyInputTextField.getText() : null;
|
||||
}
|
||||
|
||||
private void addContent() {
|
||||
txHashInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("setXMRTxKeyWindow.txHash"), 10);
|
||||
txKeyInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("setXMRTxKeyWindow.txKey"));
|
||||
|
||||
UserThread.runAfter(() -> {
|
||||
//todo: remove dev test data
|
||||
txHashInputTextField.setText("5e665addf6d7c6300670e8a89564ed12b5c1a21c336408e2835668f9a6a0d802");
|
||||
txKeyInputTextField.setText("f3ce66c9d395e5e460c8802b2c3c1fff04e508434f9738ee35558aac4678c906");
|
||||
}, 200, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
|
@ -188,8 +188,6 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
|||
final Trade trade = getTrade();
|
||||
checkNotNull(trade, "trade must not be null");
|
||||
checkArgument(trade instanceof BuyerTrade, "Check failed: trade instanceof BuyerTrade");
|
||||
// TODO UI not impl yet
|
||||
trade.setCounterCurrencyTxId("");
|
||||
((BuyerTrade) trade).onFiatPaymentStarted(resultHandler, errorMessageHandler);
|
||||
}
|
||||
|
||||
|
@ -703,5 +701,13 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
|||
public void addTradeToFailedTrades() {
|
||||
tradeManager.addTradeToFailedTrades(selectedTrade);
|
||||
}
|
||||
|
||||
public boolean isSignWitnessTrade() {
|
||||
return accountAgeWitnessService.isSignWitnessTrade(selectedTrade);
|
||||
}
|
||||
|
||||
public void maybeSignWitness() {
|
||||
accountAgeWitnessService.maybeSignWitness(selectedTrade);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,8 +22,6 @@ import bisq.desktop.common.model.ViewModel;
|
|||
import bisq.desktop.util.DisplayUtils;
|
||||
import bisq.desktop.util.GUIUtil;
|
||||
|
||||
import bisq.core.account.sign.SignedWitness;
|
||||
import bisq.core.account.witness.AccountAgeWitness;
|
||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
import bisq.core.btc.wallet.Restrictions;
|
||||
import bisq.core.locale.CurrencyUtil;
|
||||
|
@ -34,17 +32,13 @@ import bisq.core.provider.fee.FeeService;
|
|||
import bisq.core.trade.Contract;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.closed.ClosedTradableManager;
|
||||
import bisq.core.trade.messages.RefreshTradeStateRequest;
|
||||
import bisq.core.trade.messages.TraderSignedWitnessMessage;
|
||||
import bisq.core.user.User;
|
||||
import bisq.core.util.FormattingUtils;
|
||||
import bisq.core.util.coin.BsqFormatter;
|
||||
import bisq.core.util.coin.CoinFormatter;
|
||||
import bisq.core.util.validation.BtcAddressValidator;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.P2PService;
|
||||
import bisq.network.p2p.SendMailboxMessageListener;
|
||||
|
||||
import bisq.common.ClockWatcher;
|
||||
import bisq.common.app.DevEnv;
|
||||
|
@ -63,7 +57,6 @@ import javafx.beans.property.ReadOnlyObjectProperty;
|
|||
import javafx.beans.property.SimpleObjectProperty;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.Getter;
|
||||
|
@ -370,63 +363,6 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
|
|||
.size();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// AccountAgeWitness signing
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
public boolean isSignWitnessTrade() {
|
||||
checkNotNull(trade, "trade must not be null");
|
||||
checkNotNull(trade.getOffer(), "offer must not be null");
|
||||
AccountAgeWitness myWitness = accountAgeWitnessService.getMyWitness(dataModel.getSellersPaymentAccountPayload());
|
||||
|
||||
accountAgeWitnessService.getAccountAgeWitnessUtils().witnessDebugLog(trade, myWitness);
|
||||
|
||||
return accountAgeWitnessService.accountIsSigner(myWitness) &&
|
||||
!accountAgeWitnessService.peerHasSignedWitness(trade) &&
|
||||
accountAgeWitnessService.tradeAmountIsSufficient(trade.getTradeAmount());
|
||||
}
|
||||
|
||||
public void maybeSignWitness() {
|
||||
if (isSignWitnessTrade()) {
|
||||
var signedWitness = accountAgeWitnessService.traderSignPeersAccountAgeWitness(trade);
|
||||
signedWitness.ifPresent(this::sendSignedWitnessToPeer);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendSignedWitnessToPeer(SignedWitness signedWitness) {
|
||||
Trade trade = getTrade();
|
||||
if (trade == null) return;
|
||||
|
||||
NodeAddress tradingPeerNodeAddress = trade.getTradingPeerNodeAddress();
|
||||
var traderSignedWitnessMessage = new TraderSignedWitnessMessage(UUID.randomUUID().toString(), trade.getId(),
|
||||
tradingPeerNodeAddress, signedWitness);
|
||||
|
||||
p2PService.sendEncryptedMailboxMessage(
|
||||
tradingPeerNodeAddress,
|
||||
trade.getProcessModel().getTradingPeer().getPubKeyRing(),
|
||||
traderSignedWitnessMessage,
|
||||
new SendMailboxMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("SendMailboxMessageListener onArrived tradeId={} at peer {} SignedWitness {}",
|
||||
trade.getId(), tradingPeerNodeAddress, signedWitness);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStoredInMailbox() {
|
||||
log.info("SendMailboxMessageListener onStoredInMailbox tradeId={} at peer {} SignedWitness {}",
|
||||
trade.getId(), tradingPeerNodeAddress, signedWitness);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.error("SendMailboxMessageListener onFault tradeId={} at peer {} SignedWitness {}",
|
||||
trade.getId(), tradingPeerNodeAddress, signedWitness);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// States
|
||||
|
|
|
@ -48,6 +48,7 @@ import bisq.desktop.components.paymentmethods.UpholdForm;
|
|||
import bisq.desktop.components.paymentmethods.WeChatPayForm;
|
||||
import bisq.desktop.components.paymentmethods.WesternUnionForm;
|
||||
import bisq.desktop.main.overlays.popups.Popup;
|
||||
import bisq.desktop.main.overlays.windows.SetXmrTxKeyWindow;
|
||||
import bisq.desktop.main.portfolio.pendingtrades.PendingTradesViewModel;
|
||||
import bisq.desktop.main.portfolio.pendingtrades.steps.TradeStepView;
|
||||
import bisq.desktop.util.DisplayUtils;
|
||||
|
@ -384,73 +385,93 @@ public class BuyerStep2View extends TradeStepView {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void onPaymentStarted() {
|
||||
if (model.dataModel.isBootstrappedOrShowPopup()) {
|
||||
if (model.dataModel.getSellersPaymentAccountPayload() instanceof CashDepositAccountPayload) {
|
||||
String key = "confirmPaperReceiptSent";
|
||||
if (!DevEnv.isDevMode() && DontShowAgainLookup.showAgain(key)) {
|
||||
Popup popup = new Popup();
|
||||
popup.headLine(Res.get("portfolio.pending.step2_buyer.paperReceipt.headline"))
|
||||
.feedback(Res.get("portfolio.pending.step2_buyer.paperReceipt.msg"))
|
||||
.onAction(this::showConfirmPaymentStartedPopup)
|
||||
.closeButtonText(Res.get("shared.no"))
|
||||
.onClose(popup::hide)
|
||||
.dontShowAgainId(key)
|
||||
.show();
|
||||
} else {
|
||||
showConfirmPaymentStartedPopup();
|
||||
}
|
||||
} else if (model.dataModel.getSellersPaymentAccountPayload() instanceof WesternUnionAccountPayload) {
|
||||
String key = "westernUnionMTCNSent";
|
||||
if (!DevEnv.isDevMode() && DontShowAgainLookup.showAgain(key)) {
|
||||
String email = ((WesternUnionAccountPayload) model.dataModel.getSellersPaymentAccountPayload()).getEmail();
|
||||
Popup popup = new Popup();
|
||||
popup.headLine(Res.get("portfolio.pending.step2_buyer.westernUnionMTCNInfo.headline"))
|
||||
.feedback(Res.get("portfolio.pending.step2_buyer.westernUnionMTCNInfo.msg", email))
|
||||
.onAction(this::showConfirmPaymentStartedPopup)
|
||||
.actionButtonText(Res.get("shared.yes"))
|
||||
.closeButtonText(Res.get("shared.no"))
|
||||
.onClose(popup::hide)
|
||||
.dontShowAgainId(key)
|
||||
.show();
|
||||
} else {
|
||||
showConfirmPaymentStartedPopup();
|
||||
}
|
||||
} else if (model.dataModel.getSellersPaymentAccountPayload() instanceof MoneyGramAccountPayload) {
|
||||
String key = "moneyGramMTCNSent";
|
||||
if (!DevEnv.isDevMode() && DontShowAgainLookup.showAgain(key)) {
|
||||
String email = ((MoneyGramAccountPayload) model.dataModel.getSellersPaymentAccountPayload()).getEmail();
|
||||
Popup popup = new Popup();
|
||||
popup.headLine(Res.get("portfolio.pending.step2_buyer.moneyGramMTCNInfo.headline"))
|
||||
.feedback(Res.get("portfolio.pending.step2_buyer.moneyGramMTCNInfo.msg", email))
|
||||
.onAction(this::showConfirmPaymentStartedPopup)
|
||||
.actionButtonText(Res.get("shared.yes"))
|
||||
.closeButtonText(Res.get("shared.no"))
|
||||
.onClose(popup::hide)
|
||||
.dontShowAgainId(key)
|
||||
.show();
|
||||
} else {
|
||||
showConfirmPaymentStartedPopup();
|
||||
}
|
||||
} else if (model.dataModel.getSellersPaymentAccountPayload() instanceof HalCashAccountPayload) {
|
||||
String key = "halCashCodeInfo";
|
||||
if (!DevEnv.isDevMode() && DontShowAgainLookup.showAgain(key)) {
|
||||
String mobileNr = ((HalCashAccountPayload) model.dataModel.getSellersPaymentAccountPayload()).getMobileNr();
|
||||
Popup popup = new Popup();
|
||||
popup.headLine(Res.get("portfolio.pending.step2_buyer.halCashInfo.headline"))
|
||||
.feedback(Res.get("portfolio.pending.step2_buyer.halCashInfo.msg",
|
||||
model.dataModel.getTrade().getShortId(), mobileNr))
|
||||
.onAction(this::showConfirmPaymentStartedPopup)
|
||||
.actionButtonText(Res.get("shared.yes"))
|
||||
.closeButtonText(Res.get("shared.no"))
|
||||
.onClose(popup::hide)
|
||||
.dontShowAgainId(key)
|
||||
.show();
|
||||
} else {
|
||||
showConfirmPaymentStartedPopup();
|
||||
}
|
||||
if (!model.dataModel.isBootstrappedOrShowPopup()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PaymentAccountPayload sellersPaymentAccountPayload = model.dataModel.getSellersPaymentAccountPayload();
|
||||
Trade trade = checkNotNull(model.dataModel.getTrade(), "trade must not be null");
|
||||
if (sellersPaymentAccountPayload instanceof CashDepositAccountPayload) {
|
||||
String key = "confirmPaperReceiptSent";
|
||||
if (!DevEnv.isDevMode() && DontShowAgainLookup.showAgain(key)) {
|
||||
Popup popup = new Popup();
|
||||
popup.headLine(Res.get("portfolio.pending.step2_buyer.paperReceipt.headline"))
|
||||
.feedback(Res.get("portfolio.pending.step2_buyer.paperReceipt.msg"))
|
||||
.onAction(this::showConfirmPaymentStartedPopup)
|
||||
.closeButtonText(Res.get("shared.no"))
|
||||
.onClose(popup::hide)
|
||||
.dontShowAgainId(key)
|
||||
.show();
|
||||
} else {
|
||||
showConfirmPaymentStartedPopup();
|
||||
}
|
||||
} else if (sellersPaymentAccountPayload instanceof WesternUnionAccountPayload) {
|
||||
String key = "westernUnionMTCNSent";
|
||||
if (!DevEnv.isDevMode() && DontShowAgainLookup.showAgain(key)) {
|
||||
String email = ((WesternUnionAccountPayload) sellersPaymentAccountPayload).getEmail();
|
||||
Popup popup = new Popup();
|
||||
popup.headLine(Res.get("portfolio.pending.step2_buyer.westernUnionMTCNInfo.headline"))
|
||||
.feedback(Res.get("portfolio.pending.step2_buyer.westernUnionMTCNInfo.msg", email))
|
||||
.onAction(this::showConfirmPaymentStartedPopup)
|
||||
.actionButtonText(Res.get("shared.yes"))
|
||||
.closeButtonText(Res.get("shared.no"))
|
||||
.onClose(popup::hide)
|
||||
.dontShowAgainId(key)
|
||||
.show();
|
||||
} else {
|
||||
showConfirmPaymentStartedPopup();
|
||||
}
|
||||
} else if (sellersPaymentAccountPayload instanceof MoneyGramAccountPayload) {
|
||||
String key = "moneyGramMTCNSent";
|
||||
if (!DevEnv.isDevMode() && DontShowAgainLookup.showAgain(key)) {
|
||||
String email = ((MoneyGramAccountPayload) sellersPaymentAccountPayload).getEmail();
|
||||
Popup popup = new Popup();
|
||||
popup.headLine(Res.get("portfolio.pending.step2_buyer.moneyGramMTCNInfo.headline"))
|
||||
.feedback(Res.get("portfolio.pending.step2_buyer.moneyGramMTCNInfo.msg", email))
|
||||
.onAction(this::showConfirmPaymentStartedPopup)
|
||||
.actionButtonText(Res.get("shared.yes"))
|
||||
.closeButtonText(Res.get("shared.no"))
|
||||
.onClose(popup::hide)
|
||||
.dontShowAgainId(key)
|
||||
.show();
|
||||
} else {
|
||||
showConfirmPaymentStartedPopup();
|
||||
}
|
||||
} else if (sellersPaymentAccountPayload instanceof HalCashAccountPayload) {
|
||||
String key = "halCashCodeInfo";
|
||||
if (!DevEnv.isDevMode() && DontShowAgainLookup.showAgain(key)) {
|
||||
String mobileNr = ((HalCashAccountPayload) sellersPaymentAccountPayload).getMobileNr();
|
||||
Popup popup = new Popup();
|
||||
popup.headLine(Res.get("portfolio.pending.step2_buyer.halCashInfo.headline"))
|
||||
.feedback(Res.get("portfolio.pending.step2_buyer.halCashInfo.msg",
|
||||
trade.getShortId(), mobileNr))
|
||||
.onAction(this::showConfirmPaymentStartedPopup)
|
||||
.actionButtonText(Res.get("shared.yes"))
|
||||
.closeButtonText(Res.get("shared.no"))
|
||||
.onClose(popup::hide)
|
||||
.dontShowAgainId(key)
|
||||
.show();
|
||||
} else {
|
||||
showConfirmPaymentStartedPopup();
|
||||
}
|
||||
} else if (sellersPaymentAccountPayload instanceof AssetsAccountPayload) {
|
||||
Offer offer = checkNotNull(trade.getOffer(), "Offer must not be null");
|
||||
if (offer.getCurrencyCode().equals("XMR")) {
|
||||
SetXmrTxKeyWindow setXmrTxKeyWindow = new SetXmrTxKeyWindow();
|
||||
setXmrTxKeyWindow.actionButtonText(Res.get("portfolio.pending.step2_buyer.confirmStart.headline"))
|
||||
.onAction(() -> {
|
||||
String txKey = setXmrTxKeyWindow.getTxKey();
|
||||
String txHash = setXmrTxKeyWindow.getTxHash();
|
||||
trade.setCounterCurrencyExtraData(txKey);
|
||||
trade.setCounterCurrencyTxId(txHash);
|
||||
showConfirmPaymentStartedPopup();
|
||||
})
|
||||
.closeButtonText(Res.get("shared.cancel"))
|
||||
.onClose(setXmrTxKeyWindow::hide)
|
||||
.show();
|
||||
}
|
||||
} else {
|
||||
showConfirmPaymentStartedPopup();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ import bisq.core.payment.payload.USPostalMoneyOrderAccountPayload;
|
|||
import bisq.core.payment.payload.WesternUnionAccountPayload;
|
||||
import bisq.core.trade.Contract;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.asset.xmr.XmrProofResultWithTradeId;
|
||||
import bisq.core.user.DontShowAgainLookup;
|
||||
|
||||
import bisq.common.Timer;
|
||||
|
@ -59,6 +60,8 @@ import javafx.scene.layout.Priority;
|
|||
import org.fxmisc.easybind.EasyBind;
|
||||
import org.fxmisc.easybind.Subscription;
|
||||
|
||||
import javafx.beans.value.ChangeListener;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static bisq.desktop.util.FormBuilder.addButtonBusyAnimationLabelAfterGroup;
|
||||
|
@ -73,6 +76,8 @@ public class SellerStep3View extends TradeStepView {
|
|||
private BusyAnimation busyAnimation;
|
||||
private Subscription tradeStatePropertySubscription;
|
||||
private Timer timeoutTimer;
|
||||
private final ChangeListener<XmrProofResultWithTradeId> xmrProofResultWithTradeIdListener;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor, Initialisation
|
||||
|
@ -80,6 +85,10 @@ public class SellerStep3View extends TradeStepView {
|
|||
|
||||
public SellerStep3View(PendingTradesViewModel model) {
|
||||
super(model);
|
||||
|
||||
xmrProofResultWithTradeIdListener = (observable, oldValue, newValue) -> {
|
||||
processXmrProofResult(newValue);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -139,6 +148,9 @@ public class SellerStep3View extends TradeStepView {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
model.dataModel.tradeManager.getProofResultWithTradeIdProperty().addListener(xmrProofResultWithTradeIdListener);
|
||||
processXmrProofResult(model.dataModel.tradeManager.getProofResultWithTradeIdProperty().get());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -154,6 +166,9 @@ public class SellerStep3View extends TradeStepView {
|
|||
|
||||
if (timeoutTimer != null)
|
||||
timeoutTimer.stop();
|
||||
|
||||
model.dataModel.tradeManager.getProofResultWithTradeIdProperty().removeListener(xmrProofResultWithTradeIdListener);
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -216,6 +231,20 @@ public class SellerStep3View extends TradeStepView {
|
|||
peersPaymentDetailsTextField.setMouseTransparent(false);
|
||||
peersPaymentDetailsTextField.setTooltip(new Tooltip(peersPaymentDetails));
|
||||
|
||||
String counterCurrencyTxId = trade.getCounterCurrencyTxId();
|
||||
String counterCurrencyExtraData = trade.getCounterCurrencyExtraData();
|
||||
if (counterCurrencyTxId != null && !counterCurrencyTxId.isEmpty() &&
|
||||
counterCurrencyExtraData != null && !counterCurrencyExtraData.isEmpty()) {
|
||||
TextFieldWithCopyIcon txHashTextField = addCompactTopLabelTextFieldWithCopyIcon(gridPane, ++gridRow,
|
||||
0, Res.get("portfolio.pending.step3_seller.xmrTxHash"), counterCurrencyTxId).second;
|
||||
txHashTextField.setMouseTransparent(false);
|
||||
txHashTextField.setTooltip(new Tooltip(myPaymentDetails));
|
||||
|
||||
TextFieldWithCopyIcon txKeyDetailsTextField = addCompactTopLabelTextFieldWithCopyIcon(gridPane, gridRow,
|
||||
1, Res.get("portfolio.pending.step3_seller.xmrTxKey"), counterCurrencyExtraData).second;
|
||||
txKeyDetailsTextField.setMouseTransparent(false);
|
||||
txKeyDetailsTextField.setTooltip(new Tooltip(peersPaymentDetails));
|
||||
}
|
||||
|
||||
Tuple4<Button, BusyAnimation, Label, HBox> tuple = addButtonBusyAnimationLabelAfterGroup(gridPane, ++gridRow,
|
||||
Res.get("portfolio.pending.step3_seller.confirmReceipt"));
|
||||
|
@ -294,7 +323,7 @@ public class SellerStep3View extends TradeStepView {
|
|||
}
|
||||
}
|
||||
message += Res.get("portfolio.pending.step3_seller.onPaymentReceived.note");
|
||||
if (model.isSignWitnessTrade()) {
|
||||
if (model.dataModel.isSignWitnessTrade()) {
|
||||
message += Res.get("portfolio.pending.step3_seller.onPaymentReceived.signer");
|
||||
}
|
||||
new Popup()
|
||||
|
@ -351,7 +380,7 @@ public class SellerStep3View extends TradeStepView {
|
|||
message += Res.get("portfolio.pending.step3_seller.bankCheck", optionalHolderName.get(), part);
|
||||
}
|
||||
|
||||
if (model.isSignWitnessTrade()) {
|
||||
if (model.dataModel.isSignWitnessTrade()) {
|
||||
message += Res.get("portfolio.pending.step3_seller.onPaymentReceived.signer");
|
||||
}
|
||||
}
|
||||
|
@ -370,7 +399,7 @@ public class SellerStep3View extends TradeStepView {
|
|||
if (!trade.isPayoutPublished())
|
||||
trade.setState(Trade.State.SELLER_CONFIRMED_IN_UI_FIAT_PAYMENT_RECEIPT);
|
||||
|
||||
model.maybeSignWitness();
|
||||
model.dataModel.maybeSignWitness();
|
||||
|
||||
model.dataModel.onFiatPaymentReceived(() -> {
|
||||
// In case the first send failed we got the support button displayed.
|
||||
|
@ -406,6 +435,47 @@ public class SellerStep3View extends TradeStepView {
|
|||
protected void deactivatePaymentButtons(boolean isDisabled) {
|
||||
confirmButton.setDisable(isDisabled);
|
||||
}
|
||||
|
||||
private void processXmrProofResult(XmrProofResultWithTradeId result) {
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Trade trade = model.dataModel.getTrade();
|
||||
if (trade == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.getTradeId().equals(trade.getId())) {
|
||||
boolean hasFailed;
|
||||
switch (result.getXmrProofResult()) {
|
||||
case TX_NOT_CONFIRMED:
|
||||
case PROOF_OK:
|
||||
hasFailed = false;
|
||||
break;
|
||||
case UNKNOWN_ERROR:
|
||||
case TX_KEY_REUSED:
|
||||
case TX_NEVER_FOUND:
|
||||
case TX_HASH_INVALID:
|
||||
case TX_KEY_INVALID:
|
||||
case ADDRESS_INVALID:
|
||||
case AMOUNT_NOT_MATCHING:
|
||||
case PROOF_FAILED:
|
||||
default:
|
||||
hasFailed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (hasFailed) {
|
||||
// We don't show yet translated messages for the diff. error cases but the ENUM name.
|
||||
new Popup().warning(Res.get("portfolio.pending.step3_seller.xmrTxVerificationError",
|
||||
result.getTradeId(),
|
||||
result.getXmrProofResult().toString()))
|
||||
.width(800)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -100,9 +100,6 @@ import static com.google.common.base.Preconditions.checkArgument;
|
|||
|
||||
@FxmlView
|
||||
public class PreferencesView extends ActivatableViewAndModel<GridPane, PreferencesViewModel> {
|
||||
|
||||
// not supported yet
|
||||
//private ComboBox<String> btcDenominationComboBox;
|
||||
private ComboBox<BlockChainExplorer> blockChainExplorerComboBox;
|
||||
private ComboBox<BlockChainExplorer> bsqBlockChainExplorerComboBox;
|
||||
private ComboBox<String> userLanguageComboBox;
|
||||
|
@ -110,7 +107,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
|
|||
private ComboBox<TradeCurrency> preferredTradeCurrencyComboBox;
|
||||
|
||||
private ToggleButton showOwnOffersInOfferBook, useAnimations, useDarkMode, sortMarketCurrenciesNumerically,
|
||||
avoidStandbyMode, useCustomFee;
|
||||
avoidStandbyMode, useCustomFee, autoConfirmXmr;
|
||||
private int gridRow = 0;
|
||||
private InputTextField transactionFeeInputTextField, ignoreTradersListInputTextField, ignoreDustThresholdInputTextField,
|
||||
/*referralIdInputTextField,*/
|
||||
|
@ -133,7 +130,6 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
|
|||
private ListView<CryptoCurrency> cryptoCurrenciesListView;
|
||||
private ComboBox<CryptoCurrency> cryptoCurrenciesComboBox;
|
||||
private Button resetDontShowAgainButton, resyncDaoFromGenesisButton, resyncDaoFromResourcesButton;
|
||||
// private ListChangeListener<TradeCurrency> displayCurrenciesListChangeListener;
|
||||
private ObservableList<BlockChainExplorer> blockExplorers;
|
||||
private ObservableList<BlockChainExplorer> bsqBlockChainExplorers;
|
||||
private ObservableList<String> languageCodes;
|
||||
|
@ -232,7 +228,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void initializeGeneralOptions() {
|
||||
int titledGroupBgRowSpan = displayStandbyModeFeature ? 8 : 7;
|
||||
int titledGroupBgRowSpan = displayStandbyModeFeature ? 10 : 9;
|
||||
TitledGroupBg titledGroupBg = addTitledGroupBg(root, gridRow, titledGroupBgRowSpan, Res.get("setting.preferences.general"));
|
||||
GridPane.setColumnSpan(titledGroupBg, 1);
|
||||
|
||||
|
@ -367,6 +363,8 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
|
|||
}
|
||||
};
|
||||
|
||||
autoConfirmXmr = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.autoConfirmXmr"));
|
||||
|
||||
if (displayStandbyModeFeature) {
|
||||
// AvoidStandbyModeService feature works only on OSX & Windows
|
||||
avoidStandbyMode = addSlideToggleButton(root, ++gridRow,
|
||||
|
@ -592,7 +590,6 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
|
|||
TitledGroupBg titledGroupBg = addTitledGroupBg(root, ++gridRow, 5, Res.get("setting.preferences.displayOptions"), Layout.GROUP_DISTANCE);
|
||||
GridPane.setColumnSpan(titledGroupBg, 1);
|
||||
|
||||
// showOwnOffersInOfferBook = addLabelCheckBox(root, gridRow, Res.get("setting.preferences.showOwnOffers"), Layout.FIRST_ROW_AND_GROUP_DISTANCE);
|
||||
showOwnOffersInOfferBook = addSlideToggleButton(root, gridRow, Res.get("setting.preferences.showOwnOffers"), Layout.FIRST_ROW_AND_GROUP_DISTANCE);
|
||||
useAnimations = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.useAnimations"));
|
||||
useDarkMode = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.useDarkMode"));
|
||||
|
@ -649,18 +646,6 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void activateGeneralOptions() {
|
||||
/* List<BaseCurrencyNetwork> baseCurrencyNetworks = Arrays.asList(BaseCurrencyNetwork.values());
|
||||
|
||||
// We allow switching to testnet to make it easier for users to test the testnet DAO version
|
||||
// We only show mainnet and dao testnet. Testnet is rather un-usable for application testing when asics
|
||||
// create 10000s of blocks per day.
|
||||
baseCurrencyNetworks = baseCurrencyNetworks.stream()
|
||||
.filter(e -> e.isMainnet() || e.isDaoBetaNet() || e.isDaoRegTest())
|
||||
.collect(Collectors.toList());
|
||||
selectBaseCurrencyNetworkComboBox.setItems(FXCollections.observableArrayList(baseCurrencyNetworks));
|
||||
selectBaseCurrencyNetworkComboBox.setOnAction(e -> onSelectNetwork());
|
||||
selectBaseCurrencyNetworkComboBox.getSelectionModel().select(BaseCurrencyNetwork.CURRENT_VALUE);*/
|
||||
|
||||
boolean useCustomWithdrawalTxFee = preferences.isUseCustomWithdrawalTxFee();
|
||||
useCustomFee.setSelected(useCustomWithdrawalTxFee);
|
||||
|
||||
|
@ -705,17 +690,6 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
|
|||
.show();
|
||||
}
|
||||
}
|
||||
// Should we apply the changed currency immediately to the language list?
|
||||
// If so and the user selects a unknown language he might get lost and it is hard to find
|
||||
// again the language he understands
|
||||
/* if (selectedItem != null && !selectedItem.equals(preferences.getUserLanguage())) {
|
||||
preferences.setUserLanguage(selectedItem);
|
||||
UserThread.execute(() -> {
|
||||
languageCodes.clear();
|
||||
languageCodes.addAll(LanguageUtil.getAllLanguageCodes());
|
||||
userLanguageComboBox.getSelectionModel().select(preferences.getUserLanguage());
|
||||
});
|
||||
}*/
|
||||
});
|
||||
|
||||
userCountryComboBox.setItems(countries);
|
||||
|
@ -839,9 +813,6 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
|
|||
useDarkMode.setSelected(preferences.getCssTheme() == 1);
|
||||
useDarkMode.setOnAction(e -> preferences.setCssTheme(useDarkMode.isSelected()));
|
||||
|
||||
// useStickyMarketPriceCheckBox.setSelected(preferences.isUseStickyMarketPrice());
|
||||
// useStickyMarketPriceCheckBox.setOnAction(e -> preferences.setUseStickyMarketPrice(useStickyMarketPriceCheckBox.isSelected()));
|
||||
|
||||
sortMarketCurrenciesNumerically.setSelected(preferences.isSortMarketCurrenciesNumerically());
|
||||
sortMarketCurrenciesNumerically.setOnAction(e -> preferences.setSortMarketCurrenciesNumerically(sortMarketCurrenciesNumerically.isSelected()));
|
||||
|
||||
|
@ -855,6 +826,9 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
|
|||
} else {
|
||||
preferences.setUseStandbyMode(false);
|
||||
}
|
||||
|
||||
autoConfirmXmr.setSelected(preferences.isAutoConfirmXmr());
|
||||
autoConfirmXmr.setOnAction(e -> preferences.setAutoConfirmXmr(autoConfirmXmr.isSelected()));
|
||||
}
|
||||
|
||||
private void activateDaoPreferences() {
|
||||
|
@ -943,22 +917,6 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
|
|||
blockNotifyPortTextField.setDisable(daoOptionsSet);
|
||||
}
|
||||
|
||||
/* private void onSelectNetwork() {
|
||||
if (selectBaseCurrencyNetworkComboBox.getSelectionModel().getSelectedItem() != BaseCurrencyNetwork.CURRENT_VALUE)
|
||||
selectNetwork();
|
||||
}
|
||||
|
||||
private void selectNetwork() {
|
||||
new Popup().warning(Res.get("settings.net.needRestart"))
|
||||
.onAction(() -> {
|
||||
bisqEnvironment.saveBaseCryptoNetwork(selectBaseCurrencyNetworkComboBox.getSelectionModel().getSelectedItem());
|
||||
UserThread.runAfter(BisqApp.getShutDownHandler(), 500, TimeUnit.MILLISECONDS);
|
||||
})
|
||||
.actionButtonText(Res.get("shared.shutDown"))
|
||||
.closeButtonText(Res.get("shared.cancel"))
|
||||
.onClose(() -> selectBaseCurrencyNetworkComboBox.getSelectionModel().select(BaseCurrencyNetwork.CURRENT_VALUE))
|
||||
.show();
|
||||
}*/
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Deactivate
|
||||
|
@ -995,6 +953,8 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
|
|||
if (displayStandbyModeFeature) {
|
||||
avoidStandbyMode.setOnAction(null);
|
||||
}
|
||||
|
||||
autoConfirmXmr.setOnAction(null);
|
||||
}
|
||||
|
||||
private void deactivateDaoPreferences() {
|
||||
|
|
|
@ -293,6 +293,7 @@ message CounterCurrencyTransferStartedMessage {
|
|||
bytes buyer_signature = 4;
|
||||
string counter_currency_tx_id = 5;
|
||||
string uid = 6;
|
||||
string counter_currency_extra_data = 7;
|
||||
}
|
||||
|
||||
message FinalizePayoutTxRequest {
|
||||
|
@ -1385,6 +1386,7 @@ message Trade {
|
|||
PubKeyRing refund_agent_pub_key_ring = 34;
|
||||
RefundResultState refund_result_state = 35;
|
||||
int64 last_refresh_request_date = 36;
|
||||
string counter_currency_extra_data = 37;
|
||||
}
|
||||
|
||||
message BuyerAsMakerTrade {
|
||||
|
@ -1545,6 +1547,7 @@ message PreferencesPayload {
|
|||
int32 block_notify_port = 53;
|
||||
int32 css_theme = 54;
|
||||
bool tac_accepted_v120 = 55;
|
||||
bool auto_confirm_xmr = 56;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
Loading…
Add table
Reference in a new issue