mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-08-02 03:36:24 -04:00
P2P status indicator with update prompt
Co-authored-by: jmacxx <47253594+jmacxx@users.noreply.github.com> Co-authored-by: Christoph Atteneder <christoph.atteneder@gmail.com>
This commit is contained in:
parent
a2929035bc
commit
bd70b935e4
13 changed files with 354 additions and 64 deletions
|
@ -22,6 +22,11 @@ import bisq.network.p2p.NodeAddress;
|
|||
import bisq.network.p2p.P2PService;
|
||||
import bisq.network.p2p.SendMailboxMessageListener;
|
||||
import bisq.network.p2p.mailbox.MailboxMessageService;
|
||||
import bisq.network.p2p.network.Connection;
|
||||
import bisq.network.p2p.network.MessageListener;
|
||||
import bisq.network.p2p.network.NetworkNode;
|
||||
import bisq.network.p2p.peers.keepalive.messages.Ping;
|
||||
import bisq.network.p2p.peers.keepalive.messages.Pong;
|
||||
|
||||
import bisq.common.app.DevEnv;
|
||||
import bisq.common.config.Config;
|
||||
|
@ -36,6 +41,10 @@ import javax.inject.Inject;
|
|||
import javax.inject.Named;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
|
@ -45,16 +54,20 @@ import java.security.SignatureException;
|
|||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static org.bitcoinj.core.Utils.HEX;
|
||||
|
||||
public class PrivateNotificationManager {
|
||||
public class PrivateNotificationManager implements MessageListener {
|
||||
private static final Logger log = LoggerFactory.getLogger(PrivateNotificationManager.class);
|
||||
|
||||
private final P2PService p2PService;
|
||||
|
@ -68,6 +81,8 @@ public class PrivateNotificationManager {
|
|||
private ECKey privateNotificationSigningKey;
|
||||
@Nullable
|
||||
private PrivateNotificationMessage privateNotificationMessage;
|
||||
private final NetworkNode networkNode;
|
||||
private Consumer<String> pingResponseHandler = null;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -76,11 +91,13 @@ public class PrivateNotificationManager {
|
|||
|
||||
@Inject
|
||||
public PrivateNotificationManager(P2PService p2PService,
|
||||
NetworkNode networkNode,
|
||||
MailboxMessageService mailboxMessageService,
|
||||
KeyRing keyRing,
|
||||
@Named(Config.IGNORE_DEV_MSG) boolean ignoreDevMsg,
|
||||
@Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) {
|
||||
this.p2PService = p2PService;
|
||||
this.networkNode = networkNode;
|
||||
this.mailboxMessageService = mailboxMessageService;
|
||||
this.keyRing = keyRing;
|
||||
|
||||
|
@ -173,5 +190,38 @@ public class PrivateNotificationManager {
|
|||
}
|
||||
}
|
||||
|
||||
public void sendPing(NodeAddress peersNodeAddress, Consumer<String> resultHandler) {
|
||||
Ping ping = new Ping(new Random().nextInt(), 0);
|
||||
log.info("Send Ping to peer {}, nonce={}", peersNodeAddress, ping.getNonce());
|
||||
SettableFuture<Connection> future = networkNode.sendMessage(peersNodeAddress, ping);
|
||||
Futures.addCallback(future, new FutureCallback<>() {
|
||||
@Override
|
||||
public void onSuccess(Connection connection) {
|
||||
connection.addMessageListener(PrivateNotificationManager.this);
|
||||
pingResponseHandler = resultHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NotNull Throwable throwable) {
|
||||
String errorMessage = "Sending ping to " + peersNodeAddress.getHostNameForDisplay() +
|
||||
" failed. That is expected if the peer is offline.\n\tping=" + ping +
|
||||
".\n\tException=" + throwable.getMessage();
|
||||
log.info(errorMessage);
|
||||
resultHandler.accept(errorMessage);
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(NetworkEnvelope networkEnvelope, Connection connection) {
|
||||
if (networkEnvelope instanceof Pong) {
|
||||
Pong pong = (Pong) networkEnvelope;
|
||||
String key = connection.getPeersNodeAddressOptional().get().getFullAddress();
|
||||
log.info("Received Pong! {} from {}", pong, key);
|
||||
connection.removeMessageListener(this);
|
||||
if (pingResponseHandler != null) {
|
||||
pingResponseHandler.accept("SUCCESS");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,7 +96,7 @@ public class HavenoHeadlessApp implements HeadlessApp {
|
|||
bisqSetup.setQubesOSInfoHandler(() -> log.info("setQubesOSInfoHandler"));
|
||||
bisqSetup.setDownGradePreventionHandler(lastVersion -> log.info("Downgrade from version {} to version {} is not supported",
|
||||
lastVersion, Version.VERSION));
|
||||
|
||||
bisqSetup.setTorAddressUpgradeHandler(() -> log.info("setTorAddressUpgradeHandler"));
|
||||
corruptedStorageFileHandler.getFiles().ifPresent(files -> log.warn("getCorruptedDatabaseFiles. files={}", files));
|
||||
tradeManager.setTakeOfferRequestErrorMessageHandler(errorMessage -> log.error("Error taking offer: " + errorMessage));
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import bisq.core.account.sign.SignedWitnessStorageService;
|
|||
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
import bisq.core.alert.Alert;
|
||||
import bisq.core.alert.AlertManager;
|
||||
import bisq.core.alert.PrivateNotificationManager;
|
||||
import bisq.core.alert.PrivateNotificationPayload;
|
||||
import bisq.core.api.CoreMoneroNodeService;
|
||||
import bisq.core.btc.model.AddressEntry;
|
||||
|
@ -37,6 +38,10 @@ import bisq.core.payment.AmazonGiftCardAccount;
|
|||
import bisq.core.payment.PaymentAccount;
|
||||
import bisq.core.payment.RevolutAccount;
|
||||
import bisq.core.payment.payload.PaymentMethod;
|
||||
import bisq.core.support.dispute.Dispute;
|
||||
import bisq.core.support.dispute.arbitration.ArbitrationManager;
|
||||
import bisq.core.support.dispute.mediation.MediationManager;
|
||||
import bisq.core.support.dispute.refund.RefundManager;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.trade.TradeTxException;
|
||||
import bisq.core.user.Preferences;
|
||||
|
@ -45,8 +50,10 @@ import bisq.core.util.FormattingUtils;
|
|||
import bisq.core.util.coin.CoinFormatter;
|
||||
|
||||
import bisq.network.Socks5ProxyProvider;
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.P2PService;
|
||||
import bisq.network.p2p.storage.payload.PersistableNetworkPayload;
|
||||
import bisq.network.utils.Utils;
|
||||
|
||||
import bisq.common.Timer;
|
||||
import bisq.common.UserThread;
|
||||
|
@ -86,6 +93,8 @@ import java.io.IOException;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Random;
|
||||
import java.util.Scanner;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -106,19 +115,6 @@ public class HavenoSetup {
|
|||
private static final String VERSION_FILE_NAME = "version";
|
||||
private static final String RESYNC_SPV_FILE_NAME = "resyncSpv";
|
||||
|
||||
public interface HavenoSetupListener {
|
||||
default void onInitP2pNetwork() {
|
||||
}
|
||||
|
||||
default void onInitWallet() {
|
||||
}
|
||||
|
||||
default void onRequestWalletPassword() {
|
||||
}
|
||||
|
||||
void onSetupComplete();
|
||||
}
|
||||
|
||||
private static final long STARTUP_TIMEOUT_MINUTES = 4;
|
||||
|
||||
private final DomainInitialisation domainInitialisation;
|
||||
|
@ -129,6 +125,7 @@ public class HavenoSetup {
|
|||
private final BtcWalletService btcWalletService;
|
||||
private final XmrWalletService xmrWalletService;
|
||||
private final P2PService p2PService;
|
||||
private final PrivateNotificationManager privateNotificationManager;
|
||||
private final SignedWitnessStorageService signedWitnessStorageService;
|
||||
private final TradeManager tradeManager;
|
||||
private final OpenOfferManager openOfferManager;
|
||||
|
@ -141,7 +138,9 @@ public class HavenoSetup {
|
|||
private final CoinFormatter formatter;
|
||||
private final LocalBitcoinNode localBitcoinNode;
|
||||
private final AppStartupState appStartupState;
|
||||
|
||||
private final MediationManager mediationManager;
|
||||
private final RefundManager refundManager;
|
||||
private final ArbitrationManager arbitrationManager;
|
||||
@Setter
|
||||
@Nullable
|
||||
private Consumer<Runnable> displayTacHandler;
|
||||
|
@ -188,8 +187,10 @@ public class HavenoSetup {
|
|||
private Runnable qubesOSInfoHandler;
|
||||
@Setter
|
||||
@Nullable
|
||||
private Runnable torAddressUpgradeHandler;
|
||||
@Setter
|
||||
@Nullable
|
||||
private Consumer<String> downGradePreventionHandler;
|
||||
|
||||
@Getter
|
||||
final BooleanProperty newVersionAvailableProperty = new SimpleBooleanProperty(false);
|
||||
private BooleanProperty p2pNetworkReady;
|
||||
|
@ -199,6 +200,19 @@ public class HavenoSetup {
|
|||
private MonadicBinding<Boolean> p2pNetworkAndWalletInitialized;
|
||||
private final List<HavenoSetupListener> havenoSetupListeners = new ArrayList<>();
|
||||
|
||||
public interface HavenoSetupListener {
|
||||
default void onInitP2pNetwork() {
|
||||
}
|
||||
|
||||
default void onInitWallet() {
|
||||
}
|
||||
|
||||
default void onRequestWalletPassword() {
|
||||
}
|
||||
|
||||
void onSetupComplete();
|
||||
}
|
||||
|
||||
@Inject
|
||||
public HavenoSetup(DomainInitialisation domainInitialisation,
|
||||
P2PNetworkSetup p2PNetworkSetup,
|
||||
|
@ -208,6 +222,7 @@ public class HavenoSetup {
|
|||
XmrWalletService xmrWalletService,
|
||||
BtcWalletService btcWalletService,
|
||||
P2PService p2PService,
|
||||
PrivateNotificationManager privateNotificationManager,
|
||||
SignedWitnessStorageService signedWitnessStorageService,
|
||||
TradeManager tradeManager,
|
||||
OpenOfferManager openOfferManager,
|
||||
|
@ -220,7 +235,10 @@ public class HavenoSetup {
|
|||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter,
|
||||
LocalBitcoinNode localBitcoinNode,
|
||||
AppStartupState appStartupState,
|
||||
Socks5ProxyProvider socks5ProxyProvider) {
|
||||
Socks5ProxyProvider socks5ProxyProvider,
|
||||
MediationManager mediationManager,
|
||||
RefundManager refundManager,
|
||||
ArbitrationManager arbitrationManager) {
|
||||
this.domainInitialisation = domainInitialisation;
|
||||
this.p2PNetworkSetup = p2PNetworkSetup;
|
||||
this.walletAppSetup = walletAppSetup;
|
||||
|
@ -229,6 +247,7 @@ public class HavenoSetup {
|
|||
this.xmrWalletService = xmrWalletService;
|
||||
this.btcWalletService = btcWalletService;
|
||||
this.p2PService = p2PService;
|
||||
this.privateNotificationManager = privateNotificationManager;
|
||||
this.signedWitnessStorageService = signedWitnessStorageService;
|
||||
this.tradeManager = tradeManager;
|
||||
this.openOfferManager = openOfferManager;
|
||||
|
@ -241,6 +260,9 @@ public class HavenoSetup {
|
|||
this.formatter = formatter;
|
||||
this.localBitcoinNode = localBitcoinNode;
|
||||
this.appStartupState = appStartupState;
|
||||
this.mediationManager = mediationManager;
|
||||
this.refundManager = refundManager;
|
||||
this.arbitrationManager = arbitrationManager;
|
||||
|
||||
MemPoolSpaceTxBroadcaster.init(socks5ProxyProvider, preferences, localBitcoinNode);
|
||||
}
|
||||
|
@ -313,6 +335,8 @@ public class HavenoSetup {
|
|||
maybeShowSecurityRecommendation();
|
||||
maybeShowLocalhostRunningInfo();
|
||||
maybeShowAccountSigningStateInfo();
|
||||
maybeShowTorAddressUpgradeInformation();
|
||||
checkInboundConnections();
|
||||
}
|
||||
|
||||
|
||||
|
@ -663,6 +687,37 @@ public class HavenoSetup {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we have inbound connections. If not, try to ping ourselves.
|
||||
* If Haveno cannot connect to its own onion address through Tor, display
|
||||
* an informative message to let the user know to configure their firewall else
|
||||
* their offers will not be reachable.
|
||||
* Repeat this test hourly.
|
||||
*/
|
||||
private void checkInboundConnections() {
|
||||
NodeAddress onionAddress = p2PService.getNetworkNode().nodeAddressProperty().get();
|
||||
if (onionAddress == null || !onionAddress.getFullAddress().contains("onion")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (p2PService.getNetworkNode().upTime() > TimeUnit.HOURS.toMillis(1) &&
|
||||
p2PService.getNetworkNode().getInboundConnectionCount() == 0) {
|
||||
// we've been online a while and did not find any inbound connections; lets try the self-ping check
|
||||
log.info("no recent inbound connections found, starting the self-ping test");
|
||||
privateNotificationManager.sendPing(onionAddress, stringResult -> {
|
||||
log.info(stringResult);
|
||||
if (stringResult.contains("failed")) {
|
||||
getP2PNetworkStatusIconId().set("flashing:image-yellow_circle");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// schedule another inbound connection check for later
|
||||
int nextCheckInMinutes = 30 + new Random().nextInt(30);
|
||||
log.debug("next inbound connections check in {} minutes", nextCheckInMinutes);
|
||||
UserThread.runAfter(this::checkInboundConnections, nextCheckInMinutes, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
private void maybeShowSecurityRecommendation() {
|
||||
if (user.getPaymentAccountsAsObservable() == null) return;
|
||||
String key = "remindPasswordAndBackup";
|
||||
|
@ -733,6 +788,30 @@ public class HavenoSetup {
|
|||
}
|
||||
}
|
||||
|
||||
private void maybeShowTorAddressUpgradeInformation() {
|
||||
if (Config.baseCurrencyNetwork().isTestnet() ||
|
||||
Utils.isV3Address(Objects.requireNonNull(p2PService.getNetworkNode().getNodeAddress()).getHostName())) {
|
||||
return;
|
||||
}
|
||||
|
||||
maybeRunTorNodeAddressUpgradeHandler();
|
||||
|
||||
tradeManager.getNumPendingTrades().addListener((observable, oldValue, newValue) -> {
|
||||
long numPendingTrades = (long) newValue;
|
||||
if (numPendingTrades == 0) {
|
||||
maybeRunTorNodeAddressUpgradeHandler();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void maybeRunTorNodeAddressUpgradeHandler() {
|
||||
if (mediationManager.getDisputesAsObservableList().stream().allMatch(Dispute::isClosed) &&
|
||||
refundManager.getDisputesAsObservableList().stream().allMatch(Dispute::isClosed) &&
|
||||
arbitrationManager.getDisputesAsObservableList().stream().allMatch(Dispute::isClosed) &&
|
||||
tradeManager.getNumPendingTrades().isEqualTo(0).get()) {
|
||||
Objects.requireNonNull(torAddressUpgradeHandler).run();
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Getters
|
||||
|
@ -776,6 +855,10 @@ public class HavenoSetup {
|
|||
return p2PNetworkSetup.getP2PNetworkIconId();
|
||||
}
|
||||
|
||||
public StringProperty getP2PNetworkStatusIconId() {
|
||||
return p2PNetworkSetup.getP2PNetworkStatusIconId();
|
||||
}
|
||||
|
||||
public BooleanProperty getUpdatedDataReceived() {
|
||||
return p2PNetworkSetup.getUpdatedDataReceived();
|
||||
}
|
||||
|
|
|
@ -63,6 +63,8 @@ public class P2PNetworkSetup {
|
|||
@Getter
|
||||
final StringProperty p2PNetworkIconId = new SimpleStringProperty();
|
||||
@Getter
|
||||
final StringProperty p2PNetworkStatusIconId = new SimpleStringProperty();
|
||||
@Getter
|
||||
final BooleanProperty splashP2PNetworkAnimationVisible = new SimpleBooleanProperty(true);
|
||||
@Getter
|
||||
final StringProperty p2pNetworkLabelId = new SimpleStringProperty("footer-pane");
|
||||
|
@ -118,10 +120,12 @@ public class P2PNetworkSetup {
|
|||
p2PService.getNetworkNode().addConnectionListener(new ConnectionListener() {
|
||||
@Override
|
||||
public void onConnection(Connection connection) {
|
||||
updateNetworkStatusIndicator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnect(CloseConnectionReason closeConnectionReason, Connection connection) {
|
||||
updateNetworkStatusIndicator();
|
||||
// We only check at seed nodes as they are running the latest version
|
||||
// Other disconnects might be caused by peers running an older version
|
||||
if (connection.getConnectionState().isSeedNode() &&
|
||||
|
@ -225,4 +229,14 @@ public class P2PNetworkSetup {
|
|||
public void setSplashP2PNetworkAnimationVisible(boolean value) {
|
||||
splashP2PNetworkAnimationVisible.set(value);
|
||||
}
|
||||
|
||||
private void updateNetworkStatusIndicator() {
|
||||
if (p2PService.getNetworkNode().getInboundConnectionCount() > 0) {
|
||||
p2PNetworkStatusIconId.set("image-green_circle");
|
||||
} else if (p2PService.getNetworkNode().getOutboundConnectionCount() > 0) {
|
||||
p2PNetworkStatusIconId.set("image-yellow_circle");
|
||||
} else {
|
||||
p2PNetworkStatusIconId.set("image-alert-round");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -287,6 +287,7 @@ mainView.walletServiceErrorMsg.rejectedTxException=A transaction was rejected fr
|
|||
mainView.networkWarning.allConnectionsLost=You lost the connection to all {0} network peers.\nMaybe you lost your internet connection or your computer was in standby mode.
|
||||
mainView.networkWarning.localhostBitcoinLost=You lost the connection to the localhost Monero node.\nPlease restart the Haveno application to connect to other Monero nodes or restart the localhost Monero node.
|
||||
mainView.version.update=(Update available)
|
||||
mainView.status.connections=Inbound connections: {0}\nOutbound connections: {1}
|
||||
|
||||
|
||||
####################################################################
|
||||
|
@ -2136,6 +2137,12 @@ popup.info.shutDownWithTradeInit={0}\n\
|
|||
This trade has not finished initializing; shutting down now will probably make it corrupted. Please wait a minute and try again.
|
||||
popup.info.qubesOSSetupInfo=It appears you are running Bisq on Qubes OS. \n\n\
|
||||
Please make sure your Bisq qube is setup according to our Setup Guide at [HYPERLINK:https://bisq.wiki/Running_Bisq_on_Qubes].
|
||||
popup.info.p2pStatusIndicator.red={0}\n\n\
|
||||
Your node has no connection to the P2P network. Haveno cannot operate in this state.
|
||||
popup.info.p2pStatusIndicator.yellow={0}\n\n\
|
||||
Your node has no inbound Tor connections. Haveno will function ok, but if this state persists for several hours it may be an indication of connectivity problems.
|
||||
popup.info.p2pStatusIndicator.green={0}\n\n\
|
||||
Good news, your P2P connection state looks healthy!
|
||||
popup.info.firewallSetupInfo=It appears this machine blocks incoming Tor connections. \
|
||||
This can happen in VM environments such as Qubes/VirtualBox/Whonix. \n\n\
|
||||
Please set up your environment to accept incoming Tor connections, otherwise no-one will be able to take your offers.
|
||||
|
@ -2196,13 +2203,17 @@ popup.accountSigning.unsignedPubKeys.signed=Pubkeys were signed
|
|||
popup.accountSigning.unsignedPubKeys.result.signed=Signed pubkeys
|
||||
popup.accountSigning.unsignedPubKeys.result.failed=Failed to sign
|
||||
|
||||
popup.info.torMigration.msg=Your Haveno node is probably using a deprecated Tor v2 address. \
|
||||
Please switch your Haveno node to a Tor v3 address. \
|
||||
Make sure to back up your data directory beforehand.
|
||||
|
||||
####################################################################
|
||||
# Notifications
|
||||
####################################################################
|
||||
|
||||
notification.trade.headline=Notification for trade with ID {0}
|
||||
notification.ticket.headline=Support ticket for trade with ID {0}
|
||||
notification.trade.completed=The trade is now completed and you can withdraw your funds.
|
||||
notification.trade.completed=The trade is now completed, and you can withdraw your funds.
|
||||
notification.trade.accepted=Your offer has been accepted by a XMR {0}.
|
||||
notification.trade.unlocked=Your trade has been confirmed.\nYou can start the payment now.
|
||||
notification.trade.paymentStarted=The XMR buyer has started the payment.
|
||||
|
@ -2232,7 +2243,7 @@ systemTray.tooltip=Haveno: A decentralized bitcoin exchange network
|
|||
####################################################################
|
||||
|
||||
guiUtil.miningFeeInfo=Please be sure that the mining fee used by your external wallet is \
|
||||
at least {0} satoshis/vbyte. Otherwise the trade transactions may not be confirmed in time and the trade will end up in a dispute.
|
||||
at least {0} satoshis/vbyte. Otherwise, the trade transactions may not be confirmed in time and the trade will end up in a dispute.
|
||||
|
||||
guiUtil.accountExport.savedToPath=Trading accounts saved to path:\n{0}
|
||||
guiUtil.accountExport.noAccountSetup=You don't have trading accounts set up for exporting.
|
||||
|
@ -2447,7 +2458,7 @@ payment.altcoin.address=Cryptocurrency address
|
|||
payment.altcoin.tradeInstantCheckbox=Trade instant (within 1 hour) with this Cryptocurrency
|
||||
payment.altcoin.tradeInstant.popup=For instant trading it is required that both trading peers are online to be able \
|
||||
to complete the trade in less than 1 hour.\n\n\
|
||||
If you have offers open and you are not available please disable \
|
||||
If you have offers open, and you are not available please disable \
|
||||
those offers under the 'Portfolio' screen.
|
||||
payment.altcoin=Cryptocurrency
|
||||
payment.select.altcoin=Select or search Cryptocurrency
|
||||
|
@ -2563,7 +2574,7 @@ payment.westernUnion.info=When using Western Union the XMR buyer has to send the
|
|||
payment.halCash.info=When using HalCash the XMR buyer needs to send the XMR seller the HalCash code via a text message from their mobile phone.\n\n\
|
||||
Please make sure to not exceed the maximum amount your bank allows you to send with HalCash. \
|
||||
The min. amount per withdrawal is 10 EUR and the max. amount is 600 EUR. For repeated withdrawals it is \
|
||||
3000 EUR per receiver per day and 6000 EUR per receiver per month. Please cross check those limits with your \
|
||||
3000 EUR per receiver per day and 6000 EUR per receiver per month. Please cross-check those limits with your \
|
||||
bank to be sure they use the same limits as stated here.\n\n\
|
||||
The withdrawal amount must be a multiple of 10 EUR as you cannot withdraw other amounts from an ATM. The \
|
||||
UI in the create-offer and take-offer screen will adjust the XMR amount so that the EUR amount is correct. You cannot use market \
|
||||
|
@ -2645,9 +2656,9 @@ Please be aware there is a maximum of Rs. 200,000 that can be sent per transacti
|
|||
Some banks have different limits for their customers.
|
||||
payment.imps.info.buyer=Please send payment only to the account details provided in Bisq.\n\n\
|
||||
The maximum trade size is Rs. 200,000 per transaction.\n\n\
|
||||
If your trade is over Rs. 200,000 you will have to make multiple transfers. However be aware their is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\n\
|
||||
If your trade is over Rs. 200,000 you will have to make multiple transfers. However, be aware there is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\n\
|
||||
Please note some banks have different limits for their customers.
|
||||
payment.imps.info.seller=If you intend to receive over Rs. 200,000 per trade you should expect the buyer to have to make multiple transfers. However be aware there is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\n\
|
||||
payment.imps.info.seller=If you intend to receive over Rs. 200,000 per trade you should expect the buyer to have to make multiple transfers. However, be aware there is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\n\
|
||||
Please note some banks have different limits for their customers.
|
||||
|
||||
payment.neft.info.account=Please make sure to include your:\n\n\
|
||||
|
@ -2865,7 +2876,7 @@ payment.strike.info.seller=Please make sure your payment is received from the BT
|
|||
The maximum trade size is $1,000 per payment.\n\n\
|
||||
If you trade over the above limits your trade might be cancelled and there could be a penalty.
|
||||
|
||||
payment.transferwiseUsd.info.account=Due US banking regulation, sending and receiving USD payments has more restrictions \
|
||||
payment.transferwiseUsd.info.account=Due to US banking regulation, sending and receiving USD payments has more restrictions \
|
||||
than most other currencies. For this reason USD was not added to Bisq TransferWise payment method.\n\n\
|
||||
The TransferWise-USD payment method allows Bisq users to trade in USD.\n\n\
|
||||
Anyone with a Wise, formally TransferWise account, can add TransferWise-USD as a payment method in Bisq. This will \
|
||||
|
@ -3246,7 +3257,7 @@ validation.iban.checkSumInvalid=IBAN checksum is invalid
|
|||
validation.iban.invalidLength=Number must have a length of 15 to 34 chars.
|
||||
validation.iban.sepaNotSupported=SEPA is not supported in this country
|
||||
validation.interacETransfer.invalidAreaCode=Non-Canadian area code
|
||||
validation.interacETransfer.invalidPhone=Please enter a valid 11 digit phone number (ex: 1-123-456-7890) or an email address
|
||||
validation.interacETransfer.invalidPhone=Please enter a valid 11-digit phone number (ex: 1-123-456-7890) or an email address
|
||||
validation.interacETransfer.invalidQuestion=Must contain only letters, numbers, spaces and/or the symbols ' _ , . ? -
|
||||
validation.interacETransfer.invalidAnswer=Must be one word and contain only letters, numbers, and/or the symbol -
|
||||
validation.inputTooLarge=Input must not be larger than {0}
|
||||
|
@ -3263,7 +3274,7 @@ validation.mustBeDifferent=Your input must be different from the current value
|
|||
validation.cannotBeChanged=Parameter cannot be changed
|
||||
validation.numberFormatException=Number format exception {0}
|
||||
validation.mustNotBeNegative=Input must not be negative
|
||||
validation.phone.missingCountryCode=Need two letter country code to validate phone number
|
||||
validation.phone.missingCountryCode=Need two-letter country code to validate phone number
|
||||
validation.phone.invalidCharacters=Phone number {0} contains invalid characters
|
||||
validation.phone.insufficientDigits=There are not enough digits in {0} to be a valid phone number
|
||||
validation.phone.tooManyDigits=There are too many digits in {0} to be a valid phone number
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue