mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-04-06 21:13:59 -04:00
Merge branch 'haveno-dex:master' into tor
This commit is contained in:
commit
20502e79b0
6
LICENSE
6
LICENSE
@ -1,7 +1,7 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Copyright (C) 2020 Haveno Dex
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
@ -644,7 +644,7 @@ the "copyright" line and a pointer to where the full notice is found.
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
@ -659,4 +659,4 @@ specific requirements.
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
6
Makefile
6
Makefile
@ -70,9 +70,11 @@ monerod1-local:
|
||||
--log-level 0 \
|
||||
--add-exclusive-node 127.0.0.1:48080 \
|
||||
--add-exclusive-node 127.0.0.1:58080 \
|
||||
--max-connections-per-ip 10 \
|
||||
--rpc-access-control-origins http://localhost:8080 \
|
||||
--fixed-difficulty 500 \
|
||||
--disable-rpc-ban \
|
||||
--rpc-max-connections-per-private-ip 100 \
|
||||
|
||||
monerod2-local:
|
||||
./.localnet/monerod \
|
||||
@ -88,9 +90,11 @@ monerod2-local:
|
||||
--confirm-external-bind \
|
||||
--add-exclusive-node 127.0.0.1:28080 \
|
||||
--add-exclusive-node 127.0.0.1:58080 \
|
||||
--max-connections-per-ip 10 \
|
||||
--rpc-access-control-origins http://localhost:8080 \
|
||||
--fixed-difficulty 500 \
|
||||
--disable-rpc-ban \
|
||||
--rpc-max-connections-per-private-ip 100 \
|
||||
|
||||
monerod3-local:
|
||||
./.localnet/monerod \
|
||||
@ -106,9 +110,11 @@ monerod3-local:
|
||||
--confirm-external-bind \
|
||||
--add-exclusive-node 127.0.0.1:28080 \
|
||||
--add-exclusive-node 127.0.0.1:48080 \
|
||||
--max-connections-per-ip 10 \
|
||||
--rpc-access-control-origins http://localhost:8080 \
|
||||
--fixed-difficulty 500 \
|
||||
--disable-rpc-ban \
|
||||
--rpc-max-connections-per-private-ip 100 \
|
||||
|
||||
#--proxy 127.0.0.1:49775 \
|
||||
|
||||
|
@ -23,6 +23,10 @@ Main features:
|
||||
|
||||
See the [FAQ on our website](https://haveno.exchange/faq/) for more information.
|
||||
|
||||
## Haveno Demo
|
||||
|
||||
https://github.com/user-attachments/assets/eb6b3af0-78ce-46a7-bfa1-2aacd8649d47
|
||||
|
||||
## Installing Haveno
|
||||
|
||||
Haveno can be installed on Linux, macOS, and Windows by using a third party installer and network.
|
||||
@ -34,7 +38,7 @@ Haveno can be installed on Linux, macOS, and Windows by using a third party inst
|
||||
|
||||
A test network is also available for users to make test trades using Monero's stagenet. See the [instructions](https://github.com/haveno-dex/haveno/blob/master/docs/installing.md) to build Haveno and connect to the test network.
|
||||
|
||||
Alternatively, you can [create your own mainnet network](create-mainnet.md).
|
||||
Alternatively, you can [create your own mainnet network](https://github.com/haveno-dex/haveno/blob/master/docs/create-mainnet.md).
|
||||
|
||||
Note that Haveno is being actively developed. If you find issues or bugs, please let us know.
|
||||
|
||||
@ -63,7 +67,7 @@ See the [developer guide](docs/developer-guide.md) to get started developing for
|
||||
|
||||
See [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md) for our styling guides.
|
||||
|
||||
If you are not able to contribute code and want to contribute development resources, [donations](#support) fund development bounties.
|
||||
If you are not able to contribute code and want to contribute development resources, [donations](#support-and-sponsorships) fund development bounties.
|
||||
|
||||
## Bounties
|
||||
|
||||
|
20
build.gradle
20
build.gradle
@ -71,7 +71,7 @@ configure(subprojects) {
|
||||
loggingVersion = '1.2'
|
||||
lombokVersion = '1.18.30'
|
||||
mockitoVersion = '5.10.0'
|
||||
netlayerVersion = '700ec94f0f' // Tor browser version 14.0.3 and tor binary version: 0.4.8.13
|
||||
netlayerVersion = 'd9c60be46d' // Tor browser version 14.0.7 and tor binary version: 0.4.8.14
|
||||
protobufVersion = '3.19.1'
|
||||
protocVersion = protobufVersion
|
||||
pushyVersion = '0.13.2'
|
||||
@ -457,14 +457,14 @@ configure(project(':core')) {
|
||||
doLast {
|
||||
// get monero binaries download url
|
||||
Map moneroBinaries = [
|
||||
'linux-x86_64' : 'https://github.com/haveno-dex/monero/releases/download/release5/monero-bins-haveno-linux-x86_64.tar.gz',
|
||||
'linux-x86_64-sha256' : '92003b6d9104e8fe3c4dff292b782ed9b82b157aaff95200fda35e5c3dcb733a',
|
||||
'linux-aarch64' : 'https://github.com/haveno-dex/monero/releases/download/release5/monero-bins-haveno-linux-aarch64.tar.gz',
|
||||
'linux-aarch64-sha256' : '18b069c6c474ce18efea261c875a4d54022520e888712b2a56524d9c92f133b1',
|
||||
'mac' : 'https://github.com/haveno-dex/monero/releases/download/release5/monero-bins-haveno-mac.tar.gz',
|
||||
'mac-sha256' : 'd308352191cd5a9e5e3932ad15869e033e22e209de459f4fd6460b111377dae2',
|
||||
'windows' : 'https://github.com/haveno-dex/monero/releases/download/release5/monero-bins-haveno-windows.zip',
|
||||
'windows-sha256' : '9c9e1994d4738e2a89ca28bef343bcad460ea6c06e0dd40de8278ab3033bd6c7'
|
||||
'linux-x86_64' : 'https://github.com/haveno-dex/monero/releases/download/release6/monero-bins-haveno-linux-x86_64.tar.gz',
|
||||
'linux-x86_64-sha256' : '44470a3cf2dd9be7f3371a8cc89a34cf9a7e88c442739d87ef9a0ec3ccb65208',
|
||||
'linux-aarch64' : 'https://github.com/haveno-dex/monero/releases/download/release6/monero-bins-haveno-linux-aarch64.tar.gz',
|
||||
'linux-aarch64-sha256' : 'c9505524689b0d7a020b8d2fd449c3cb9f8fd546747f9bdcf36cac795179f71c',
|
||||
'mac' : 'https://github.com/haveno-dex/monero/releases/download/release6/monero-bins-haveno-mac.tar.gz',
|
||||
'mac-sha256' : 'dea6eddefa09630cfff7504609bd5d7981316336c64e5458e242440694187df8',
|
||||
'windows' : 'https://github.com/haveno-dex/monero/releases/download/release6/monero-bins-haveno-windows.zip',
|
||||
'windows-sha256' : '284820e28c4770d7065fad7863e66fe0058053ca2372b78345d83c222edc572d'
|
||||
]
|
||||
|
||||
String osKey
|
||||
@ -610,7 +610,7 @@ configure(project(':desktop')) {
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
apply from: 'package/package.gradle'
|
||||
|
||||
version = '1.0.18-SNAPSHOT'
|
||||
version = '1.0.19-SNAPSHOT'
|
||||
|
||||
jar.manifest.attributes(
|
||||
"Implementation-Title": project.name,
|
||||
|
@ -69,7 +69,7 @@ public class ClockWatcher {
|
||||
listeners.forEach(listener -> listener.onMissedSecondTick(missedMs));
|
||||
|
||||
if (missedMs > ClockWatcher.IDLE_TOLERANCE_MS) {
|
||||
log.info("We have been in standby mode for {} sec", missedMs / 1000);
|
||||
log.warn("We have been in standby mode for {} sec", missedMs / 1000);
|
||||
listeners.forEach(listener -> listener.onAwakeFromStandby(missedMs));
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||
public class Version {
|
||||
// The application versions
|
||||
// We use semantic versioning with major, minor and patch
|
||||
public static final String VERSION = "1.0.18";
|
||||
public static final String VERSION = "1.0.19";
|
||||
|
||||
/**
|
||||
* Holds a list of the tagged resource files for optimizing the getData requests.
|
||||
@ -72,6 +72,25 @@ public class Version {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int compare(String version1, String version2) {
|
||||
if (version1.equals(version2))
|
||||
return 0;
|
||||
else if (getMajorVersion(version1) > getMajorVersion(version2))
|
||||
return 1;
|
||||
else if (getMajorVersion(version1) < getMajorVersion(version2))
|
||||
return -1;
|
||||
else if (getMinorVersion(version1) > getMinorVersion(version2))
|
||||
return 1;
|
||||
else if (getMinorVersion(version1) < getMinorVersion(version2))
|
||||
return -1;
|
||||
else if (getPatchVersion(version1) > getPatchVersion(version2))
|
||||
return 1;
|
||||
else if (getPatchVersion(version1) < getPatchVersion(version2))
|
||||
return -1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int getSubVersion(String version, int index) {
|
||||
final String[] split = version.split("\\.");
|
||||
checkArgument(split.length == 3, "Version number must be in semantic version format (contain 2 '.'). version=" + version);
|
||||
@ -91,8 +110,9 @@ public class Version {
|
||||
// For the switch to version 2, offers created with the old version will become invalid and have to be canceled.
|
||||
// For the switch to version 3, offers created with the old version can be migrated to version 3 just by opening
|
||||
// the Haveno app.
|
||||
// VERSION = 0.0.1 -> TRADE_PROTOCOL_VERSION = 1
|
||||
public static final int TRADE_PROTOCOL_VERSION = 1;
|
||||
// Version = 0.0.1 -> TRADE_PROTOCOL_VERSION = 1
|
||||
// Version = 1.0.19 -> TRADE_PROTOCOL_VERSION = 2
|
||||
public static final int TRADE_PROTOCOL_VERSION = 2;
|
||||
private static String p2pMessageVersion;
|
||||
|
||||
public static String getP2PMessageVersion() {
|
||||
|
@ -159,7 +159,7 @@ public class CoreOffersService {
|
||||
}
|
||||
|
||||
OpenOffer getMyOffer(String id) {
|
||||
return openOfferManager.getOpenOfferById(id)
|
||||
return openOfferManager.getOpenOffer(id)
|
||||
.filter(open -> open.getOffer().isMyOffer(keyRing))
|
||||
.orElseThrow(() ->
|
||||
new IllegalStateException(format("openoffer with id '%s' not found", id)));
|
||||
@ -265,6 +265,7 @@ public class CoreOffersService {
|
||||
if (!seenKeyImages.add(keyImage)) {
|
||||
for (Offer offer2 : offers) {
|
||||
if (offer == offer2) continue;
|
||||
if (offer2.getOfferPayload().getReserveTxKeyImages() == null) continue;
|
||||
if (offer2.getOfferPayload().getReserveTxKeyImages().contains(keyImage)) {
|
||||
log.warn("Key image {} belongs to multiple offers, seen in offer {} and {}", keyImage, offer.getId(), offer2.getId());
|
||||
duplicateFundedOffers.add(offer2);
|
||||
|
@ -47,7 +47,6 @@ import haveno.core.support.messages.ChatMessage;
|
||||
import haveno.core.support.traderchat.TradeChatSession;
|
||||
import haveno.core.support.traderchat.TraderChatManager;
|
||||
import haveno.core.trade.ClosedTradableManager;
|
||||
import haveno.core.trade.Tradable;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.TradeManager;
|
||||
import haveno.core.trade.TradeUtil;
|
||||
@ -223,8 +222,7 @@ class CoreTradesService {
|
||||
}
|
||||
|
||||
private Optional<Trade> getClosedTrade(String tradeId) {
|
||||
Optional<Tradable> tradable = closedTradableManager.getTradeById(tradeId);
|
||||
return tradable.filter((t) -> t instanceof Trade).map(value -> (Trade) value);
|
||||
return closedTradableManager.getTradeById(tradeId);
|
||||
}
|
||||
|
||||
List<Trade> getTrades() {
|
||||
|
@ -24,6 +24,7 @@ import haveno.common.UserThread;
|
||||
import haveno.common.app.DevEnv;
|
||||
import haveno.common.config.BaseCurrencyNetwork;
|
||||
import haveno.common.config.Config;
|
||||
import haveno.core.locale.Res;
|
||||
import haveno.core.trade.HavenoUtils;
|
||||
import haveno.core.user.Preferences;
|
||||
import haveno.core.xmr.model.EncryptedConnectionList;
|
||||
@ -43,7 +44,6 @@ import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.LongProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
@ -51,7 +51,6 @@ import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||
import javafx.beans.property.ReadOnlyIntegerProperty;
|
||||
import javafx.beans.property.ReadOnlyLongProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleLongProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
@ -91,7 +90,7 @@ public final class XmrConnectionService {
|
||||
private final LongProperty chainHeight = new SimpleLongProperty(0);
|
||||
private final DownloadListener downloadListener = new DownloadListener();
|
||||
@Getter
|
||||
private final BooleanProperty connectionServiceFallbackHandlerActive = new SimpleBooleanProperty();
|
||||
private final SimpleStringProperty connectionServiceFallbackHandler = new SimpleStringProperty();
|
||||
@Getter
|
||||
private final StringProperty connectionServiceErrorMsg = new SimpleStringProperty();
|
||||
private final LongProperty numUpdates = new SimpleLongProperty(0);
|
||||
@ -261,6 +260,7 @@ public final class XmrConnectionService {
|
||||
|
||||
private MoneroRpcConnection getBestConnection(Collection<MoneroRpcConnection> ignoredConnections) {
|
||||
accountService.checkAccountOpen();
|
||||
if (!fallbackApplied && lastUsedLocalSyncingNode() && !xmrLocalNode.isDetected()) return null; // user needs to explicitly allow fallback after syncing local node
|
||||
Set<MoneroRpcConnection> ignoredConnectionsSet = new HashSet<>(ignoredConnections);
|
||||
addLocalNodeIfIgnored(ignoredConnectionsSet);
|
||||
MoneroRpcConnection bestConnection = connectionManager.getBestAvailableConnection(ignoredConnectionsSet.toArray(new MoneroRpcConnection[0])); // checks connections
|
||||
@ -604,9 +604,6 @@ public final class XmrConnectionService {
|
||||
if (coreContext.isApiUser()) connectionManager.setAutoSwitch(connectionList.getAutoSwitch());
|
||||
else connectionManager.setAutoSwitch(true); // auto switch is always enabled on desktop ui
|
||||
|
||||
// start local node if applicable
|
||||
maybeStartLocalNode();
|
||||
|
||||
// update connection
|
||||
if (connectionManager.getConnection() == null || connectionManager.getAutoSwitch()) {
|
||||
MoneroRpcConnection bestConnection = getBestConnection();
|
||||
@ -619,9 +616,6 @@ public final class XmrConnectionService {
|
||||
MoneroRpcConnection connection = new MoneroRpcConnection(config.xmrNode, config.xmrNodeUsername, config.xmrNodePassword).setPriority(1);
|
||||
if (isProxyApplied(connection)) connection.setProxyUri(getProxyUri());
|
||||
connectionManager.setConnection(connection);
|
||||
|
||||
// start local node if applicable
|
||||
maybeStartLocalNode();
|
||||
}
|
||||
|
||||
// register connection listener
|
||||
@ -634,20 +628,8 @@ public final class XmrConnectionService {
|
||||
onConnectionChanged(connectionManager.getConnection());
|
||||
}
|
||||
|
||||
private void maybeStartLocalNode() {
|
||||
|
||||
// skip if seed node
|
||||
if (HavenoUtils.isSeedNode()) return;
|
||||
|
||||
// start local node if offline and used as last connection
|
||||
if (connectionManager.getConnection() != null && xmrLocalNode.equalsUri(connectionManager.getConnection().getUri()) && !xmrLocalNode.isDetected() && !xmrLocalNode.shouldBeIgnored()) {
|
||||
try {
|
||||
log.info("Starting local node");
|
||||
xmrLocalNode.start();
|
||||
} catch (Exception e) {
|
||||
log.error("Unable to start local monero node, error={}\n", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
private boolean lastUsedLocalSyncingNode() {
|
||||
return connectionManager.getConnection() != null && xmrLocalNode.equalsUri(connectionManager.getConnection().getUri()) && !xmrLocalNode.isDetected() && !xmrLocalNode.shouldBeIgnored();
|
||||
}
|
||||
|
||||
private void onConnectionChanged(MoneroRpcConnection currentConnection) {
|
||||
@ -733,12 +715,17 @@ public final class XmrConnectionService {
|
||||
if (isShutDownStarted) return;
|
||||
|
||||
// invoke fallback handling on startup error
|
||||
boolean canFallback = isFixedConnection() || isCustomConnections();
|
||||
boolean canFallback = isFixedConnection() || isCustomConnections() || lastUsedLocalSyncingNode();
|
||||
if (lastInfo == null && canFallback) {
|
||||
if (!connectionServiceFallbackHandlerActive.get() && (lastFallbackInvocation == null || System.currentTimeMillis() - lastFallbackInvocation > FALLBACK_INVOCATION_PERIOD_MS)) {
|
||||
log.warn("Failed to fetch daemon info from custom connection on startup: " + e.getMessage());
|
||||
if (connectionServiceFallbackHandler.get() == null || connectionServiceFallbackHandler.equals("") && (lastFallbackInvocation == null || System.currentTimeMillis() - lastFallbackInvocation > FALLBACK_INVOCATION_PERIOD_MS)) {
|
||||
lastFallbackInvocation = System.currentTimeMillis();
|
||||
connectionServiceFallbackHandlerActive.set(true);
|
||||
if (lastUsedLocalSyncingNode()) {
|
||||
log.warn("Failed to fetch daemon info from local connection on startup: " + e.getMessage());
|
||||
connectionServiceFallbackHandler.set(Res.get("connectionFallback.localNode"));
|
||||
} else {
|
||||
log.warn("Failed to fetch daemon info from custom connection on startup: " + e.getMessage());
|
||||
connectionServiceFallbackHandler.set(Res.get("connectionFallback.customNode"));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -25,6 +25,8 @@ import haveno.core.trade.HavenoUtils;
|
||||
import haveno.core.user.Preferences;
|
||||
import haveno.core.xmr.XmrNodeSettings;
|
||||
import haveno.core.xmr.nodes.XmrNodes;
|
||||
import haveno.core.xmr.nodes.XmrNodes.XmrNode;
|
||||
import haveno.core.xmr.nodes.XmrNodesSetupPreferences;
|
||||
import haveno.core.xmr.wallet.XmrWalletService;
|
||||
|
||||
import java.io.File;
|
||||
@ -55,6 +57,7 @@ public class XmrLocalNode {
|
||||
private MoneroConnectionManager connectionManager;
|
||||
private final Config config;
|
||||
private final Preferences preferences;
|
||||
private final XmrNodes xmrNodes;
|
||||
private final List<XmrLocalNodeListener> listeners = new ArrayList<>();
|
||||
|
||||
// required arguments
|
||||
@ -69,9 +72,12 @@ public class XmrLocalNode {
|
||||
}
|
||||
|
||||
@Inject
|
||||
public XmrLocalNode(Config config, Preferences preferences) {
|
||||
public XmrLocalNode(Config config,
|
||||
Preferences preferences,
|
||||
XmrNodes xmrNodes) {
|
||||
this.config = config;
|
||||
this.preferences = preferences;
|
||||
this.xmrNodes = xmrNodes;
|
||||
this.daemon = new MoneroDaemonRpc(getUri());
|
||||
|
||||
// initialize connection manager to listen to local connection
|
||||
@ -101,7 +107,19 @@ public class XmrLocalNode {
|
||||
* Returns whether Haveno should ignore a local Monero node even if it is usable.
|
||||
*/
|
||||
public boolean shouldBeIgnored() {
|
||||
return config.ignoreLocalXmrNode || preferences.getMoneroNodesOption() == XmrNodes.MoneroNodesOption.CUSTOM;
|
||||
if (config.ignoreLocalXmrNode) return true;
|
||||
|
||||
// determine if local node is configured
|
||||
boolean hasConfiguredLocalNode = false;
|
||||
for (XmrNode node : xmrNodes.selectPreferredNodes(new XmrNodesSetupPreferences(preferences))) {
|
||||
if (node.getAddress() != null && equalsUri("http://" + node.getAddress() + ":" + node.getPort())) {
|
||||
hasConfiguredLocalNode = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasConfiguredLocalNode) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void addListener(XmrLocalNodeListener listener) {
|
||||
@ -120,7 +138,11 @@ public class XmrLocalNode {
|
||||
}
|
||||
|
||||
public boolean equalsUri(String uri) {
|
||||
return HavenoUtils.isLocalHost(uri) && MoneroUtils.parseUri(uri).getPort() == HavenoUtils.getDefaultMoneroPort();
|
||||
try {
|
||||
return HavenoUtils.isLocalHost(uri) && MoneroUtils.parseUri(uri).getPort() == HavenoUtils.getDefaultMoneroPort();
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -178,6 +178,9 @@ public class DomainInitialisation {
|
||||
closedTradableManager.onAllServicesInitialized();
|
||||
failedTradesManager.onAllServicesInitialized();
|
||||
|
||||
filterManager.setFilterWarningHandler(filterWarningHandler);
|
||||
filterManager.onAllServicesInitialized();
|
||||
|
||||
openOfferManager.onAllServicesInitialized();
|
||||
|
||||
balances.onAllServicesInitialized();
|
||||
@ -199,10 +202,6 @@ public class DomainInitialisation {
|
||||
priceFeedService.setCurrencyCodeOnInit();
|
||||
priceFeedService.startRequestingPrices();
|
||||
|
||||
filterManager.setFilterWarningHandler(filterWarningHandler);
|
||||
filterManager.onAllServicesInitialized();
|
||||
|
||||
|
||||
mobileNotificationService.onAllServicesInitialized();
|
||||
myOfferTakenEvents.onAllServicesInitialized();
|
||||
tradeEvents.onAllServicesInitialized();
|
||||
|
@ -86,7 +86,7 @@ public class HavenoHeadlessApp implements HeadlessApp {
|
||||
havenoSetup.setDisplaySecurityRecommendationHandler(key -> log.info("onDisplaySecurityRecommendationHandler"));
|
||||
havenoSetup.setWrongOSArchitectureHandler(msg -> log.error("onWrongOSArchitectureHandler. msg={}", msg));
|
||||
havenoSetup.setRejectedTxErrorMessageHandler(errorMessage -> log.warn("setRejectedTxErrorMessageHandler. errorMessage={}", errorMessage));
|
||||
havenoSetup.setShowPopupIfInvalidBtcConfigHandler(() -> log.error("onShowPopupIfInvalidBtcConfigHandler"));
|
||||
havenoSetup.setShowPopupIfInvalidXmrConfigHandler(() -> log.error("onShowPopupIfInvalidXmrConfigHandler"));
|
||||
havenoSetup.setRevolutAccountsUpdateHandler(revolutAccountList -> log.info("setRevolutAccountsUpdateHandler: revolutAccountList={}", revolutAccountList));
|
||||
havenoSetup.setOsxKeyLoggerWarningHandler(() -> log.info("setOsxKeyLoggerWarningHandler"));
|
||||
havenoSetup.setQubesOSInfoHandler(() -> log.info("setQubesOSInfoHandler"));
|
||||
|
@ -158,7 +158,7 @@ public class HavenoSetup {
|
||||
rejectedTxErrorMessageHandler;
|
||||
@Setter
|
||||
@Nullable
|
||||
private Consumer<Boolean> displayMoneroConnectionFallbackHandler;
|
||||
private Consumer<String> displayMoneroConnectionFallbackHandler;
|
||||
@Setter
|
||||
@Nullable
|
||||
private Consumer<Boolean> displayTorNetworkSettingsHandler;
|
||||
@ -176,7 +176,7 @@ public class HavenoSetup {
|
||||
private Consumer<PrivateNotificationPayload> displayPrivateNotificationHandler;
|
||||
@Setter
|
||||
@Nullable
|
||||
private Runnable showPopupIfInvalidBtcConfigHandler;
|
||||
private Runnable showPopupIfInvalidXmrConfigHandler;
|
||||
@Setter
|
||||
@Nullable
|
||||
private Consumer<List<RevolutAccount>> revolutAccountsUpdateHandler;
|
||||
@ -430,7 +430,7 @@ public class HavenoSetup {
|
||||
getXmrWalletSyncProgress().addListener((observable, oldValue, newValue) -> resetStartupTimeout());
|
||||
|
||||
// listen for fallback handling
|
||||
getConnectionServiceFallbackHandlerActive().addListener((observable, oldValue, newValue) -> {
|
||||
getConnectionServiceFallbackHandler().addListener((observable, oldValue, newValue) -> {
|
||||
if (displayMoneroConnectionFallbackHandler == null) return;
|
||||
displayMoneroConnectionFallbackHandler.accept(newValue);
|
||||
});
|
||||
@ -461,7 +461,7 @@ public class HavenoSetup {
|
||||
havenoSetupListeners.forEach(HavenoSetupListener::onInitWallet);
|
||||
walletAppSetup.init(chainFileLockedExceptionHandler,
|
||||
showFirstPopupIfResyncSPVRequestedHandler,
|
||||
showPopupIfInvalidBtcConfigHandler,
|
||||
showPopupIfInvalidXmrConfigHandler,
|
||||
() -> {},
|
||||
() -> {});
|
||||
}
|
||||
@ -734,8 +734,8 @@ public class HavenoSetup {
|
||||
return xmrConnectionService.getConnectionServiceErrorMsg();
|
||||
}
|
||||
|
||||
public BooleanProperty getConnectionServiceFallbackHandlerActive() {
|
||||
return xmrConnectionService.getConnectionServiceFallbackHandlerActive();
|
||||
public StringProperty getConnectionServiceFallbackHandler() {
|
||||
return xmrConnectionService.getConnectionServiceFallbackHandler();
|
||||
}
|
||||
|
||||
public StringProperty getTopErrorMsg() {
|
||||
|
@ -117,7 +117,7 @@ public class WalletAppSetup {
|
||||
|
||||
void init(@Nullable Consumer<String> chainFileLockedExceptionHandler,
|
||||
@Nullable Runnable showFirstPopupIfResyncSPVRequestedHandler,
|
||||
@Nullable Runnable showPopupIfInvalidBtcConfigHandler,
|
||||
@Nullable Runnable showPopupIfInvalidXmrConfigHandler,
|
||||
Runnable downloadCompleteHandler,
|
||||
Runnable walletInitializedHandler) {
|
||||
log.info("Initialize WalletAppSetup with monero-java v{}", MoneroUtils.getVersion());
|
||||
@ -199,8 +199,8 @@ public class WalletAppSetup {
|
||||
walletInitializedHandler.run();
|
||||
},
|
||||
exception -> {
|
||||
if (exception instanceof InvalidHostException && showPopupIfInvalidBtcConfigHandler != null) {
|
||||
showPopupIfInvalidBtcConfigHandler.run();
|
||||
if (exception instanceof InvalidHostException && showPopupIfInvalidXmrConfigHandler != null) {
|
||||
showPopupIfInvalidXmrConfigHandler.run();
|
||||
} else {
|
||||
walletServiceException.set(exception);
|
||||
}
|
||||
|
@ -406,6 +406,10 @@ public class FilterManager {
|
||||
.anyMatch(e -> e.equals(address));
|
||||
}
|
||||
|
||||
public String getDisableTradeBelowVersion() {
|
||||
return getFilter() == null || getFilter().getDisableTradeBelowVersion() == null || getFilter().getDisableTradeBelowVersion().isEmpty() ? null : getFilter().getDisableTradeBelowVersion();
|
||||
}
|
||||
|
||||
public boolean requireUpdateToNewVersionForTrading() {
|
||||
if (getFilter() == null) {
|
||||
return false;
|
||||
|
@ -97,6 +97,7 @@ import haveno.network.p2p.peers.PeerManager;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@ -134,7 +135,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
private static final long REPUBLISH_AGAIN_AT_STARTUP_DELAY_SEC = 30;
|
||||
private static final long REPUBLISH_INTERVAL_MS = TimeUnit.MINUTES.toMillis(30);
|
||||
private static final long REFRESH_INTERVAL_MS = OfferPayload.TTL / 2;
|
||||
private static final int NUM_ATTEMPTS_THRESHOLD = 5; // process pending offer only on republish cycle after this many attempts
|
||||
private static final int NUM_ATTEMPTS_THRESHOLD = 5; // process offer only on republish cycle after this many attempts
|
||||
|
||||
private final CoreContext coreContext;
|
||||
private final KeyRing keyRing;
|
||||
@ -236,7 +237,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
public void onAdded(Offer offer) {
|
||||
|
||||
// cancel offer if reserved funds spent
|
||||
Optional<OpenOffer> openOfferOptional = getOpenOfferById(offer.getId());
|
||||
Optional<OpenOffer> openOfferOptional = getOpenOffer(offer.getId());
|
||||
if (openOfferOptional.isPresent() && openOfferOptional.get().getState() != OpenOffer.State.RESERVED && offer.isReservedFundsSpent()) {
|
||||
log.warn("Canceling open offer because reserved funds have been spent, offerId={}, state={}", offer.getId(), openOfferOptional.get().getState());
|
||||
cancelOpenOffer(openOfferOptional.get(), null, null);
|
||||
@ -474,19 +475,19 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
// .forEach(openOffer -> OfferUtil.getInvalidMakerFeeTxErrorMessage(openOffer.getOffer(), btcWalletService)
|
||||
// .ifPresent(errorMsg -> invalidOffers.add(new Tuple2<>(openOffer, errorMsg))));
|
||||
|
||||
// process pending offers
|
||||
processPendingOffers(false, (transaction) -> {}, (errorMessage) -> {
|
||||
log.warn("Error processing pending offers on bootstrap: " + errorMessage);
|
||||
// processs offers
|
||||
processOffers(false, (transaction) -> {}, (errorMessage) -> {
|
||||
log.warn("Error processing offers on bootstrap: " + errorMessage);
|
||||
});
|
||||
|
||||
// register to process pending offers on new block
|
||||
// register to process offers on new block
|
||||
xmrWalletService.addWalletListener(new MoneroWalletListener() {
|
||||
@Override
|
||||
public void onNewBlock(long height) {
|
||||
|
||||
// process each pending offer on new block a few times, then rely on period republish
|
||||
processPendingOffers(true, (transaction) -> {}, (errorMessage) -> {
|
||||
log.warn("Error processing pending offers on new block {}: {}", height, errorMessage);
|
||||
// process each offer on new block a few times, then rely on period republish
|
||||
processOffers(true, (transaction) -> {}, (errorMessage) -> {
|
||||
log.warn("Error processing offers on new block {}: {}", height, errorMessage);
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -554,13 +555,13 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
synchronized (processOffersLock) {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
addOpenOffer(openOffer);
|
||||
processPendingOffer(getOpenOffers(), openOffer, (transaction) -> {
|
||||
processOffer(getOpenOffers(), openOffer, (transaction) -> {
|
||||
requestPersistence();
|
||||
latch.countDown();
|
||||
resultHandler.handleResult(transaction);
|
||||
}, (errorMessage) -> {
|
||||
if (!openOffer.isCanceled()) {
|
||||
log.warn("Error processing pending offer {}: {}", openOffer.getId(), errorMessage);
|
||||
log.warn("Error processing offer {}: {}", openOffer.getId(), errorMessage);
|
||||
doCancelOffer(openOffer, resetAddressEntriesOnError);
|
||||
}
|
||||
latch.countDown();
|
||||
@ -573,12 +574,13 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
|
||||
// Remove from offerbook
|
||||
public void removeOffer(Offer offer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||
Optional<OpenOffer> openOfferOptional = getOpenOfferById(offer.getId());
|
||||
Optional<OpenOffer> openOfferOptional = getOpenOffer(offer.getId());
|
||||
if (openOfferOptional.isPresent()) {
|
||||
cancelOpenOffer(openOfferOptional.get(), resultHandler, errorMessageHandler);
|
||||
} else {
|
||||
log.warn("Offer was not found in our list of open offers. We still try to remove it from the offerbook.");
|
||||
errorMessageHandler.handleErrorMessage("Offer was not found in our list of open offers. " + "We still try to remove it from the offerbook.");
|
||||
String errorMsg = "Offer was not found in our list of open offers. We still try to remove it from the offerbook.";
|
||||
log.warn(errorMsg);
|
||||
errorMessageHandler.handleErrorMessage(errorMsg);
|
||||
offerBookService.removeOffer(offer.getOfferPayload(), () -> offer.setState(Offer.State.REMOVED), null);
|
||||
}
|
||||
}
|
||||
@ -686,7 +688,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
OpenOffer.State originalState,
|
||||
ResultHandler resultHandler,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
Optional<OpenOffer> openOfferOptional = getOpenOfferById(editedOffer.getId());
|
||||
Optional<OpenOffer> openOfferOptional = getOpenOffer(editedOffer.getId());
|
||||
|
||||
if (openOfferOptional.isPresent()) {
|
||||
OpenOffer openOffer = openOfferOptional.get();
|
||||
@ -705,12 +707,21 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
|
||||
addOpenOffer(editedOpenOffer);
|
||||
|
||||
if (editedOpenOffer.isAvailable())
|
||||
maybeRepublishOffer(editedOpenOffer);
|
||||
// reset arbitrator signature if invalid
|
||||
Arbitrator arbitrator = user.getAcceptedArbitratorByAddress(editedOpenOffer.getOffer().getOfferPayload().getArbitratorSigner());
|
||||
if (arbitrator == null || !HavenoUtils.isArbitratorSignatureValid(editedOpenOffer.getOffer().getOfferPayload(), arbitrator)) {
|
||||
editedOpenOffer.getOffer().getOfferPayload().setArbitratorSignature(null);
|
||||
editedOpenOffer.getOffer().getOfferPayload().setArbitratorSigner(null);
|
||||
}
|
||||
|
||||
offersToBeEdited.remove(openOffer.getId());
|
||||
requestPersistence();
|
||||
resultHandler.handleResult();
|
||||
// process offer which might sign and publish
|
||||
processOffer(getOpenOffers(), editedOpenOffer, (transaction) -> {
|
||||
offersToBeEdited.remove(openOffer.getId());
|
||||
requestPersistence();
|
||||
resultHandler.handleResult();
|
||||
}, (errorMsg) -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMsg);
|
||||
});
|
||||
} else {
|
||||
errorMessageHandler.handleErrorMessage("There is no offer with this id existing to be published.");
|
||||
}
|
||||
@ -727,6 +738,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
} else {
|
||||
resultHandler.handleResult();
|
||||
}
|
||||
requestPersistence();
|
||||
} else {
|
||||
errorMessageHandler.handleErrorMessage("Editing of offer can't be canceled as it is not edited.");
|
||||
}
|
||||
@ -736,7 +748,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
doCancelOffer(openOffer, true);
|
||||
}
|
||||
|
||||
// remove open offer which thaws its key images
|
||||
// cancel open offer which thaws its key images
|
||||
private void doCancelOffer(@NotNull OpenOffer openOffer, boolean resetAddressEntries) {
|
||||
Offer offer = openOffer.getOffer();
|
||||
offer.setState(Offer.State.REMOVED);
|
||||
@ -750,7 +762,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
|
||||
// close open offer after key images spent
|
||||
public void closeOpenOffer(Offer offer) {
|
||||
getOpenOfferById(offer.getId()).ifPresent(openOffer -> {
|
||||
getOpenOffer(offer.getId()).ifPresent(openOffer -> {
|
||||
removeOpenOffer(openOffer);
|
||||
openOffer.setState(OpenOffer.State.CLOSED);
|
||||
xmrWalletService.resetAddressEntriesForOpenOffer(offer.getId());
|
||||
@ -813,14 +825,14 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
return openOffers.getObservableList();
|
||||
}
|
||||
|
||||
public Optional<OpenOffer> getOpenOfferById(String offerId) {
|
||||
public Optional<OpenOffer> getOpenOffer(String offerId) {
|
||||
synchronized (openOffers) {
|
||||
return openOffers.stream().filter(e -> e.getId().equals(offerId)).findFirst();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasOpenOffer(String offerId) {
|
||||
return getOpenOfferById(offerId).isPresent();
|
||||
return getOpenOffer(offerId).isPresent();
|
||||
}
|
||||
|
||||
public Optional<SignedOffer> getSignedOfferById(String offerId) {
|
||||
@ -881,30 +893,21 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Place offer helpers
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
private void processPendingOffers(boolean skipOffersWithTooManyAttempts,
|
||||
private void processOffers(boolean skipOffersWithTooManyAttempts,
|
||||
TransactionResultHandler resultHandler, // TODO (woodser): transaction not needed with result handler
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
ThreadUtils.execute(() -> {
|
||||
List<String> errorMessages = new ArrayList<String>();
|
||||
synchronized (processOffersLock) {
|
||||
List<OpenOffer> openOffers = getOpenOffers();
|
||||
for (OpenOffer pendingOffer : openOffers) {
|
||||
if (pendingOffer.getState() != OpenOffer.State.PENDING) continue;
|
||||
if (skipOffersWithTooManyAttempts && pendingOffer.getNumProcessingAttempts() > NUM_ATTEMPTS_THRESHOLD) continue; // skip offers with too many attempts
|
||||
removeOffersWithDuplicateKeyImages(openOffers);
|
||||
for (OpenOffer offer : openOffers) {
|
||||
if (skipOffersWithTooManyAttempts && offer.getNumProcessingAttempts() > NUM_ATTEMPTS_THRESHOLD) continue; // skip offers with too many attempts
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
processPendingOffer(openOffers, pendingOffer, (transaction) -> {
|
||||
processOffer(openOffers, offer, (transaction) -> {
|
||||
latch.countDown();
|
||||
}, errorMessage -> {
|
||||
if (!pendingOffer.isCanceled()) {
|
||||
String warnMessage = "Error processing pending offer, offerId=" + pendingOffer.getId() + ", attempt=" + pendingOffer.getNumProcessingAttempts() + ": " + errorMessage;
|
||||
errorMessages.add(warnMessage);
|
||||
|
||||
// cancel offer if invalid
|
||||
if (pendingOffer.getOffer().getState() == Offer.State.INVALID) {
|
||||
log.warn("Canceling offer because it's invalid: {}", pendingOffer.getId());
|
||||
doCancelOffer(pendingOffer);
|
||||
}
|
||||
}
|
||||
errorMessages.add(errorMessage);
|
||||
latch.countDown();
|
||||
});
|
||||
HavenoUtils.awaitLatch(latch);
|
||||
@ -919,7 +922,29 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
}, THREAD_ID);
|
||||
}
|
||||
|
||||
private void processPendingOffer(List<OpenOffer> openOffers, OpenOffer openOffer, TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||
private void removeOffersWithDuplicateKeyImages(List<OpenOffer> openOffers) {
|
||||
|
||||
// collect offers with duplicate key images
|
||||
Set<String> keyImages = new HashSet<>();
|
||||
Set<OpenOffer> offersToRemove = new HashSet<>();
|
||||
for (OpenOffer openOffer : openOffers) {
|
||||
if (openOffer.getOffer().getOfferPayload().getReserveTxKeyImages() == null) continue;
|
||||
if (Collections.disjoint(keyImages, openOffer.getOffer().getOfferPayload().getReserveTxKeyImages())) {
|
||||
keyImages.addAll(openOffer.getOffer().getOfferPayload().getReserveTxKeyImages());
|
||||
} else {
|
||||
offersToRemove.add(openOffer);
|
||||
}
|
||||
}
|
||||
|
||||
// remove offers with duplicate key images
|
||||
for (OpenOffer offerToRemove : offersToRemove) {
|
||||
log.warn("Removing open offer which has duplicate key images with other open offers: {}", offerToRemove.getId());
|
||||
doCancelOffer(offerToRemove);
|
||||
openOffers.remove(offerToRemove);
|
||||
}
|
||||
}
|
||||
|
||||
private void processOffer(List<OpenOffer> openOffers, OpenOffer openOffer, TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||
|
||||
// skip if already processing
|
||||
if (openOffer.isProcessing()) {
|
||||
@ -929,23 +954,33 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
|
||||
// process offer
|
||||
openOffer.setProcessing(true);
|
||||
doProcessPendingOffer(openOffers, openOffer, (transaction) -> {
|
||||
doProcessOffer(openOffers, openOffer, (transaction) -> {
|
||||
openOffer.setProcessing(false);
|
||||
resultHandler.handleResult(transaction);
|
||||
}, (errorMsg) -> {
|
||||
openOffer.setProcessing(false);
|
||||
openOffer.setNumProcessingAttempts(openOffer.getNumProcessingAttempts() + 1);
|
||||
openOffer.getOffer().setErrorMessage(errorMsg);
|
||||
if (!openOffer.isCanceled()) {
|
||||
errorMsg = "Error processing offer, offerId=" + openOffer.getId() + ", attempt=" + openOffer.getNumProcessingAttempts() + ": " + errorMsg;
|
||||
openOffer.getOffer().setErrorMessage(errorMsg);
|
||||
|
||||
// cancel offer if invalid
|
||||
if (openOffer.getOffer().getState() == Offer.State.INVALID) {
|
||||
log.warn("Canceling offer because it's invalid: {}", openOffer.getId());
|
||||
doCancelOffer(openOffer);
|
||||
}
|
||||
}
|
||||
errorMessageHandler.handleErrorMessage(errorMsg);
|
||||
});
|
||||
}
|
||||
|
||||
private void doProcessPendingOffer(List<OpenOffer> openOffers, OpenOffer openOffer, TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||
private void doProcessOffer(List<OpenOffer> openOffers, OpenOffer openOffer, TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
|
||||
// done processing if wallet not initialized
|
||||
if (xmrWalletService.getWallet() == null) {
|
||||
// done processing if canceled or wallet not initialized
|
||||
if (openOffer.isCanceled() || xmrWalletService.getWallet() == null) {
|
||||
resultHandler.handleResult(null);
|
||||
return;
|
||||
}
|
||||
@ -958,6 +993,33 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
return;
|
||||
}
|
||||
|
||||
// validate non-pending state
|
||||
if (!openOffer.isPending()) {
|
||||
boolean isValid = true;
|
||||
Arbitrator arbitrator = user.getAcceptedArbitratorByAddress(openOffer.getOffer().getOfferPayload().getArbitratorSigner());
|
||||
if (openOffer.getOffer().getOfferPayload().getArbitratorSigner() == null) {
|
||||
isValid = false;
|
||||
} else if (arbitrator == null) {
|
||||
log.warn("Offer {} signed by unavailable arbitrator, reposting", openOffer.getId());
|
||||
isValid = false;
|
||||
} else if (!HavenoUtils.isArbitratorSignatureValid(openOffer.getOffer().getOfferPayload(), arbitrator)) {
|
||||
log.warn("Offer {} has invalid arbitrator signature, reposting", openOffer.getId());
|
||||
isValid = false;
|
||||
}
|
||||
if ((openOffer.getOffer().getOfferPayload().getReserveTxKeyImages() != null || openOffer.getOffer().getOfferPayload().getReserveTxKeyImages().isEmpty()) && (openOffer.getReserveTxHash() == null || openOffer.getReserveTxHash().isEmpty())) {
|
||||
log.warn("Offer {} is missing reserve tx hash but has reserved key images, reposting", openOffer.getId());
|
||||
isValid = false;
|
||||
}
|
||||
if (isValid) {
|
||||
resultHandler.handleResult(null);
|
||||
return;
|
||||
} else {
|
||||
openOffer.getOffer().getOfferPayload().setArbitratorSignature(null);
|
||||
openOffer.getOffer().getOfferPayload().setArbitratorSigner(null);
|
||||
if (openOffer.isAvailable()) openOffer.setState(OpenOffer.State.PENDING);
|
||||
}
|
||||
}
|
||||
|
||||
// cancel offer if scheduled txs unavailable
|
||||
if (openOffer.getScheduledTxHashes() != null) {
|
||||
boolean scheduledTxsAvailable = true;
|
||||
@ -975,6 +1037,12 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
}
|
||||
}
|
||||
|
||||
// sign and post offer if already funded
|
||||
if (openOffer.getReserveTxHash() != null) {
|
||||
signAndPostOffer(openOffer, false, resultHandler, errorMessageHandler);
|
||||
return;
|
||||
}
|
||||
|
||||
// get amount needed to reserve offer
|
||||
BigInteger amountNeeded = openOffer.getOffer().getAmountNeeded();
|
||||
|
||||
@ -987,43 +1055,30 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
setSplitOutputTx(openOffer, splitOutputTx);
|
||||
}
|
||||
|
||||
// if not found, create tx to split exact output
|
||||
if (splitOutputTx == null) {
|
||||
if (openOffer.getSplitOutputTxHash() != null) {
|
||||
log.warn("Split output tx unexpectedly unavailable for offer, offerId={}, split output tx={}", openOffer.getId(), openOffer.getSplitOutputTxHash());
|
||||
setSplitOutputTx(openOffer, null);
|
||||
}
|
||||
try {
|
||||
splitOrSchedule(openOffers, openOffer, amountNeeded);
|
||||
} catch (Exception e) {
|
||||
log.warn("Unable to split or schedule funds for offer {}: {}", openOffer.getId(), e.getMessage());
|
||||
openOffer.getOffer().setState(Offer.State.INVALID);
|
||||
errorMessageHandler.handleErrorMessage(e.getMessage());
|
||||
return;
|
||||
}
|
||||
} else if (!splitOutputTx.isLocked()) {
|
||||
|
||||
// otherwise sign and post offer if split output available
|
||||
signAndPostOffer(openOffer, true, resultHandler, errorMessageHandler);
|
||||
// if wallet has exact available balance, try to sign and post directly
|
||||
if (xmrWalletService.getAvailableBalance().equals(amountNeeded)) {
|
||||
signAndPostOffer(openOffer, true, resultHandler, (errorMessage) -> {
|
||||
splitOrSchedule(splitOutputTx, openOffers, openOffer, amountNeeded, resultHandler, errorMessageHandler);
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
splitOrSchedule(splitOutputTx, openOffers, openOffer, amountNeeded, resultHandler, errorMessageHandler);
|
||||
}
|
||||
} else {
|
||||
|
||||
// sign and post offer if enough funds
|
||||
boolean hasFundsReserved = openOffer.getReserveTxHash() != null;
|
||||
boolean hasSufficientBalance = xmrWalletService.getAvailableBalance().compareTo(amountNeeded) >= 0;
|
||||
if (hasFundsReserved || hasSufficientBalance) {
|
||||
if (hasSufficientBalance) {
|
||||
signAndPostOffer(openOffer, true, resultHandler, errorMessageHandler);
|
||||
return;
|
||||
} else if (openOffer.getScheduledTxHashes() == null) {
|
||||
scheduleWithEarliestTxs(openOffers, openOffer);
|
||||
resultHandler.handleResult(null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// handle result
|
||||
resultHandler.handleResult(null);
|
||||
} catch (Exception e) {
|
||||
if (!openOffer.isCanceled()) log.error("Error processing pending offer: {}\n", e.getMessage(), e);
|
||||
if (!openOffer.isCanceled()) log.error("Error processing offer: {}\n", e.getMessage(), e);
|
||||
errorMessageHandler.handleErrorMessage(e.getMessage());
|
||||
}
|
||||
}).start();
|
||||
@ -1087,13 +1142,13 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
if (output.isSpent() || output.isFrozen()) removeTxs.add(tx);
|
||||
}
|
||||
}
|
||||
if (!hasExactAmount(tx, reserveAmount, preferredSubaddressIndex)) removeTxs.add(tx);
|
||||
if (!hasExactOutput(tx, reserveAmount, preferredSubaddressIndex)) removeTxs.add(tx);
|
||||
}
|
||||
splitOutputTxs.removeAll(removeTxs);
|
||||
return splitOutputTxs;
|
||||
}
|
||||
|
||||
private boolean hasExactAmount(MoneroTxWallet tx, BigInteger amount, Integer preferredSubaddressIndex) {
|
||||
private boolean hasExactOutput(MoneroTxWallet tx, BigInteger amount, Integer preferredSubaddressIndex) {
|
||||
boolean hasExactOutput = (tx.getOutputsWallet(new MoneroOutputQuery()
|
||||
.setAccountIndex(0)
|
||||
.setSubaddressIndex(preferredSubaddressIndex)
|
||||
@ -1115,7 +1170,35 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
return earliestUnscheduledTx;
|
||||
}
|
||||
|
||||
private void splitOrSchedule(List<OpenOffer> openOffers, OpenOffer openOffer, BigInteger offerReserveAmount) {
|
||||
// if split tx not found and cannot reserve exact amount directly, create tx to split or reserve exact output
|
||||
private void splitOrSchedule(MoneroTxWallet splitOutputTx, List<OpenOffer> openOffers, OpenOffer openOffer, BigInteger amountNeeded, TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||
if (splitOutputTx == null) {
|
||||
if (openOffer.getSplitOutputTxHash() != null) {
|
||||
log.warn("Split output tx unexpectedly unavailable for offer, offerId={}, split output tx={}", openOffer.getId(), openOffer.getSplitOutputTxHash());
|
||||
setSplitOutputTx(openOffer, null);
|
||||
}
|
||||
try {
|
||||
splitOrScheduleAux(openOffers, openOffer, amountNeeded);
|
||||
resultHandler.handleResult(null);
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
log.warn("Unable to split or schedule funds for offer {}: {}", openOffer.getId(), e.getMessage());
|
||||
openOffer.getOffer().setState(Offer.State.INVALID);
|
||||
errorMessageHandler.handleErrorMessage(e.getMessage());
|
||||
return;
|
||||
}
|
||||
} else if (!splitOutputTx.isLocked()) {
|
||||
|
||||
// otherwise sign and post offer if split output available
|
||||
signAndPostOffer(openOffer, true, resultHandler, errorMessageHandler);
|
||||
return;
|
||||
} else {
|
||||
resultHandler.handleResult(null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void splitOrScheduleAux(List<OpenOffer> openOffers, OpenOffer openOffer, BigInteger offerReserveAmount) {
|
||||
|
||||
// handle sufficient available balance to split output
|
||||
boolean sufficientAvailableBalance = xmrWalletService.getAvailableBalance().compareTo(offerReserveAmount) >= 0;
|
||||
@ -1294,18 +1377,17 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
transaction -> {
|
||||
|
||||
// set offer state
|
||||
openOffer.setState(OpenOffer.State.AVAILABLE);
|
||||
openOffer.setScheduledTxHashes(null);
|
||||
openOffer.setScheduledAmount(null);
|
||||
requestPersistence();
|
||||
|
||||
resultHandler.handleResult(transaction);
|
||||
if (!stopped) {
|
||||
startPeriodicRepublishOffersTimer();
|
||||
startPeriodicRefreshOffersTimer();
|
||||
} else {
|
||||
log.debug("We have stopped already. We ignore that placeOfferProtocol.placeOffer.onResult call.");
|
||||
}
|
||||
resultHandler.handleResult(transaction);
|
||||
},
|
||||
errorMessageHandler);
|
||||
|
||||
@ -1355,6 +1437,40 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
return;
|
||||
}
|
||||
|
||||
// verify max length of extra info
|
||||
if (offer.getOfferPayload().getExtraInfo() != null && offer.getOfferPayload().getExtraInfo().length() > Restrictions.MAX_EXTRA_INFO_LENGTH) {
|
||||
errorMessage = "Extra info is too long for offer " + request.offerId + ". Max length is " + Restrictions.MAX_EXTRA_INFO_LENGTH + " but got " + offer.getOfferPayload().getExtraInfo().length();
|
||||
log.warn(errorMessage);
|
||||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
// verify the trade protocol version
|
||||
if (request.getOfferPayload().getProtocolVersion() != Version.TRADE_PROTOCOL_VERSION) {
|
||||
errorMessage = "Unsupported protocol version: " + request.getOfferPayload().getProtocolVersion();
|
||||
log.warn(errorMessage);
|
||||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
// verify the min version number
|
||||
if (filterManager.getDisableTradeBelowVersion() != null) {
|
||||
if (Version.compare(request.getOfferPayload().getVersionNr(), filterManager.getDisableTradeBelowVersion()) < 0) {
|
||||
errorMessage = "Offer version number is too low: " + request.getOfferPayload().getVersionNr() + " < " + filterManager.getDisableTradeBelowVersion();
|
||||
log.warn(errorMessage);
|
||||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// verify the max version number
|
||||
if (Version.compare(request.getOfferPayload().getVersionNr(), Version.VERSION) > 0) {
|
||||
errorMessage = "Offer version number is too high: " + request.getOfferPayload().getVersionNr() + " > " + Version.VERSION;
|
||||
log.warn(errorMessage);
|
||||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
// verify maker and taker fees
|
||||
boolean hasBuyerAsTakerWithoutDeposit = offer.getDirection() == OfferDirection.SELL && offer.isPrivateOffer() && offer.getChallengeHash() != null && offer.getChallengeHash().length() > 0 && offer.getTakerFeePct() == 0;
|
||||
if (hasBuyerAsTakerWithoutDeposit) {
|
||||
@ -1557,6 +1673,14 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't allow trade start if not connected to Monero node
|
||||
if (!Boolean.TRUE.equals(xmrConnectionService.isConnected())) {
|
||||
errorMessage = "We got a handleOfferAvailabilityRequest but we are not connected to a Monero node.";
|
||||
log.info(errorMessage);
|
||||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
if (stopped) {
|
||||
errorMessage = "We have stopped already. We ignore that handleOfferAvailabilityRequest call.";
|
||||
log.debug(errorMessage);
|
||||
@ -1575,7 +1699,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
}
|
||||
|
||||
try {
|
||||
Optional<OpenOffer> openOfferOptional = getOpenOfferById(request.offerId);
|
||||
Optional<OpenOffer> openOfferOptional = getOpenOffer(request.offerId);
|
||||
AvailabilityResult availabilityResult;
|
||||
byte[] makerSignature = null;
|
||||
if (openOfferOptional.isPresent()) {
|
||||
@ -1801,27 +1925,20 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
originalOfferPayload.getChallengeHash(),
|
||||
updatedExtraDataMap,
|
||||
protocolVersion,
|
||||
originalOfferPayload.getArbitratorSigner(),
|
||||
originalOfferPayload.getArbitratorSignature(),
|
||||
originalOfferPayload.getReserveTxKeyImages(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
originalOfferPayload.getExtraInfo());
|
||||
|
||||
// Save states from original data to use for the updated
|
||||
Offer.State originalOfferState = originalOffer.getState();
|
||||
OpenOffer.State originalOpenOfferState = originalOpenOffer.getState();
|
||||
// cancel old offer
|
||||
log.info("Canceling outdated offer id={}", originalOffer.getId());
|
||||
doCancelOffer(originalOpenOffer, false);
|
||||
|
||||
// remove old offer
|
||||
originalOffer.setState(Offer.State.REMOVED);
|
||||
originalOpenOffer.setState(OpenOffer.State.CANCELED);
|
||||
removeOpenOffer(originalOpenOffer);
|
||||
|
||||
// Create new Offer
|
||||
// create new offer
|
||||
Offer updatedOffer = new Offer(updatedPayload);
|
||||
updatedOffer.setPriceFeedService(priceFeedService);
|
||||
updatedOffer.setState(originalOfferState);
|
||||
|
||||
OpenOffer updatedOpenOffer = new OpenOffer(updatedOffer, originalOpenOffer.getTriggerPrice());
|
||||
updatedOpenOffer.setState(originalOpenOfferState);
|
||||
addOpenOffer(updatedOpenOffer);
|
||||
requestPersistence();
|
||||
|
||||
@ -1873,10 +1990,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeRepublishOffer(OpenOffer openOffer) {
|
||||
maybeRepublishOffer(openOffer, null);
|
||||
}
|
||||
|
||||
private void maybeRepublishOffer(OpenOffer openOffer, @Nullable Runnable completeHandler) {
|
||||
ThreadUtils.execute(() -> {
|
||||
|
||||
@ -1886,81 +1999,54 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
return;
|
||||
}
|
||||
|
||||
// determine if offer is valid
|
||||
boolean isValid = true;
|
||||
Arbitrator arbitrator = user.getAcceptedArbitratorByAddress(openOffer.getOffer().getOfferPayload().getArbitratorSigner());
|
||||
if (arbitrator == null) {
|
||||
log.warn("Offer {} signed by unavailable arbitrator, reposting", openOffer.getId());
|
||||
isValid = false;
|
||||
} else if (!HavenoUtils.isArbitratorSignatureValid(openOffer.getOffer().getOfferPayload(), arbitrator)) {
|
||||
log.warn("Offer {} has invalid arbitrator signature, reposting", openOffer.getId());
|
||||
isValid = false;
|
||||
}
|
||||
if ((openOffer.getOffer().getOfferPayload().getReserveTxKeyImages() != null || openOffer.getOffer().getOfferPayload().getReserveTxKeyImages().isEmpty()) && (openOffer.getReserveTxHash() == null || openOffer.getReserveTxHash().isEmpty())) {
|
||||
log.warn("Offer {} is missing reserve tx hash but has reserved key images, reposting", openOffer.getId());
|
||||
isValid = false;
|
||||
}
|
||||
// reprocess offer then publish
|
||||
synchronized (processOffersLock) {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
processOffer(getOpenOffers(), openOffer, (transaction) -> {
|
||||
requestPersistence();
|
||||
latch.countDown();
|
||||
|
||||
// if valid, re-add offer to book
|
||||
if (isValid) {
|
||||
offerBookService.addOffer(openOffer.getOffer(),
|
||||
() -> {
|
||||
if (!stopped) {
|
||||
|
||||
// refresh means we send only the data needed to refresh the TTL (hash, signature and sequence no.)
|
||||
if (periodicRefreshOffersTimer == null) {
|
||||
startPeriodicRefreshOffersTimer();
|
||||
}
|
||||
if (completeHandler != null) {
|
||||
completeHandler.run();
|
||||
}
|
||||
}
|
||||
},
|
||||
errorMessage -> {
|
||||
if (!stopped) {
|
||||
log.error("Adding offer to P2P network failed. " + errorMessage);
|
||||
stopRetryRepublishOffersTimer();
|
||||
retryRepublishOffersTimer = UserThread.runAfter(OpenOfferManager.this::republishOffers,
|
||||
RETRY_REPUBLISH_DELAY_SEC);
|
||||
if (completeHandler != null) completeHandler.run();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
||||
// reset offer state to pending
|
||||
openOffer.getOffer().getOfferPayload().setArbitratorSignature(null);
|
||||
openOffer.getOffer().getOfferPayload().setArbitratorSigner(null);
|
||||
openOffer.getOffer().setState(Offer.State.UNKNOWN);
|
||||
openOffer.setState(OpenOffer.State.PENDING);
|
||||
|
||||
// republish offer
|
||||
synchronized (processOffersLock) {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
processPendingOffer(getOpenOffers(), openOffer, (transaction) -> {
|
||||
requestPersistence();
|
||||
latch.countDown();
|
||||
// skip if prevented from publishing
|
||||
if (preventedFromPublishing(openOffer)) {
|
||||
if (completeHandler != null) completeHandler.run();
|
||||
}, (errorMessage) -> {
|
||||
if (!openOffer.isCanceled()) {
|
||||
log.warn("Error republishing offer {}: {}", openOffer.getId(), errorMessage);
|
||||
openOffer.getOffer().setErrorMessage(errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
// publish offer to books
|
||||
offerBookService.addOffer(openOffer.getOffer(),
|
||||
() -> {
|
||||
if (!stopped) {
|
||||
|
||||
// cancel offer if invalid
|
||||
if (openOffer.getOffer().getState() == Offer.State.INVALID) {
|
||||
log.warn("Canceling offer because it's invalid: {}", openOffer.getId());
|
||||
doCancelOffer(openOffer);
|
||||
}
|
||||
}
|
||||
latch.countDown();
|
||||
if (completeHandler != null) completeHandler.run();
|
||||
});
|
||||
HavenoUtils.awaitLatch(latch);
|
||||
}
|
||||
// refresh means we send only the data needed to refresh the TTL (hash, signature and sequence no.)
|
||||
if (periodicRefreshOffersTimer == null) {
|
||||
startPeriodicRefreshOffersTimer();
|
||||
}
|
||||
if (completeHandler != null) {
|
||||
completeHandler.run();
|
||||
}
|
||||
}
|
||||
},
|
||||
errorMessage -> {
|
||||
if (!stopped) {
|
||||
log.error("Adding offer to P2P network failed. " + errorMessage);
|
||||
stopRetryRepublishOffersTimer();
|
||||
retryRepublishOffersTimer = UserThread.runAfter(OpenOfferManager.this::republishOffers,
|
||||
RETRY_REPUBLISH_DELAY_SEC);
|
||||
if (completeHandler != null) completeHandler.run();
|
||||
}
|
||||
});
|
||||
}, (errorMessage) -> {
|
||||
log.warn("Error republishing offer {}: {}", openOffer.getId(), errorMessage);
|
||||
latch.countDown();
|
||||
if (completeHandler != null) completeHandler.run();
|
||||
});
|
||||
HavenoUtils.awaitLatch(latch);
|
||||
}
|
||||
}, THREAD_ID);
|
||||
}
|
||||
|
||||
private boolean preventedFromPublishing(OpenOffer openOffer) {
|
||||
if (!Boolean.TRUE.equals(xmrConnectionService.isConnected())) return true;
|
||||
return openOffer.isDeactivated() || openOffer.isCanceled() || openOffer.getOffer().getOfferPayload().getArbitratorSigner() == null;
|
||||
}
|
||||
|
||||
@ -1983,25 +2069,27 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
if (periodicRefreshOffersTimer == null)
|
||||
periodicRefreshOffersTimer = UserThread.runPeriodically(() -> {
|
||||
if (!stopped) {
|
||||
int size = openOffers.size();
|
||||
//we clone our list as openOffers might change during our delayed call
|
||||
final ArrayList<OpenOffer> openOffersList = new ArrayList<>(openOffers.getList());
|
||||
for (int i = 0; i < size; i++) {
|
||||
// we delay to avoid reaching throttle limits
|
||||
// roughly 4 offers per second
|
||||
|
||||
long delay = 300;
|
||||
final long minDelay = (i + 1) * delay;
|
||||
final long maxDelay = (i + 2) * delay;
|
||||
final OpenOffer openOffer = openOffersList.get(i);
|
||||
UserThread.runAfterRandomDelay(() -> {
|
||||
// we need to check if in the meantime the offer has been removed
|
||||
boolean contained = false;
|
||||
synchronized (openOffers) {
|
||||
contained = openOffers.contains(openOffer);
|
||||
}
|
||||
if (contained) maybeRefreshOffer(openOffer, 0, 1);
|
||||
}, minDelay, maxDelay, TimeUnit.MILLISECONDS);
|
||||
synchronized (openOffers) {
|
||||
int size = openOffers.size();
|
||||
//we clone our list as openOffers might change during our delayed call
|
||||
final ArrayList<OpenOffer> openOffersList = new ArrayList<>(openOffers.getList());
|
||||
for (int i = 0; i < size; i++) {
|
||||
// we delay to avoid reaching throttle limits
|
||||
// roughly 4 offers per second
|
||||
|
||||
long delay = 300;
|
||||
final long minDelay = (i + 1) * delay;
|
||||
final long maxDelay = (i + 2) * delay;
|
||||
final OpenOffer openOffer = openOffersList.get(i);
|
||||
UserThread.runAfterRandomDelay(() -> {
|
||||
// we need to check if in the meantime the offer has been removed
|
||||
boolean contained = false;
|
||||
synchronized (openOffers) {
|
||||
contained = openOffers.contains(openOffer);
|
||||
}
|
||||
if (contained) maybeRefreshOffer(openOffer, 0, 1);
|
||||
}, minDelay, maxDelay, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.debug("We have stopped already. We ignore that periodicRefreshOffersTimer.run call.");
|
||||
|
@ -23,7 +23,7 @@ import haveno.common.handlers.ErrorMessageHandler;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.locale.Res;
|
||||
import haveno.core.offer.messages.SignOfferResponse;
|
||||
import haveno.core.offer.placeoffer.tasks.AddToOfferBook;
|
||||
import haveno.core.offer.placeoffer.tasks.MaybeAddToOfferBook;
|
||||
import haveno.core.offer.placeoffer.tasks.MakerProcessSignOfferResponse;
|
||||
import haveno.core.offer.placeoffer.tasks.MakerReserveOfferFunds;
|
||||
import haveno.core.offer.placeoffer.tasks.MakerSendSignOfferRequest;
|
||||
@ -135,7 +135,7 @@ public class PlaceOfferProtocol {
|
||||
);
|
||||
taskRunner.addTasks(
|
||||
MakerProcessSignOfferResponse.class,
|
||||
AddToOfferBook.class
|
||||
MaybeAddToOfferBook.class
|
||||
);
|
||||
|
||||
taskRunner.run();
|
||||
|
@ -87,6 +87,9 @@ public class MakerReserveOfferFunds extends Task<PlaceOfferModel> {
|
||||
try {
|
||||
//if (true) throw new RuntimeException("Pretend error");
|
||||
reserveTx = model.getXmrWalletService().createReserveTx(penaltyFee, makerFee, sendAmount, securityDeposit, returnAddress, openOffer.isReserveExactAmount(), preferredSubaddressIndex);
|
||||
} catch (IllegalStateException e) {
|
||||
log.warn("Illegal state creating reserve tx, offerId={}, error={}", openOffer.getShortId(), i + 1, e.getMessage());
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.warn("Error creating reserve tx, offerId={}, attempt={}/{}, error={}", openOffer.getShortId(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
|
||||
model.getXmrWalletService().handleWalletError(e, sourceConnection);
|
||||
|
@ -77,7 +77,7 @@ public class MakerSendSignOfferRequest extends Task<PlaceOfferModel> {
|
||||
offer.getOfferPayload().getReserveTxKeyImages(),
|
||||
returnAddress);
|
||||
|
||||
// send request to least used arbitrators until success
|
||||
// send request to random arbitrators until success
|
||||
sendSignOfferRequests(request, () -> {
|
||||
complete();
|
||||
}, (errorMessage) -> {
|
||||
|
@ -20,13 +20,14 @@ package haveno.core.offer.placeoffer.tasks;
|
||||
import haveno.common.taskrunner.Task;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.offer.Offer;
|
||||
import haveno.core.offer.OpenOffer;
|
||||
import haveno.core.offer.placeoffer.PlaceOfferModel;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
public class AddToOfferBook extends Task<PlaceOfferModel> {
|
||||
public class MaybeAddToOfferBook extends Task<PlaceOfferModel> {
|
||||
|
||||
public AddToOfferBook(TaskRunner<PlaceOfferModel> taskHandler, PlaceOfferModel model) {
|
||||
public MaybeAddToOfferBook(TaskRunner<PlaceOfferModel> taskHandler, PlaceOfferModel model) {
|
||||
super(taskHandler, model);
|
||||
}
|
||||
|
||||
@ -35,17 +36,22 @@ public class AddToOfferBook extends Task<PlaceOfferModel> {
|
||||
try {
|
||||
runInterceptHook();
|
||||
checkNotNull(model.getSignOfferResponse().getSignedOfferPayload().getArbitratorSignature(), "Offer's arbitrator signature is null: " + model.getOpenOffer().getOffer().getId());
|
||||
model.getOfferBookService().addOffer(new Offer(model.getSignOfferResponse().getSignedOfferPayload()),
|
||||
() -> {
|
||||
model.setOfferAddedToOfferBook(true);
|
||||
complete();
|
||||
},
|
||||
errorMessage -> {
|
||||
model.getOpenOffer().getOffer().setErrorMessage("Could not add offer to offerbook.\n" +
|
||||
"Please check your network connection and try again.");
|
||||
|
||||
failed(errorMessage);
|
||||
});
|
||||
if (model.getOpenOffer().isPending() || model.getOpenOffer().isAvailable()) {
|
||||
model.getOfferBookService().addOffer(new Offer(model.getSignOfferResponse().getSignedOfferPayload()),
|
||||
() -> {
|
||||
model.getOpenOffer().setState(OpenOffer.State.AVAILABLE);
|
||||
model.setOfferAddedToOfferBook(true);
|
||||
complete();
|
||||
},
|
||||
errorMessage -> {
|
||||
model.getOpenOffer().getOffer().setErrorMessage("Could not add offer to offerbook.\n" +
|
||||
"Please check your network connection and try again.");
|
||||
failed(errorMessage);
|
||||
});
|
||||
} else {
|
||||
complete();
|
||||
return;
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
model.getOpenOffer().getOffer().setErrorMessage("An error occurred.\n" +
|
||||
"Error message:\n"
|
@ -36,6 +36,7 @@ package haveno.core.payment;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import haveno.common.proto.ProtoUtil;
|
||||
import haveno.common.proto.persistable.PersistablePayload;
|
||||
import haveno.common.util.Utilities;
|
||||
@ -341,12 +342,29 @@ public abstract class PaymentAccount implements PersistablePayload {
|
||||
// ---------------------------- SERIALIZATION -----------------------------
|
||||
|
||||
public String toJson() {
|
||||
Map<String, Object> jsonMap = new HashMap<String, Object>();
|
||||
if (paymentAccountPayload != null) jsonMap.putAll(gsonBuilder.create().fromJson(paymentAccountPayload.toJson(), (Type) Object.class));
|
||||
Gson gson = gsonBuilder.create();
|
||||
Map<String, Object> jsonMap = new HashMap<>();
|
||||
|
||||
if (paymentAccountPayload != null) {
|
||||
String payloadJson = paymentAccountPayload.toJson();
|
||||
Map<String, Object> payloadMap = gson.fromJson(payloadJson, new TypeToken<Map<String, Object>>() {}.getType());
|
||||
|
||||
for (Map.Entry<String, Object> entry : payloadMap.entrySet()) {
|
||||
Object value = entry.getValue();
|
||||
if (value instanceof List) {
|
||||
List<?> list = (List<?>) value;
|
||||
String joinedString = list.stream().map(Object::toString).collect(Collectors.joining(","));
|
||||
entry.setValue(joinedString);
|
||||
}
|
||||
}
|
||||
|
||||
jsonMap.putAll(payloadMap);
|
||||
}
|
||||
|
||||
jsonMap.put("accountName", getAccountName());
|
||||
jsonMap.put("accountId", getId());
|
||||
if (paymentAccountPayload != null) jsonMap.put("salt", getSaltAsHex());
|
||||
return gsonBuilder.create().toJson(jsonMap);
|
||||
return gson.toJson(jsonMap);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -388,12 +406,7 @@ public abstract class PaymentAccount implements PersistablePayload {
|
||||
PaymentAccountForm form = new PaymentAccountForm(PaymentAccountForm.FormId.valueOf(paymentMethod.getId()));
|
||||
for (PaymentAccountFormField.FieldId fieldId : getInputFieldIds()) {
|
||||
PaymentAccountFormField field = getEmptyFormField(fieldId);
|
||||
Object value = jsonMap.get(HavenoUtils.toCamelCase(field.getId().toString()));
|
||||
if (value instanceof List) { // TODO: list should already be serialized to comma delimited string in PaymentAccount.toJson() (PaymentAccountTypeAdapter?)
|
||||
field.setValue(String.join(",", (List<String>) value));
|
||||
} else {
|
||||
field.setValue((String) value);
|
||||
}
|
||||
field.setValue((String) jsonMap.get(HavenoUtils.toCamelCase(field.getId().toString())));
|
||||
form.getFields().add(field);
|
||||
}
|
||||
return form;
|
||||
|
@ -52,7 +52,8 @@ public class ProvidersRepository {
|
||||
private static final String DEFAULT_LOCAL_NODE = "http://localhost:8078/";
|
||||
private static final List<String> DEFAULT_NODES = Arrays.asList(
|
||||
"http://elaxlgigphpicy5q7pi5wkz2ko2vgjbq4576vic7febmx4xcxvk6deqd.onion/", // Haveno
|
||||
"http://lrrgpezvdrbpoqvkavzobmj7dr2otxc5x6wgktrw337bk6mxsvfp5yid.onion/" // Cake
|
||||
"http://lrrgpezvdrbpoqvkavzobmj7dr2otxc5x6wgktrw337bk6mxsvfp5yid.onion/", // Cake
|
||||
"http://2c6y3sqmknakl3fkuwh4tjhxb2q5isr53dnfcqs33vt3y7elujc6tyad.onion/" // boldsuck
|
||||
);
|
||||
|
||||
private final Config config;
|
||||
|
@ -232,10 +232,12 @@ public abstract class SupportManager {
|
||||
getAllChatMessages(ackMessage.getSourceId()).stream()
|
||||
.filter(msg -> msg.getUid().equals(ackMessage.getSourceUid()))
|
||||
.forEach(msg -> {
|
||||
if (ackMessage.isSuccess())
|
||||
msg.setAcknowledged(true);
|
||||
else
|
||||
msg.setAckError(ackMessage.getErrorMessage());
|
||||
UserThread.execute(() -> {
|
||||
if (ackMessage.isSuccess())
|
||||
msg.setAcknowledged(true);
|
||||
else
|
||||
msg.setAckError(ackMessage.getErrorMessage());
|
||||
});
|
||||
});
|
||||
requestPersistence();
|
||||
}
|
||||
|
@ -399,13 +399,6 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
||||
chatMessage.setSystemMessage(true);
|
||||
dispute.addAndPersistChatMessage(chatMessage);
|
||||
|
||||
// export latest multisig hex
|
||||
try {
|
||||
trade.exportMultisigHex();
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to export multisig hex", e);
|
||||
}
|
||||
|
||||
// create dispute opened message
|
||||
NodeAddress agentNodeAddress = getAgentNodeAddress(dispute);
|
||||
DisputeOpenedMessage disputeOpenedMessage = new DisputeOpenedMessage(dispute,
|
||||
@ -578,8 +571,9 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
||||
trade.advanceState(Trade.State.BUYER_SENT_PAYMENT_SENT_MSG);
|
||||
}
|
||||
|
||||
// update multisig hex
|
||||
if (message.getUpdatedMultisigHex() != null) sender.setUpdatedMultisigHex(message.getUpdatedMultisigHex());
|
||||
// update opener's multisig hex
|
||||
TradePeer opener = sender == trade.getArbitrator() ? trade.getTradePeer() : sender;
|
||||
if (message.getOpenerUpdatedMultisigHex() != null) opener.setUpdatedMultisigHex(message.getOpenerUpdatedMultisigHex());
|
||||
|
||||
// add chat message with price info
|
||||
if (trade instanceof ArbitratorTrade) addPriceInfoMessage(dispute, 0);
|
||||
@ -605,7 +599,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
||||
if (trade.isArbitrator()) {
|
||||
TradePeer senderPeer = sender == trade.getMaker() ? trade.getTaker() : trade.getMaker();
|
||||
if (senderPeer != trade.getMaker() && senderPeer != trade.getTaker()) throw new RuntimeException("Sender peer is not maker or taker, address=" + senderPeer.getNodeAddress());
|
||||
sendDisputeOpenedMessageToPeer(dispute, contract, senderPeer.getPubKeyRing(), trade.getSelf().getUpdatedMultisigHex());
|
||||
sendDisputeOpenedMessageToPeer(dispute, contract, senderPeer.getPubKeyRing(), opener.getUpdatedMultisigHex());
|
||||
}
|
||||
tradeManager.requestPersistence();
|
||||
errorMessage = null;
|
||||
|
@ -62,7 +62,6 @@ import haveno.core.support.dispute.messages.DisputeClosedMessage;
|
||||
import haveno.core.support.dispute.messages.DisputeOpenedMessage;
|
||||
import haveno.core.support.messages.ChatMessage;
|
||||
import haveno.core.support.messages.SupportMessage;
|
||||
import haveno.core.trade.BuyerTrade;
|
||||
import haveno.core.trade.ClosedTradableManager;
|
||||
import haveno.core.trade.Contract;
|
||||
import haveno.core.trade.HavenoUtils;
|
||||
@ -464,14 +463,6 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||
// check daemon connection
|
||||
trade.verifyDaemonConnection();
|
||||
|
||||
// adapt from 1.0.6 to 1.0.7 which changes field usage
|
||||
// TODO: remove after future updates to allow old trades to clear
|
||||
if (trade.getPayoutTxHex() != null && trade.getBuyer().getPaymentSentMessage() != null && trade.getPayoutTxHex().equals(trade.getBuyer().getPaymentSentMessage().getPayoutTxHex())) {
|
||||
log.warn("Nullifying payout tx hex after 1.0.7 update {} {}", trade.getClass().getSimpleName(), trade.getShortId());
|
||||
if (trade instanceof BuyerTrade) trade.getSelf().setUnsignedPayoutTxHex(trade.getPayoutTxHex());
|
||||
trade.setPayoutTxHex(null);
|
||||
}
|
||||
|
||||
// sign arbitrator-signed payout tx
|
||||
if (trade.getPayoutTxHex() == null) {
|
||||
try {
|
||||
|
@ -196,7 +196,7 @@ public final class MediationManager extends DisputeManager<MediationDisputeList>
|
||||
tradeManager.requestPersistence();
|
||||
}
|
||||
} else {
|
||||
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOfferById(tradeId);
|
||||
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOffer(tradeId);
|
||||
openOfferOptional.ifPresent(openOffer -> openOfferManager.closeOpenOffer(openOffer.getOffer()));
|
||||
}
|
||||
sendAckMessage(chatMessage, dispute.getAgentPubKeyRing(), true, null);
|
||||
|
@ -34,7 +34,7 @@ import java.util.Optional;
|
||||
public final class DisputeOpenedMessage extends DisputeMessage {
|
||||
private final Dispute dispute;
|
||||
private final NodeAddress senderNodeAddress;
|
||||
private final String updatedMultisigHex;
|
||||
private final String openerUpdatedMultisigHex;
|
||||
private final PaymentSentMessage paymentSentMessage;
|
||||
|
||||
public DisputeOpenedMessage(Dispute dispute,
|
||||
@ -67,7 +67,7 @@ public final class DisputeOpenedMessage extends DisputeMessage {
|
||||
super(messageVersion, uid, supportType);
|
||||
this.dispute = dispute;
|
||||
this.senderNodeAddress = senderNodeAddress;
|
||||
this.updatedMultisigHex = updatedMultisigHex;
|
||||
this.openerUpdatedMultisigHex = updatedMultisigHex;
|
||||
this.paymentSentMessage = paymentSentMessage;
|
||||
}
|
||||
|
||||
@ -78,7 +78,7 @@ public final class DisputeOpenedMessage extends DisputeMessage {
|
||||
.setDispute(dispute.toProtoMessage())
|
||||
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
||||
.setType(SupportType.toProtoMessage(supportType))
|
||||
.setUpdatedMultisigHex(updatedMultisigHex);
|
||||
.setOpenerUpdatedMultisigHex(openerUpdatedMultisigHex);
|
||||
Optional.ofNullable(paymentSentMessage).ifPresent(e -> builder.setPaymentSentMessage(paymentSentMessage.toProtoNetworkEnvelope().getPaymentSentMessage()));
|
||||
return getNetworkEnvelopeBuilder().setDisputeOpenedMessage(builder).build();
|
||||
}
|
||||
@ -91,7 +91,7 @@ public final class DisputeOpenedMessage extends DisputeMessage {
|
||||
proto.getUid(),
|
||||
messageVersion,
|
||||
SupportType.fromProto(proto.getType()),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex()),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getOpenerUpdatedMultisigHex()),
|
||||
proto.hasPaymentSentMessage() ? PaymentSentMessage.fromProto(proto.getPaymentSentMessage(), messageVersion) : null);
|
||||
}
|
||||
|
||||
@ -108,7 +108,7 @@ public final class DisputeOpenedMessage extends DisputeMessage {
|
||||
",\n DisputeOpenedMessage.uid='" + uid + '\'' +
|
||||
",\n messageVersion=" + messageVersion +
|
||||
",\n supportType=" + supportType +
|
||||
",\n updatedMultisigHex=" + updatedMultisigHex +
|
||||
",\n openerUpdatedMultisigHex=" + openerUpdatedMultisigHex +
|
||||
",\n paymentSentMessage=" + paymentSentMessage +
|
||||
"\n} " + super.toString();
|
||||
}
|
||||
|
@ -196,7 +196,7 @@ public final class RefundManager extends DisputeManager<RefundDisputeList> {
|
||||
tradeManager.requestPersistence();
|
||||
}
|
||||
} else {
|
||||
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOfferById(tradeId);
|
||||
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOffer(tradeId);
|
||||
openOfferOptional.ifPresent(openOffer -> openOfferManager.closeOpenOffer(openOffer.getOffer()));
|
||||
}
|
||||
sendAckMessage(chatMessage, dispute.getAgentPubKeyRing(), true, null);
|
||||
@ -205,7 +205,7 @@ public final class RefundManager extends DisputeManager<RefundDisputeList> {
|
||||
if (tradeManager.getOpenTrade(tradeId).isPresent()) {
|
||||
tradeManager.closeDisputedTrade(tradeId, Trade.DisputeState.REFUND_REQUEST_CLOSED);
|
||||
} else {
|
||||
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOfferById(tradeId);
|
||||
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOffer(tradeId);
|
||||
openOfferOptional.ifPresent(openOffer -> openOfferManager.closeOpenOffer(openOffer.getOffer()));
|
||||
}
|
||||
|
||||
|
@ -150,9 +150,9 @@ public class ClosedTradableManager implements PersistedDataHost {
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<Tradable> getTradeById(String id) {
|
||||
public Optional<Trade> getTradeById(String id) {
|
||||
synchronized (closedTradables) {
|
||||
return closedTradables.stream().filter(e -> e instanceof Trade && e.getId().equals(id)).findFirst();
|
||||
return getClosedTrades().stream().filter(e -> e.getId().equals(id)).findFirst();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,6 +143,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
private static final long DELETE_AFTER_NUM_BLOCKS = 2; // if deposit requested but not published
|
||||
private static final long EXTENDED_RPC_TIMEOUT = 600000; // 10 minutes
|
||||
private static final long DELETE_AFTER_MS = TradeProtocol.TRADE_STEP_TIMEOUT_SECONDS;
|
||||
private static final int NUM_CONFIRMATIONS_FOR_SCHEDULED_IMPORT = 10;
|
||||
protected final Object pollLock = new Object();
|
||||
protected static final Object importMultisigLock = new Object();
|
||||
private boolean pollInProgress;
|
||||
@ -194,7 +195,8 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
SELLER_SENT_PAYMENT_RECEIVED_MSG(Phase.PAYMENT_RECEIVED),
|
||||
SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG(Phase.PAYMENT_RECEIVED),
|
||||
SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG(Phase.PAYMENT_RECEIVED),
|
||||
SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG(Phase.PAYMENT_RECEIVED);
|
||||
SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG(Phase.PAYMENT_RECEIVED),
|
||||
BUYER_RECEIVED_PAYMENT_RECEIVED_MSG(Phase.PAYMENT_RECEIVED);
|
||||
|
||||
@NotNull
|
||||
public Phase getPhase() {
|
||||
@ -602,12 +604,12 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
}
|
||||
}
|
||||
|
||||
// notified from TradeProtocol of ack messages
|
||||
public void onAckMessage(AckMessage ackMessage, NodeAddress sender) {
|
||||
// notified from TradeProtocol of ack messages
|
||||
public void onAckMessage(AckMessage ackMessage, NodeAddress sender) {
|
||||
for (TradeListener listener : new ArrayList<TradeListener>(tradeListeners)) { // copy array to allow listener invocation to unregister listener without concurrent modification exception
|
||||
listener.onAckMessage(ackMessage, sender);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -617,8 +619,9 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
public void initialize(ProcessModelServiceProvider serviceProvider) {
|
||||
if (isInitialized) throw new IllegalStateException(getClass().getSimpleName() + " " + getId() + " is already initialized");
|
||||
|
||||
// done if payout unlocked and marked complete
|
||||
if (isPayoutUnlocked() && isCompleted()) {
|
||||
// skip initialization if trade is complete
|
||||
// starting in v1.0.19, seller resends payment received message until acked or stored in mailbox
|
||||
if (isFinished()) {
|
||||
clearAndShutDown();
|
||||
return;
|
||||
}
|
||||
@ -633,16 +636,20 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
ThreadUtils.execute(() -> onConnectionChanged(connection), getId());
|
||||
});
|
||||
|
||||
// reset buyer's payment sent state if no ack receive
|
||||
if (this instanceof BuyerTrade && getState().ordinal() >= Trade.State.BUYER_CONFIRMED_PAYMENT_SENT.ordinal() && getState().ordinal() < Trade.State.BUYER_STORED_IN_MAILBOX_PAYMENT_SENT_MSG.ordinal()) {
|
||||
log.warn("Resetting state of {} {} from {} to {} because no ack was received", getClass().getSimpleName(), getId(), getState(), Trade.State.DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN);
|
||||
setState(Trade.State.DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN);
|
||||
}
|
||||
// reset states if no ack receive
|
||||
if (!isPayoutPublished()) {
|
||||
|
||||
// reset seller's payment received state if no ack receive
|
||||
if (this instanceof SellerTrade && getState().ordinal() >= Trade.State.SELLER_CONFIRMED_PAYMENT_RECEIPT.ordinal() && getState().ordinal() < Trade.State.SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG.ordinal()) {
|
||||
log.warn("Resetting state of {} {} from {} to {} because no ack was received", getClass().getSimpleName(), getId(), getState(), Trade.State.BUYER_SENT_PAYMENT_SENT_MSG);
|
||||
setState(Trade.State.BUYER_SENT_PAYMENT_SENT_MSG);
|
||||
// reset buyer's payment sent state if no ack receive
|
||||
if (this instanceof BuyerTrade && getState().ordinal() >= Trade.State.BUYER_CONFIRMED_PAYMENT_SENT.ordinal() && getState().ordinal() < Trade.State.BUYER_STORED_IN_MAILBOX_PAYMENT_SENT_MSG.ordinal()) {
|
||||
log.warn("Resetting state of {} {} from {} to {} because no ack was received", getClass().getSimpleName(), getId(), getState(), Trade.State.DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN);
|
||||
setState(Trade.State.DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN);
|
||||
}
|
||||
|
||||
// reset seller's payment received state if no ack receive
|
||||
if (this instanceof SellerTrade && getState().ordinal() >= Trade.State.SELLER_CONFIRMED_PAYMENT_RECEIPT.ordinal() && getState().ordinal() < Trade.State.SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG.ordinal()) {
|
||||
log.warn("Resetting state of {} {} from {} to {} because no ack was received", getClass().getSimpleName(), getId(), getState(), Trade.State.BUYER_SENT_PAYMENT_SENT_MSG);
|
||||
resetToPaymentSentState();
|
||||
}
|
||||
}
|
||||
|
||||
// handle trade state events
|
||||
@ -732,15 +739,20 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
xmrWalletService.addWalletListener(idlePayoutSyncer);
|
||||
}
|
||||
|
||||
// TODO: buyer's payment sent message state property became unsynced if shut down while awaiting ack from seller. fixed in v1.0.19 so this check can be removed?
|
||||
// TODO: buyer's payment sent message state property became unsynced if shut down while awaiting ack from seller. fixed mismatch in v1.0.19, but can this check can be removed?
|
||||
if (isBuyer()) {
|
||||
MessageState expectedState = getPaymentSentMessageState();
|
||||
if (expectedState != null && expectedState != processModel.getPaymentSentMessageStateProperty().get()) {
|
||||
log.warn("Updating unexpected payment sent message state for {} {}, expected={}, actual={}", getClass().getSimpleName(), getId(), expectedState, processModel.getPaymentSentMessageStateProperty().get());
|
||||
processModel.getPaymentSentMessageStateProperty().set(expectedState);
|
||||
if (expectedState != null && expectedState != getSeller().getPaymentSentMessageStateProperty().get()) {
|
||||
log.warn("Updating unexpected payment sent message state for {} {}, expected={}, actual={}", getClass().getSimpleName(), getId(), expectedState, processModel.getPaymentSentMessageStatePropertySeller().get());
|
||||
getSeller().getPaymentSentMessageStateProperty().set(expectedState);
|
||||
}
|
||||
}
|
||||
|
||||
// handle confirmations
|
||||
walletHeight.addListener((observable, oldValue, newValue) -> {
|
||||
importMultisigHexIfScheduled();
|
||||
});
|
||||
|
||||
// trade is initialized
|
||||
isInitialized = true;
|
||||
|
||||
@ -765,11 +777,30 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
}
|
||||
}
|
||||
|
||||
// start polling if deposit requested
|
||||
if (isDepositRequested()) tryInitPolling();
|
||||
// init syncing if deposit requested
|
||||
if (isDepositRequested()) {
|
||||
tryInitSyncing();
|
||||
}
|
||||
isFullyInitialized = true;
|
||||
}
|
||||
|
||||
public boolean isFinished() {
|
||||
return isPayoutUnlocked() && isCompleted() && !getProtocol().needsToResendPaymentReceivedMessages();
|
||||
}
|
||||
|
||||
public void resetToPaymentSentState() {
|
||||
setState(Trade.State.BUYER_SENT_PAYMENT_SENT_MSG);
|
||||
for (TradePeer peer : getAllPeers()) peer.setPaymentReceivedMessage(null);
|
||||
setPayoutTxHex(null);
|
||||
}
|
||||
|
||||
public void reprocessApplicableMessages() {
|
||||
if (!isDepositRequested() || isPayoutUnlocked() || isCompleted()) return;
|
||||
getProtocol().maybeReprocessPaymentSentMessage(false);
|
||||
getProtocol().maybeReprocessPaymentReceivedMessage(false);
|
||||
HavenoUtils.arbitrationManager.maybeReprocessDisputeClosedMessage(this, false);
|
||||
}
|
||||
|
||||
public void awaitInitialized() {
|
||||
while (!isFullyInitialized) HavenoUtils.waitFor(100); // TODO: use proper notification and refactor isInitialized, fullyInitialized, and arbitrator idling
|
||||
}
|
||||
@ -1077,6 +1108,26 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
}
|
||||
}
|
||||
|
||||
public void scheduleImportMultisigHex() {
|
||||
processModel.setImportMultisigHexScheduled(true);
|
||||
requestPersistence();
|
||||
}
|
||||
|
||||
private void importMultisigHexIfScheduled() {
|
||||
if (!isInitialized || isShutDownStarted) return;
|
||||
if (!isDepositsConfirmed() || getMaker().getDepositTx() == null) return;
|
||||
if (walletHeight.get() - getMaker().getDepositTx().getHeight() < NUM_CONFIRMATIONS_FOR_SCHEDULED_IMPORT) return;
|
||||
ThreadUtils.execute(() -> {
|
||||
if (!isInitialized || isShutDownStarted) return;
|
||||
synchronized (getLock()) {
|
||||
if (processModel.isImportMultisigHexScheduled()) {
|
||||
processModel.setImportMultisigHexScheduled(false);
|
||||
ThreadUtils.submitToPool(() -> importMultisigHex());
|
||||
}
|
||||
}
|
||||
}, getId());
|
||||
}
|
||||
|
||||
public void importMultisigHex() {
|
||||
synchronized (walletLock) {
|
||||
synchronized (HavenoUtils.getDaemonLock()) { // lock on daemon because import calls full refresh
|
||||
@ -1089,10 +1140,10 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to import multisig hex, tradeId={}, attempt={}/{}, error={}", getShortId(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
|
||||
handleWalletError(e, sourceConnection);
|
||||
doPollWallet();
|
||||
if (isPayoutPublished()) break;
|
||||
log.warn("Failed to import multisig hex, tradeId={}, attempt={}/{}, error={}", getShortId(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
|
||||
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||
}
|
||||
@ -1141,6 +1192,9 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
if (removed) wallet.importMultisigHex(multisigHexes.toArray(new String[0]));
|
||||
if (wallet.isMultisigImportNeeded()) throw new IllegalStateException(errorMessage);
|
||||
}
|
||||
|
||||
// remove scheduled import
|
||||
processModel.setImportMultisigHexScheduled(false);
|
||||
} catch (MoneroError e) {
|
||||
|
||||
// import multisig hex individually if one is invalid
|
||||
@ -1335,7 +1389,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
MoneroTxSet describedTxSet = wallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(payoutTxHex));
|
||||
if (describedTxSet.getTxs() == null || describedTxSet.getTxs().size() != 1) throw new IllegalArgumentException("Bad payout tx"); // TODO (woodser): test nack
|
||||
MoneroTxWallet payoutTx = describedTxSet.getTxs().get(0);
|
||||
if (payoutTxId == null) updatePayout(payoutTx); // update payout tx if not signed
|
||||
if (payoutTxId == null) updatePayout(payoutTx); // update payout tx if id currently unknown
|
||||
|
||||
// verify payout tx has exactly 2 destinations
|
||||
if (payoutTx.getOutgoingTransfer() == null || payoutTx.getOutgoingTransfer().getDestinations() == null || payoutTx.getOutgoingTransfer().getDestinations().size() != 2) throw new IllegalArgumentException("Payout tx does not have exactly two destinations");
|
||||
@ -1366,6 +1420,9 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
BigInteger expectedSellerPayout = sellerDepositAmount.subtract(tradeAmount).subtract(txCostSplit);
|
||||
if (!sellerPayoutDestination.getAmount().equals(expectedSellerPayout)) throw new IllegalArgumentException("Seller destination amount is not deposit amount - trade amount - 1/2 tx costs, " + sellerPayoutDestination.getAmount() + " vs " + expectedSellerPayout);
|
||||
|
||||
// update payout tx
|
||||
updatePayout(payoutTx);
|
||||
|
||||
// check connection
|
||||
boolean doSign = sign && getPayoutTxHex() == null;
|
||||
if (doSign || publish) verifyDaemonConnection();
|
||||
@ -1374,28 +1431,36 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
if (doSign) {
|
||||
|
||||
// sign tx
|
||||
String signedPayoutTxHex;
|
||||
try {
|
||||
MoneroMultisigSignResult result = wallet.signMultisigTxHex(payoutTxHex);
|
||||
if (result.getSignedMultisigTxHex() == null) throw new IllegalArgumentException("Error signing payout tx, signed multisig hex is null");
|
||||
setPayoutTxHex(result.getSignedMultisigTxHex());
|
||||
signedPayoutTxHex = result.getSignedMultisigTxHex();
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
// verify miner fee is within tolerance unless outdated offer version
|
||||
if (getOffer().getOfferPayload().getProtocolVersion() >= 2) {
|
||||
|
||||
// verify fee is within tolerance by recreating payout tx
|
||||
// TODO (monero-project): creating tx will require exchanging updated multisig hex if message needs reprocessed. provide weight with describe_transfer so fee can be estimated?
|
||||
log.info("Creating fee estimate tx for {} {}", getClass().getSimpleName(), getId());
|
||||
saveWallet(); // save wallet before creating fee estimate tx
|
||||
MoneroTxWallet feeEstimateTx = createPayoutTx();
|
||||
BigInteger feeEstimate = feeEstimateTx.getFee();
|
||||
double feeDiff = payoutTx.getFee().subtract(feeEstimate).abs().doubleValue() / feeEstimate.doubleValue(); // TODO: use BigDecimal?
|
||||
if (feeDiff > XmrWalletService.MINER_FEE_TOLERANCE) throw new IllegalArgumentException("Miner fee is not within " + (XmrWalletService.MINER_FEE_TOLERANCE * 100) + "% of estimated fee, expected " + feeEstimate + " but was " + payoutTx.getFee());
|
||||
log.info("Payout tx fee {} is within tolerance, diff %={}", payoutTx.getFee(), feeDiff);
|
||||
}
|
||||
|
||||
// set signed payout tx hex
|
||||
setPayoutTxHex(signedPayoutTxHex);
|
||||
|
||||
// describe result
|
||||
describedTxSet = wallet.describeMultisigTxSet(getPayoutTxHex());
|
||||
payoutTx = describedTxSet.getTxs().get(0);
|
||||
updatePayout(payoutTx);
|
||||
|
||||
// verify fee is within tolerance by recreating payout tx
|
||||
// TODO (monero-project): creating tx will require exchanging updated multisig hex if message needs reprocessed. provide weight with describe_transfer so fee can be estimated?
|
||||
log.info("Creating fee estimate tx for {} {}", getClass().getSimpleName(), getId());
|
||||
saveWallet(); // save wallet before creating fee estimate tx
|
||||
MoneroTxWallet feeEstimateTx = createPayoutTx();
|
||||
BigInteger feeEstimate = feeEstimateTx.getFee();
|
||||
double feeDiff = payoutTx.getFee().subtract(feeEstimate).abs().doubleValue() / feeEstimate.doubleValue(); // TODO: use BigDecimal?
|
||||
if (feeDiff > XmrWalletService.MINER_FEE_TOLERANCE) throw new IllegalArgumentException("Miner fee is not within " + (XmrWalletService.MINER_FEE_TOLERANCE * 100) + "% of estimated fee, expected " + feeEstimate + " but was " + payoutTx.getFee());
|
||||
log.info("Payout tx fee {} is within tolerance, diff %={}", payoutTx.getFee(), feeDiff);
|
||||
}
|
||||
|
||||
// save trade state
|
||||
@ -1506,7 +1571,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
peer.setUpdatedMultisigHex(null);
|
||||
peer.setDisputeClosedMessage(null);
|
||||
peer.setPaymentSentMessage(null);
|
||||
peer.setPaymentReceivedMessage(null);
|
||||
if (peer.isPaymentReceivedMessageReceived()) peer.setPaymentReceivedMessage(null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1604,7 +1669,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
}
|
||||
|
||||
// unreserve maker's open offer
|
||||
Optional<OpenOffer> openOffer = processModel.getOpenOfferManager().getOpenOfferById(this.getId());
|
||||
Optional<OpenOffer> openOffer = processModel.getOpenOfferManager().getOpenOffer(this.getId());
|
||||
if (this instanceof MakerTrade && openOffer.isPresent()) {
|
||||
processModel.getOpenOfferManager().unreserveOpenOffer(openOffer.get());
|
||||
}
|
||||
@ -1618,15 +1683,16 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
// done if wallet already deleted
|
||||
if (!walletExists()) return;
|
||||
|
||||
// move to failed trades
|
||||
processModel.getTradeManager().onMoveInvalidTradeToFailedTrades(this);
|
||||
|
||||
// set error height
|
||||
if (processModel.getTradeProtocolErrorHeight() == 0) {
|
||||
log.warn("Scheduling to remove trade if unfunded for {} {} from height {}", getClass().getSimpleName(), getId(), xmrConnectionService.getLastInfo().getHeight());
|
||||
processModel.setTradeProtocolErrorHeight(xmrConnectionService.getLastInfo().getHeight());
|
||||
processModel.setTradeProtocolErrorHeight(xmrConnectionService.getLastInfo().getHeight()); // height denotes scheduled error handling
|
||||
}
|
||||
|
||||
// move to failed trades
|
||||
processModel.getTradeManager().onMoveInvalidTradeToFailedTrades(this);
|
||||
requestPersistence();
|
||||
|
||||
// listen for deposits published to restore trade
|
||||
protocolErrorStateSubscription = EasyBind.subscribe(stateProperty(), state -> {
|
||||
if (isDepositsPublished()) {
|
||||
@ -1680,10 +1746,14 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
});
|
||||
}
|
||||
|
||||
public boolean isProtocolErrorHandlingScheduled() {
|
||||
return processModel.getTradeProtocolErrorHeight() > 0;
|
||||
}
|
||||
|
||||
private void restoreDepositsPublishedTrade() {
|
||||
|
||||
// close open offer
|
||||
if (this instanceof MakerTrade && processModel.getOpenOfferManager().getOpenOfferById(getId()).isPresent()) {
|
||||
if (this instanceof MakerTrade && processModel.getOpenOfferManager().getOpenOffer(getId()).isPresent()) {
|
||||
log.info("Closing open offer because {} {} was restored after protocol error", getClass().getSimpleName(), getShortId());
|
||||
processModel.getOpenOfferManager().closeOpenOffer(checkNotNull(getOffer()));
|
||||
}
|
||||
@ -1868,10 +1938,9 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
getSeller().setPayoutTxFee(splitTxFee);
|
||||
getBuyer().setPayoutAmount(getBuyer().getSecurityDeposit().subtract(getBuyer().getPayoutTxFee()).add(getAmount()));
|
||||
getSeller().setPayoutAmount(getSeller().getSecurityDeposit().subtract(getSeller().getPayoutTxFee()));
|
||||
} else if (getDisputeState().isClosed()) {
|
||||
} else {
|
||||
DisputeResult disputeResult = getDisputeResult();
|
||||
if (disputeResult == null) log.warn("Dispute result is not set for {} {}", getClass().getSimpleName(), getId());
|
||||
else {
|
||||
if (disputeResult != null) {
|
||||
BigInteger[] buyerSellerPayoutTxFees = ArbitrationManager.getBuyerSellerPayoutTxCost(disputeResult, payoutTx.getFee());
|
||||
getBuyer().setPayoutTxFee(buyerSellerPayoutTxFees[0]);
|
||||
getSeller().setPayoutTxFee(buyerSellerPayoutTxFees[1]);
|
||||
@ -2015,9 +2084,9 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
throw new IllegalArgumentException("Trade is not buyer, seller, or arbitrator");
|
||||
}
|
||||
|
||||
public MessageState getPaymentSentMessageState() {
|
||||
private MessageState getPaymentSentMessageState() {
|
||||
if (isPaymentReceived()) return MessageState.ACKNOWLEDGED;
|
||||
if (processModel.getPaymentSentMessageStateProperty().get() == MessageState.ACKNOWLEDGED) return MessageState.ACKNOWLEDGED;
|
||||
if (getSeller().getPaymentSentMessageStateProperty().get() == MessageState.ACKNOWLEDGED) return MessageState.ACKNOWLEDGED;
|
||||
switch (state) {
|
||||
case BUYER_SENT_PAYMENT_SENT_MSG:
|
||||
return MessageState.SENT;
|
||||
@ -2156,7 +2225,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
}
|
||||
|
||||
public boolean isPaymentSent() {
|
||||
return getState().getPhase().ordinal() >= Phase.PAYMENT_SENT.ordinal();
|
||||
return getState().getPhase().ordinal() >= Phase.PAYMENT_SENT.ordinal() && getState() != State.BUYER_SEND_FAILED_PAYMENT_SENT_MSG;
|
||||
}
|
||||
|
||||
public boolean hasPaymentReceivedMessage() {
|
||||
@ -2174,7 +2243,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
}
|
||||
|
||||
public boolean isPaymentReceived() {
|
||||
return getState().getPhase().ordinal() >= Phase.PAYMENT_RECEIVED.ordinal();
|
||||
return getState().getPhase().ordinal() >= Phase.PAYMENT_RECEIVED.ordinal() && getState() != State.SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG;
|
||||
}
|
||||
|
||||
public boolean isPayoutPublished() {
|
||||
@ -2345,7 +2414,12 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
return tradeAmountTransferred();
|
||||
}
|
||||
|
||||
public boolean tradeAmountTransferred() {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private boolean tradeAmountTransferred() {
|
||||
return isPaymentReceived() || (getDisputeResult() != null && getDisputeResult().getWinner() == DisputeResult.Winner.SELLER);
|
||||
}
|
||||
|
||||
@ -2361,11 +2435,6 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// lazy initialization
|
||||
private ObjectProperty<BigInteger> getAmountProperty() {
|
||||
if (tradeAmountProperty == null)
|
||||
@ -2410,11 +2479,12 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
|
||||
// sync and reprocess messages on new thread
|
||||
if (isInitialized && connection != null && !Boolean.FALSE.equals(xmrConnectionService.isConnected())) {
|
||||
ThreadUtils.execute(() -> tryInitPolling(), getId());
|
||||
ThreadUtils.execute(() -> tryInitSyncing(), getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
private void tryInitPolling() {
|
||||
|
||||
private void tryInitSyncing() {
|
||||
if (isShutDownStarted) return;
|
||||
|
||||
// set known deposit txs
|
||||
@ -2423,23 +2493,18 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
|
||||
// start polling
|
||||
if (!isIdling()) {
|
||||
tryInitPollingAux();
|
||||
doTryInitSyncing();
|
||||
} else {
|
||||
long startSyncingInMs = ThreadLocalRandom.current().nextLong(0, getPollPeriod()); // random time to start polling
|
||||
UserThread.runAfter(() -> ThreadUtils.execute(() -> {
|
||||
if (!isShutDownStarted) tryInitPollingAux();
|
||||
if (!isShutDownStarted) doTryInitSyncing();
|
||||
}, getId()), startSyncingInMs / 1000l);
|
||||
}
|
||||
}
|
||||
|
||||
private void tryInitPollingAux() {
|
||||
private void doTryInitSyncing() {
|
||||
if (!wasWalletSynced) trySyncWallet(true);
|
||||
updatePollPeriod();
|
||||
|
||||
// reprocess pending payout messages
|
||||
this.getProtocol().maybeReprocessPaymentReceivedMessage(false);
|
||||
HavenoUtils.arbitrationManager.maybeReprocessDisputeClosedMessage(this, false);
|
||||
|
||||
startPolling();
|
||||
}
|
||||
|
||||
@ -2790,7 +2855,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
if (!isShutDownStarted) wallet = getWallet();
|
||||
restartInProgress = false;
|
||||
pollWallet();
|
||||
if (!isShutDownStarted) ThreadUtils.execute(() -> tryInitPolling(), getId());
|
||||
if (!isShutDownStarted) ThreadUtils.execute(() -> tryInitSyncing(), getId());
|
||||
}
|
||||
|
||||
private void setStateDepositsSeen() {
|
||||
|
@ -450,8 +450,8 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||
return;
|
||||
}
|
||||
|
||||
// skip if marked as failed
|
||||
if (failedTradesManager.getObservableList().contains(trade)) {
|
||||
// skip if failed and error handling not scheduled
|
||||
if (failedTradesManager.getObservableList().contains(trade) && !trade.isProtocolErrorHandlingScheduled()) {
|
||||
log.warn("Skipping initialization of failed trade {} {}", trade.getClass().getSimpleName(), trade.getId());
|
||||
tradesToSkip.add(trade);
|
||||
return;
|
||||
@ -460,8 +460,8 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||
// initialize trade
|
||||
initPersistedTrade(trade);
|
||||
|
||||
// remove trade if protocol didn't initialize
|
||||
if (getOpenTradeByUid(trade.getUid()).isPresent() && !trade.isDepositsPublished()) {
|
||||
// record if protocol didn't initialize
|
||||
if (!trade.isDepositsPublished()) {
|
||||
uninitializedTrades.add(trade);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@ -556,7 +556,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||
if (request.getMakerNodeAddress().equals(p2PService.getNetworkNode().getNodeAddress())) {
|
||||
|
||||
// get open offer
|
||||
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOfferById(request.getOfferId());
|
||||
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOffer(request.getOfferId());
|
||||
if (!openOfferOptional.isPresent()) return;
|
||||
OpenOffer openOffer = openOfferOptional.get();
|
||||
if (openOffer.getState() != OpenOffer.State.AVAILABLE) return;
|
||||
@ -747,7 +747,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||
}
|
||||
|
||||
private void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender) {
|
||||
log.info("TradeManager handling InitMultisigRequest for tradeId={}, sender={}, uid={}", request.getOfferId(), sender, request.getUid());
|
||||
log.info("TradeManager handling InitMultisigRequest for tradeId={}, sender={}, uid={}", request.getOfferId(), sender, request.getUid());
|
||||
|
||||
try {
|
||||
Validator.nonEmptyStringOf(request.getOfferId());
|
||||
@ -766,7 +766,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||
}
|
||||
|
||||
private void handleSignContractRequest(SignContractRequest request, NodeAddress sender) {
|
||||
log.info("TradeManager handling SignContractRequest for tradeId={}, sender={}, uid={}", request.getOfferId(), sender, request.getUid());
|
||||
log.info("TradeManager handling SignContractRequest for tradeId={}, sender={}, uid={}", request.getOfferId(), sender, request.getUid());
|
||||
|
||||
try {
|
||||
Validator.nonEmptyStringOf(request.getOfferId());
|
||||
@ -923,8 +923,8 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||
requestPersistence();
|
||||
}, errorMessage -> {
|
||||
log.warn("Taker error during trade initialization: " + errorMessage);
|
||||
xmrWalletService.resetAddressEntriesForOpenOffer(trade.getId()); // TODO: move to maybe remove on error
|
||||
trade.onProtocolError();
|
||||
xmrWalletService.resetAddressEntriesForOpenOffer(trade.getId()); // TODO: move this into protocol error handling
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
});
|
||||
|
||||
@ -1285,6 +1285,12 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasFailedScheduledTrade(String offerId) {
|
||||
synchronized (failedTradesManager) {
|
||||
return failedTradesManager.getTradeById(offerId).isPresent() && failedTradesManager.getTradeById(offerId).get().isProtocolErrorHandlingScheduled();
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<Trade> getOpenTradeByUid(String tradeUid) {
|
||||
synchronized (tradableList) {
|
||||
return tradableList.stream().filter(e -> e.getUid().equals(tradeUid)).findFirst();
|
||||
@ -1315,7 +1321,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||
}
|
||||
|
||||
public Optional<Trade> getClosedTrade(String tradeId) {
|
||||
return closedTradableManager.getClosedTrades().stream().filter(e -> e.getId().equals(tradeId)).findFirst();
|
||||
return closedTradableManager.getTradeById(tradeId);
|
||||
}
|
||||
|
||||
public Optional<Trade> getFailedTrade(String tradeId) {
|
||||
|
@ -43,7 +43,7 @@ public class ArbitratorProtocol extends DisputeProtocol {
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void handleInitTradeRequest(InitTradeRequest message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("ArbitratorProtocol.handleInitTradeRequest()");
|
||||
log.info(TradeProtocol.LOG_HIGHLIGHT + "handleInitTradeRequest() for {} {}", trade.getClass().getSimpleName(), trade.getShortId());
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade.getLock()) {
|
||||
latchTrade();
|
||||
@ -78,7 +78,7 @@ public class ArbitratorProtocol extends DisputeProtocol {
|
||||
}
|
||||
|
||||
public void handleDepositRequest(DepositRequest request, NodeAddress sender) {
|
||||
System.out.println("ArbitratorProtocol.handleDepositRequest() " + trade.getId());
|
||||
log.info(TradeProtocol.LOG_HIGHLIGHT + "handleDepositRequest() for {} {}", trade.getClass().getSimpleName(), trade.getShortId());
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade.getLock()) {
|
||||
latchTrade();
|
||||
|
@ -60,8 +60,8 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
|
||||
public void handleInitTradeRequest(InitTradeRequest message,
|
||||
NodeAddress peer,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println(getClass().getCanonicalName() + ".handleInitTradeRequest()");
|
||||
ThreadUtils.execute(() -> {
|
||||
log.info(TradeProtocol.LOG_HIGHLIGHT + "handleInitTradeRequest() for {} {} from {}", trade.getClass().getSimpleName(), trade.getShortId(), peer);
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade.getLock()) {
|
||||
latchTrade();
|
||||
this.errorMessageHandler = errorMessageHandler;
|
||||
|
@ -68,7 +68,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
|
||||
@Override
|
||||
public void onTakeOffer(TradeResultHandler tradeResultHandler,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println(getClass().getSimpleName() + ".onTakeOffer()");
|
||||
log.info(TradeProtocol.LOG_HIGHLIGHT + "onTakerOffer for {} {}", getClass().getSimpleName(), trade.getShortId());
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade.getLock()) {
|
||||
latchTrade();
|
||||
@ -99,7 +99,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
|
||||
@Override
|
||||
public void handleInitTradeRequest(InitTradeRequest message,
|
||||
NodeAddress peer) {
|
||||
System.out.println(getClass().getCanonicalName() + ".handleInitTradeRequest()");
|
||||
log.info(TradeProtocol.LOG_HIGHLIGHT + "handleInitTradeRequest() for {} {} from {}", trade.getClass().getSimpleName(), trade.getShortId(), peer);
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade.getLock()) {
|
||||
latchTrade();
|
||||
|
@ -119,7 +119,7 @@ public class BuyerProtocol extends DisputeProtocol {
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void onPaymentSent(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("BuyerProtocol.onPaymentSent()");
|
||||
log.info(TradeProtocol.LOG_HIGHLIGHT + "BuyerProtocol.onPaymentSent() for {} {}", trade.getClass().getSimpleName(), trade.getShortId());
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade.getLock()) {
|
||||
latchTrade();
|
||||
|
@ -44,6 +44,7 @@ import haveno.core.account.witness.AccountAgeWitnessService;
|
||||
import haveno.core.filter.FilterManager;
|
||||
import haveno.core.network.MessageState;
|
||||
import haveno.core.offer.Offer;
|
||||
import haveno.core.offer.OfferDirection;
|
||||
import haveno.core.offer.OpenOfferManager;
|
||||
import haveno.core.payment.PaymentAccount;
|
||||
import haveno.core.payment.payload.PaymentAccountPayload;
|
||||
@ -73,6 +74,9 @@ import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
// Fields marked as transient are only used during protocol execution which are based on directMessages so we do not
|
||||
@ -90,6 +94,7 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
transient private ProcessModelServiceProvider provider;
|
||||
transient private TradeManager tradeManager;
|
||||
transient private Offer offer;
|
||||
transient public Throwable error;
|
||||
|
||||
// Added in v1.4.0
|
||||
// MessageState of the last message sent from the seller to the buyer in the take offer process.
|
||||
@ -158,15 +163,14 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
@Getter
|
||||
@Setter
|
||||
private long tradeProtocolErrorHeight;
|
||||
|
||||
// We want to indicate the user the state of the message delivery of the
|
||||
// PaymentSentMessage. As well we do an automatic re-send in case it was not ACKed yet.
|
||||
// To enable that even after restart we persist the state.
|
||||
@Getter
|
||||
@Setter
|
||||
private ObjectProperty<MessageState> paymentSentMessageStateProperty = new SimpleObjectProperty<>(MessageState.UNDEFINED);
|
||||
@Setter
|
||||
private ObjectProperty<MessageState> paymentSentMessageStatePropertyArbitrator = new SimpleObjectProperty<>(MessageState.UNDEFINED);
|
||||
private boolean importMultisigHexScheduled;
|
||||
private ObjectProperty<Boolean> paymentAccountDecryptedProperty = new SimpleObjectProperty<>(false);
|
||||
@Deprecated
|
||||
private ObjectProperty<MessageState> paymentSentMessageStatePropertySeller = new SimpleObjectProperty<>(MessageState.UNDEFINED);
|
||||
@Deprecated
|
||||
private ObjectProperty<MessageState> paymentSentMessageStatePropertyArbitrator = new SimpleObjectProperty<>(MessageState.UNDEFINED);
|
||||
|
||||
public ProcessModel(String offerId, String accountId, PubKeyRing pubKeyRing) {
|
||||
this(offerId, accountId, pubKeyRing, new TradePeer(), new TradePeer(), new TradePeer());
|
||||
@ -188,6 +192,31 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
this.offer = offer;
|
||||
this.provider = provider;
|
||||
this.tradeManager = tradeManager;
|
||||
for (TradePeer peer : getTradePeers()) {
|
||||
peer.applyTransient(tradeManager);
|
||||
}
|
||||
|
||||
// migrate deprecated fields to new model for v1.0.19
|
||||
if (paymentSentMessageStatePropertySeller.get() != MessageState.UNDEFINED && getSeller().getPaymentSentMessageStateProperty().get() == MessageState.UNDEFINED) {
|
||||
getSeller().getPaymentSentMessageStateProperty().set(paymentSentMessageStatePropertySeller.get());
|
||||
tradeManager.requestPersistence();
|
||||
}
|
||||
if (paymentSentMessageStatePropertyArbitrator.get() != MessageState.UNDEFINED && getArbitrator().getPaymentSentMessageStateProperty().get() == MessageState.UNDEFINED) {
|
||||
getArbitrator().getPaymentSentMessageStateProperty().set(paymentSentMessageStatePropertyArbitrator.get());
|
||||
tradeManager.requestPersistence();
|
||||
}
|
||||
}
|
||||
|
||||
private List<TradePeer> getTradePeers() {
|
||||
return Arrays.asList(maker, taker, arbitrator);
|
||||
}
|
||||
|
||||
private TradePeer getBuyer() {
|
||||
return offer.getDirection() == OfferDirection.BUY ? maker : taker;
|
||||
}
|
||||
|
||||
private TradePeer getSeller() {
|
||||
return offer.getDirection() == OfferDirection.BUY ? taker : maker;
|
||||
}
|
||||
|
||||
|
||||
@ -203,11 +232,12 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
.setPubKeyRing(pubKeyRing.toProtoMessage())
|
||||
.setUseSavingsWallet(useSavingsWallet)
|
||||
.setFundsNeededForTrade(fundsNeededForTrade)
|
||||
.setPaymentSentMessageState(paymentSentMessageStateProperty.get().name())
|
||||
.setPaymentSentMessageStateSeller(paymentSentMessageStatePropertySeller.get().name())
|
||||
.setPaymentSentMessageStateArbitrator(paymentSentMessageStatePropertyArbitrator.get().name())
|
||||
.setBuyerPayoutAmountFromMediation(buyerPayoutAmountFromMediation)
|
||||
.setSellerPayoutAmountFromMediation(sellerPayoutAmountFromMediation)
|
||||
.setTradeProtocolErrorHeight(tradeProtocolErrorHeight);
|
||||
.setTradeProtocolErrorHeight(tradeProtocolErrorHeight)
|
||||
.setImportMultisigHexScheduled(importMultisigHexScheduled);
|
||||
Optional.ofNullable(maker).ifPresent(e -> builder.setMaker((protobuf.TradePeer) maker.toProtoMessage()));
|
||||
Optional.ofNullable(taker).ifPresent(e -> builder.setTaker((protobuf.TradePeer) taker.toProtoMessage()));
|
||||
Optional.ofNullable(arbitrator).ifPresent(e -> builder.setArbitrator((protobuf.TradePeer) arbitrator.toProtoMessage()));
|
||||
@ -231,6 +261,7 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
processModel.setBuyerPayoutAmountFromMediation(proto.getBuyerPayoutAmountFromMediation());
|
||||
processModel.setSellerPayoutAmountFromMediation(proto.getSellerPayoutAmountFromMediation());
|
||||
processModel.setTradeProtocolErrorHeight(proto.getTradeProtocolErrorHeight());
|
||||
processModel.setImportMultisigHexScheduled(proto.getImportMultisigHexScheduled());
|
||||
|
||||
// nullable
|
||||
processModel.setPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getPayoutTxSignature()));
|
||||
@ -240,14 +271,13 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
processModel.setTradeFeeAddress(ProtoUtil.stringOrNullFromProto(proto.getTradeFeeAddress()));
|
||||
processModel.setMultisigAddress(ProtoUtil.stringOrNullFromProto(proto.getMultisigAddress()));
|
||||
|
||||
String paymentSentMessageStateString = ProtoUtil.stringOrNullFromProto(proto.getPaymentSentMessageState());
|
||||
MessageState paymentSentMessageState = ProtoUtil.enumFromProto(MessageState.class, paymentSentMessageStateString);
|
||||
processModel.setPaymentSentMessageState(paymentSentMessageState);
|
||||
|
||||
// deprecated fields need to be read in order to migrate to new fields
|
||||
String paymentSentMessageStateSellerString = ProtoUtil.stringOrNullFromProto(proto.getPaymentSentMessageStateSeller());
|
||||
MessageState paymentSentMessageStateSeller = ProtoUtil.enumFromProto(MessageState.class, paymentSentMessageStateSellerString);
|
||||
processModel.paymentSentMessageStatePropertySeller.set(paymentSentMessageStateSeller);
|
||||
String paymentSentMessageStateArbitratorString = ProtoUtil.stringOrNullFromProto(proto.getPaymentSentMessageStateArbitrator());
|
||||
MessageState paymentSentMessageStateArbitrator = ProtoUtil.enumFromProto(MessageState.class, paymentSentMessageStateArbitratorString);
|
||||
processModel.setPaymentSentMessageStateArbitrator(paymentSentMessageStateArbitrator);
|
||||
|
||||
processModel.paymentSentMessageStatePropertyArbitrator.set(paymentSentMessageStateArbitrator);
|
||||
return processModel;
|
||||
}
|
||||
|
||||
@ -274,32 +304,8 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
return getP2PService().getAddress();
|
||||
}
|
||||
|
||||
void setPaymentSentAckMessage(AckMessage ackMessage) {
|
||||
MessageState messageState = ackMessage.isSuccess() ?
|
||||
MessageState.ACKNOWLEDGED :
|
||||
MessageState.FAILED;
|
||||
setPaymentSentMessageState(messageState);
|
||||
}
|
||||
|
||||
void setPaymentSentAckMessageArbitrator(AckMessage ackMessage) {
|
||||
MessageState messageState = ackMessage.isSuccess() ?
|
||||
MessageState.ACKNOWLEDGED :
|
||||
MessageState.FAILED;
|
||||
setPaymentSentMessageStateArbitrator(messageState);
|
||||
}
|
||||
|
||||
public void setPaymentSentMessageState(MessageState paymentSentMessageStateProperty) {
|
||||
this.paymentSentMessageStateProperty.set(paymentSentMessageStateProperty);
|
||||
if (tradeManager != null) {
|
||||
tradeManager.requestPersistence();
|
||||
}
|
||||
}
|
||||
|
||||
public void setPaymentSentMessageStateArbitrator(MessageState paymentSentMessageStateProperty) {
|
||||
this.paymentSentMessageStatePropertyArbitrator.set(paymentSentMessageStateProperty);
|
||||
if (tradeManager != null) {
|
||||
tradeManager.requestPersistence();
|
||||
}
|
||||
public boolean isPaymentReceivedMessagesReceived() {
|
||||
return getArbitrator().isPaymentReceivedMessageReceived() && getBuyer().isPaymentReceivedMessageReceived();
|
||||
}
|
||||
|
||||
void setDepositTxSentAckMessage(AckMessage ackMessage) {
|
||||
|
@ -65,7 +65,7 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
|
||||
public void handleInitTradeRequest(InitTradeRequest message,
|
||||
NodeAddress peer,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println(getClass().getCanonicalName() + ".handleInitTradeRequest()");
|
||||
log.info(TradeProtocol.LOG_HIGHLIGHT + "handleInitTradeRequest() for {} {} from {}", trade.getClass().getSimpleName(), trade.getShortId(), peer);
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade.getLock()) {
|
||||
latchTrade();
|
||||
|
@ -68,7 +68,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
|
||||
@Override
|
||||
public void onTakeOffer(TradeResultHandler tradeResultHandler,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println(getClass().getSimpleName() + ".onTakeOffer()");
|
||||
log.info(TradeProtocol.LOG_HIGHLIGHT + "onTakerOffer for {} {}", getClass().getSimpleName(), trade.getShortId());
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade.getLock()) {
|
||||
latchTrade();
|
||||
@ -99,7 +99,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
|
||||
@Override
|
||||
public void handleInitTradeRequest(InitTradeRequest message,
|
||||
NodeAddress peer) {
|
||||
System.out.println(getClass().getCanonicalName() + ".handleInitTradeRequest()");
|
||||
log.info(TradeProtocol.LOG_HIGHLIGHT + "handleInitTradeRequest() for {} {} from {}", trade.getClass().getSimpleName(), trade.getShortId(), peer);
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade.getLock()) {
|
||||
latchTrade();
|
||||
|
@ -53,6 +53,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class SellerProtocol extends DisputeProtocol {
|
||||
|
||||
enum SellerEvent implements FluentProtocol.Event {
|
||||
STARTUP,
|
||||
DEPOSIT_TXS_CONFIRMED,
|
||||
@ -69,31 +70,37 @@ public class SellerProtocol extends DisputeProtocol {
|
||||
|
||||
// re-send payment received message if payout not published
|
||||
ThreadUtils.execute(() -> {
|
||||
if (trade.isShutDownStarted() || trade.isPayoutPublished()) return;
|
||||
if (!needsToResendPaymentReceivedMessages()) return;
|
||||
synchronized (trade.getLock()) {
|
||||
if (trade.isShutDownStarted() || trade.isPayoutPublished()) return;
|
||||
if (trade.getState().ordinal() >= Trade.State.SELLER_SENT_PAYMENT_RECEIVED_MSG.ordinal() && !trade.isPayoutPublished()) {
|
||||
latchTrade();
|
||||
given(anyPhase(Trade.Phase.PAYMENT_RECEIVED)
|
||||
.with(SellerEvent.STARTUP))
|
||||
.setup(tasks(
|
||||
SellerSendPaymentReceivedMessageToBuyer.class,
|
||||
SellerSendPaymentReceivedMessageToArbitrator.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
unlatchTrade();
|
||||
},
|
||||
(errorMessage) -> {
|
||||
log.warn("Error sending PaymentReceivedMessage on startup: " + errorMessage);
|
||||
unlatchTrade();
|
||||
})))
|
||||
.executeTasks();
|
||||
awaitTradeLatch();
|
||||
}
|
||||
if (!needsToResendPaymentReceivedMessages()) return;
|
||||
latchTrade();
|
||||
given(anyPhase(Trade.Phase.PAYMENT_RECEIVED)
|
||||
.with(SellerEvent.STARTUP))
|
||||
.setup(tasks(
|
||||
SellerSendPaymentReceivedMessageToBuyer.class,
|
||||
SellerSendPaymentReceivedMessageToArbitrator.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
unlatchTrade();
|
||||
},
|
||||
(errorMessage) -> {
|
||||
log.warn("Error sending PaymentReceivedMessage on startup: " + errorMessage);
|
||||
unlatchTrade();
|
||||
})))
|
||||
.executeTasks();
|
||||
awaitTradeLatch();
|
||||
}
|
||||
}, trade.getId());
|
||||
}
|
||||
|
||||
public boolean needsToResendPaymentReceivedMessages() {
|
||||
return !trade.isShutDownStarted() && trade.getState().ordinal() >= Trade.State.SELLER_SENT_PAYMENT_RECEIVED_MSG.ordinal() && !trade.getProcessModel().isPaymentReceivedMessagesReceived() && resendPaymentReceivedMessagesEnabled();
|
||||
}
|
||||
|
||||
private boolean resendPaymentReceivedMessagesEnabled() {
|
||||
return trade.getOffer().getOfferPayload().getProtocolVersion() >= 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTradeMessage(TradeMessage message, NodeAddress peer) {
|
||||
super.onTradeMessage(message, peer);
|
||||
@ -115,7 +122,7 @@ public class SellerProtocol extends DisputeProtocol {
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void onPaymentReceived(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||
log.info("SellerProtocol.onPaymentReceived()");
|
||||
log.info(TradeProtocol.LOG_HIGHLIGHT + "SellerProtocol.onPaymentReceived() for {} {}", trade.getClass().getSimpleName(), trade.getShortId());
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade.getLock()) {
|
||||
latchTrade();
|
||||
@ -137,7 +144,7 @@ public class SellerProtocol extends DisputeProtocol {
|
||||
resultHandler.handleResult();
|
||||
}, (errorMessage) -> {
|
||||
log.warn("Error confirming payment received, reverting state to {}, error={}", Trade.State.BUYER_SENT_PAYMENT_SENT_MSG, errorMessage);
|
||||
trade.setState(Trade.State.BUYER_SENT_PAYMENT_SENT_MSG);
|
||||
trade.resetToPaymentSentState();
|
||||
handleTaskRunnerFault(event, errorMessage);
|
||||
})))
|
||||
.run(() -> trade.advanceState(Trade.State.SELLER_CONFIRMED_PAYMENT_RECEIPT))
|
||||
|
@ -24,12 +24,17 @@ import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.proto.ProtoUtil;
|
||||
import haveno.common.proto.persistable.PersistablePayload;
|
||||
import haveno.core.account.witness.AccountAgeWitness;
|
||||
import haveno.core.network.MessageState;
|
||||
import haveno.core.payment.payload.PaymentAccountPayload;
|
||||
import haveno.core.proto.CoreProtoResolver;
|
||||
import haveno.core.support.dispute.messages.DisputeClosedMessage;
|
||||
import haveno.core.trade.TradeManager;
|
||||
import haveno.core.trade.messages.PaymentReceivedMessage;
|
||||
import haveno.core.trade.messages.PaymentSentMessage;
|
||||
import haveno.network.p2p.AckMessage;
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -57,6 +62,7 @@ public final class TradePeer implements PersistablePayload {
|
||||
@Nullable
|
||||
transient private byte[] preparedDepositTx;
|
||||
transient private MoneroTxWallet depositTx;
|
||||
transient private TradeManager tradeManager;
|
||||
|
||||
// Persistable mutable
|
||||
@Nullable
|
||||
@ -96,7 +102,6 @@ public final class TradePeer implements PersistablePayload {
|
||||
@Getter
|
||||
private DisputeClosedMessage disputeClosedMessage;
|
||||
|
||||
|
||||
// added in v 0.6
|
||||
@Nullable
|
||||
private byte[] accountAgeWitnessNonce;
|
||||
@ -142,13 +147,32 @@ public final class TradePeer implements PersistablePayload {
|
||||
private long payoutAmount;
|
||||
@Nullable
|
||||
private String updatedMultisigHex;
|
||||
@Getter
|
||||
@Deprecated
|
||||
private boolean depositsConfirmedMessageAcked;
|
||||
|
||||
// We want to indicate the user the state of the message delivery of the payment
|
||||
// confirmation messages. We do an automatic re-send in case it was not ACKed yet.
|
||||
// To enable that even after restart we persist the state.
|
||||
@Setter
|
||||
boolean depositsConfirmedMessageAcked;
|
||||
private ObjectProperty<MessageState> depositsConfirmedMessageStateProperty = new SimpleObjectProperty<>(MessageState.UNDEFINED);
|
||||
@Setter
|
||||
private ObjectProperty<MessageState> paymentSentMessageStateProperty = new SimpleObjectProperty<>(MessageState.UNDEFINED);
|
||||
@Setter
|
||||
private ObjectProperty<MessageState> paymentReceivedMessageStateProperty = new SimpleObjectProperty<>(MessageState.UNDEFINED);
|
||||
|
||||
public TradePeer() {
|
||||
}
|
||||
|
||||
public void applyTransient(TradeManager tradeManager) {
|
||||
this.tradeManager = tradeManager;
|
||||
|
||||
// migrate deprecated fields to new model for v1.0.19
|
||||
if (depositsConfirmedMessageAcked && depositsConfirmedMessageStateProperty.get() == MessageState.UNDEFINED) {
|
||||
depositsConfirmedMessageStateProperty.set(MessageState.ACKNOWLEDGED);
|
||||
tradeManager.requestPersistence();
|
||||
}
|
||||
}
|
||||
|
||||
public BigInteger getDepositTxFee() {
|
||||
return BigInteger.valueOf(depositTxFee);
|
||||
}
|
||||
@ -181,6 +205,60 @@ public final class TradePeer implements PersistablePayload {
|
||||
this.payoutAmount = payoutAmount.longValueExact();
|
||||
}
|
||||
|
||||
void setDepositsConfirmedAckMessage(AckMessage ackMessage) {
|
||||
MessageState messageState = ackMessage.isSuccess() ?
|
||||
MessageState.ACKNOWLEDGED :
|
||||
MessageState.FAILED;
|
||||
setDepositsConfirmedMessageState(messageState);
|
||||
}
|
||||
|
||||
void setPaymentSentAckMessage(AckMessage ackMessage) {
|
||||
MessageState messageState = ackMessage.isSuccess() ?
|
||||
MessageState.ACKNOWLEDGED :
|
||||
MessageState.FAILED;
|
||||
setPaymentSentMessageState(messageState);
|
||||
}
|
||||
|
||||
void setPaymentReceivedAckMessage(AckMessage ackMessage) {
|
||||
MessageState messageState = ackMessage.isSuccess() ?
|
||||
MessageState.ACKNOWLEDGED :
|
||||
MessageState.FAILED;
|
||||
setPaymentReceivedMessageState(messageState);
|
||||
}
|
||||
|
||||
public void setDepositsConfirmedMessageState(MessageState depositsConfirmedMessageStateProperty) {
|
||||
this.depositsConfirmedMessageStateProperty.set(depositsConfirmedMessageStateProperty);
|
||||
if (tradeManager != null) {
|
||||
tradeManager.requestPersistence();
|
||||
}
|
||||
}
|
||||
|
||||
public void setPaymentSentMessageState(MessageState paymentSentMessageStateProperty) {
|
||||
this.paymentSentMessageStateProperty.set(paymentSentMessageStateProperty);
|
||||
if (tradeManager != null) {
|
||||
tradeManager.requestPersistence();
|
||||
}
|
||||
}
|
||||
|
||||
public void setPaymentReceivedMessageState(MessageState paymentReceivedMessageStateProperty) {
|
||||
this.paymentReceivedMessageStateProperty.set(paymentReceivedMessageStateProperty);
|
||||
if (tradeManager != null) {
|
||||
tradeManager.requestPersistence();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDepositsConfirmedMessageAcked() {
|
||||
return depositsConfirmedMessageStateProperty.get() == MessageState.ACKNOWLEDGED;
|
||||
}
|
||||
|
||||
public boolean isPaymentSentMessageAcked() {
|
||||
return paymentSentMessageStateProperty.get() == MessageState.ACKNOWLEDGED;
|
||||
}
|
||||
|
||||
public boolean isPaymentReceivedMessageReceived() {
|
||||
return paymentReceivedMessageStateProperty.get() == MessageState.ACKNOWLEDGED || paymentReceivedMessageStateProperty.get() == MessageState.STORED_IN_MAILBOX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message toProtoMessage() {
|
||||
final protobuf.TradePeer.Builder builder = protobuf.TradePeer.newBuilder();
|
||||
@ -221,6 +299,9 @@ public final class TradePeer implements PersistablePayload {
|
||||
Optional.ofNullable(payoutTxFee).ifPresent(e -> builder.setPayoutTxFee(payoutTxFee));
|
||||
Optional.ofNullable(payoutAmount).ifPresent(e -> builder.setPayoutAmount(payoutAmount));
|
||||
builder.setDepositsConfirmedMessageAcked(depositsConfirmedMessageAcked);
|
||||
builder.setDepositsConfirmedMessageState(depositsConfirmedMessageStateProperty.get().name());
|
||||
builder.setPaymentSentMessageState(paymentSentMessageStateProperty.get().name());
|
||||
builder.setPaymentReceivedMessageState(paymentReceivedMessageStateProperty.get().name());
|
||||
|
||||
builder.setCurrentDate(currentDate);
|
||||
return builder.build();
|
||||
@ -270,6 +351,19 @@ public final class TradePeer implements PersistablePayload {
|
||||
tradePeer.setUnsignedPayoutTxHex(ProtoUtil.stringOrNullFromProto(proto.getUnsignedPayoutTxHex()));
|
||||
tradePeer.setPayoutTxFee(BigInteger.valueOf(proto.getPayoutTxFee()));
|
||||
tradePeer.setPayoutAmount(BigInteger.valueOf(proto.getPayoutAmount()));
|
||||
|
||||
String depositsConfirmedMessageStateString = ProtoUtil.stringOrNullFromProto(proto.getDepositsConfirmedMessageState());
|
||||
MessageState depositsConfirmedMessageState = ProtoUtil.enumFromProto(MessageState.class, depositsConfirmedMessageStateString);
|
||||
tradePeer.setDepositsConfirmedMessageState(depositsConfirmedMessageState);
|
||||
|
||||
String paymentSentMessageStateString = ProtoUtil.stringOrNullFromProto(proto.getPaymentSentMessageState());
|
||||
MessageState paymentSentMessageState = ProtoUtil.enumFromProto(MessageState.class, paymentSentMessageStateString);
|
||||
tradePeer.setPaymentSentMessageState(paymentSentMessageState);
|
||||
|
||||
String paymentReceivedMessageStateString = ProtoUtil.stringOrNullFromProto(proto.getPaymentReceivedMessageState());
|
||||
MessageState paymentReceivedMessageState = ProtoUtil.enumFromProto(MessageState.class, paymentReceivedMessageStateString);
|
||||
tradePeer.setPaymentReceivedMessageState(paymentReceivedMessageState);
|
||||
|
||||
return tradePeer;
|
||||
}
|
||||
}
|
||||
|
@ -96,6 +96,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
private static final String TIMEOUT_REACHED = "Timeout reached.";
|
||||
public static final int MAX_ATTEMPTS = 5; // max attempts to create txs and other wallet functions
|
||||
public static final long REPROCESS_DELAY_MS = 5000;
|
||||
public static final String LOG_HIGHLIGHT = "\u001B[36m"; // cyan
|
||||
|
||||
protected final ProcessModel processModel;
|
||||
protected final Trade trade;
|
||||
@ -106,6 +107,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
protected ErrorMessageHandler errorMessageHandler;
|
||||
|
||||
private boolean depositsConfirmedTasksCalled;
|
||||
private int reprocessPaymentSentMessageCount;
|
||||
private int reprocessPaymentReceivedMessageCount;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -124,12 +126,12 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
|
||||
protected void onTradeMessage(TradeMessage message, NodeAddress peerNodeAddress) {
|
||||
log.info("Received {} as TradeMessage from {} with tradeId {} and uid {}", message.getClass().getSimpleName(), peerNodeAddress, message.getOfferId(), message.getUid());
|
||||
ThreadUtils.execute(() -> handle(message, peerNodeAddress), trade.getId());
|
||||
handle(message, peerNodeAddress);
|
||||
}
|
||||
|
||||
protected void onMailboxMessage(TradeMessage message, NodeAddress peerNodeAddress) {
|
||||
log.info("Received {} as MailboxMessage from {} with tradeId {} and uid {}", message.getClass().getSimpleName(), peerNodeAddress, message.getOfferId(), message.getUid());
|
||||
ThreadUtils.execute(() -> handle(message, peerNodeAddress), trade.getId());
|
||||
handle(message, peerNodeAddress);
|
||||
}
|
||||
|
||||
private void handle(TradeMessage message, NodeAddress peerNodeAddress) {
|
||||
@ -163,7 +165,6 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
}
|
||||
} else if (networkEnvelope instanceof AckMessage) {
|
||||
onAckMessage((AckMessage) networkEnvelope, peer);
|
||||
trade.onAckMessage((AckMessage) networkEnvelope, peer); // notify trade listeners
|
||||
}
|
||||
}
|
||||
|
||||
@ -208,11 +209,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
onMailboxMessage(tradeMessage, mailboxMessage.getSenderNodeAddress());
|
||||
} else if (mailboxMessage instanceof AckMessage) {
|
||||
AckMessage ackMessage = (AckMessage) mailboxMessage;
|
||||
if (!trade.isCompleted()) {
|
||||
// We only apply the msg if we have not already completed the trade
|
||||
onAckMessage(ackMessage, mailboxMessage.getSenderNodeAddress());
|
||||
}
|
||||
// In any case we remove the msg
|
||||
onAckMessage(ackMessage, mailboxMessage.getSenderNodeAddress());
|
||||
processModel.getP2PService().getMailboxMessageService().removeMailboxMsg(ackMessage);
|
||||
log.info("Remove {} from the P2P network.", ackMessage.getClass().getSimpleName());
|
||||
}
|
||||
@ -240,7 +237,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
protected void onInitialized() {
|
||||
|
||||
// listen for direct messages unless completed
|
||||
if (!trade.isCompleted()) processModel.getP2PService().addDecryptedDirectMessageListener(this);
|
||||
if (!trade.isFinished()) processModel.getP2PService().addDecryptedDirectMessageListener(this);
|
||||
|
||||
// initialize trade
|
||||
synchronized (trade.getLock()) {
|
||||
@ -250,6 +247,9 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
MailboxMessageService mailboxMessageService = processModel.getP2PService().getMailboxMessageService();
|
||||
if (!trade.isCompleted()) mailboxMessageService.addDecryptedMailboxListener(this);
|
||||
handleMailboxCollection(mailboxMessageService.getMyDecryptedMailboxMessages());
|
||||
|
||||
// reprocess applicable messages
|
||||
trade.reprocessApplicableMessages();
|
||||
}
|
||||
|
||||
// send deposits confirmed message if applicable
|
||||
@ -279,24 +279,46 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
}, trade.getId());
|
||||
}
|
||||
|
||||
public void maybeReprocessPaymentReceivedMessage(boolean reprocessOnError) {
|
||||
public boolean needsToResendPaymentReceivedMessages() {
|
||||
return false; // seller protocol overrides
|
||||
}
|
||||
|
||||
public void maybeReprocessPaymentSentMessage(boolean reprocessOnError) {
|
||||
if (trade.isShutDownStarted()) return;
|
||||
ThreadUtils.execute(() -> {
|
||||
if (trade.isShutDownStarted()) return;
|
||||
synchronized (trade.getLock()) {
|
||||
|
||||
// skip if no need to reprocess
|
||||
if (trade.isSeller() || trade.getSeller().getPaymentReceivedMessage() == null || (trade.getState().ordinal() >= Trade.State.SELLER_SENT_PAYMENT_RECEIVED_MSG.ordinal() && trade.isPayoutPublished())) {
|
||||
if (trade.isShutDownStarted() || trade.isBuyer() || trade.getBuyer().getPaymentSentMessage() == null || trade.getState().ordinal() >= Trade.State.BUYER_SENT_PAYMENT_SENT_MSG.ordinal()) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.warn("Reprocessing payment received message for {} {}", trade.getClass().getSimpleName(), trade.getId());
|
||||
log.warn("Reprocessing PaymentSentMessage for {} {}", trade.getClass().getSimpleName(), trade.getId());
|
||||
handle(trade.getBuyer().getPaymentSentMessage(), trade.getBuyer().getPaymentSentMessage().getSenderNodeAddress(), reprocessOnError);
|
||||
}
|
||||
}, trade.getId());
|
||||
}
|
||||
|
||||
public void maybeReprocessPaymentReceivedMessage(boolean reprocessOnError) {
|
||||
if (trade.isShutDownStarted()) return;
|
||||
ThreadUtils.execute(() -> {
|
||||
if (trade.isShutDownStarted()) return;
|
||||
synchronized (trade.getLock()) {
|
||||
|
||||
// skip if no need to reprocess
|
||||
if (trade.isShutDownStarted() || trade.isSeller() || trade.getSeller().getPaymentReceivedMessage() == null || (trade.getState().ordinal() >= Trade.State.SELLER_SENT_PAYMENT_RECEIVED_MSG.ordinal() && trade.isPayoutPublished())) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.warn("Reprocessing PaymentReceivedMessage for {} {}", trade.getClass().getSimpleName(), trade.getId());
|
||||
handle(trade.getSeller().getPaymentReceivedMessage(), trade.getSeller().getPaymentReceivedMessage().getSenderNodeAddress(), reprocessOnError);
|
||||
}
|
||||
}, trade.getId());
|
||||
}
|
||||
|
||||
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender) {
|
||||
System.out.println(getClass().getSimpleName() + ".handleInitMultisigRequest() for " + trade.getClass().getSimpleName() + " " + trade.getShortId());
|
||||
log.info(LOG_HIGHLIGHT + "handleInitMultisigRequest() for " + trade.getClass().getSimpleName() + " " + trade.getShortId() + " from " + sender);
|
||||
trade.addInitProgressStep();
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade.getLock()) {
|
||||
@ -333,7 +355,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
}
|
||||
|
||||
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender) {
|
||||
System.out.println(getClass().getSimpleName() + ".handleSignContractRequest() for " + trade.getClass().getSimpleName() + " " + trade.getShortId());
|
||||
log.info(LOG_HIGHLIGHT + "handleSignContractRequest() for " + trade.getClass().getSimpleName() + " " + trade.getShortId() + " from " + sender);
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade.getLock()) {
|
||||
|
||||
@ -376,7 +398,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
}
|
||||
|
||||
public void handleSignContractResponse(SignContractResponse message, NodeAddress sender) {
|
||||
System.out.println(getClass().getSimpleName() + ".handleSignContractResponse() for " + trade.getClass().getSimpleName() + " " + trade.getShortId());
|
||||
log.info(LOG_HIGHLIGHT + "handleSignContractResponse() for " + trade.getClass().getSimpleName() + " " + trade.getShortId() + " from " + sender);
|
||||
trade.addInitProgressStep();
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade.getLock()) {
|
||||
@ -422,7 +444,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
}
|
||||
|
||||
public void handleDepositResponse(DepositResponse response, NodeAddress sender) {
|
||||
System.out.println(getClass().getSimpleName() + ".handleDepositResponse() for " + trade.getClass().getSimpleName() + " " + trade.getShortId());
|
||||
log.info(LOG_HIGHLIGHT + "handleDepositResponse() for " + trade.getClass().getSimpleName() + " " + trade.getShortId() + " from " + sender);
|
||||
trade.addInitProgressStep();
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade.getLock()) {
|
||||
@ -452,7 +474,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
}
|
||||
|
||||
public void handle(DepositsConfirmedMessage message, NodeAddress sender) {
|
||||
System.out.println(getClass().getSimpleName() + ".handle(DepositsConfirmedMessage) from " + sender + " for " + trade.getClass().getSimpleName() + " " + trade.getShortId());
|
||||
log.info(LOG_HIGHLIGHT + "handle(DepositsConfirmedMessage) for " + trade.getClass().getSimpleName() + " " + trade.getShortId() + " from " + sender);
|
||||
if (!trade.isInitialized() || trade.isShutDown()) return;
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade.getLock()) {
|
||||
@ -481,12 +503,33 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
|
||||
// received by seller and arbitrator
|
||||
protected void handle(PaymentSentMessage message, NodeAddress peer) {
|
||||
System.out.println(getClass().getSimpleName() + ".handle(PaymentSentMessage) for " + trade.getClass().getSimpleName() + " " + trade.getShortId());
|
||||
if (!trade.isInitialized() || trade.isShutDown()) return;
|
||||
handle(message, peer, true);
|
||||
}
|
||||
|
||||
// received by seller and arbitrator
|
||||
protected void handle(PaymentSentMessage message, NodeAddress peer, boolean reprocessOnError) {
|
||||
log.info(LOG_HIGHLIGHT + "handle(PaymentSentMessage) for " + trade.getClass().getSimpleName() + " " + trade.getShortId() + " from " + peer);
|
||||
|
||||
// ignore if not seller or arbitrator
|
||||
if (!(trade instanceof SellerTrade || trade instanceof ArbitratorTrade)) {
|
||||
log.warn("Ignoring PaymentSentMessage since not seller or arbitrator");
|
||||
return;
|
||||
}
|
||||
|
||||
// validate signature
|
||||
try {
|
||||
HavenoUtils.verifyPaymentSentMessage(trade, message);
|
||||
} catch (Throwable t) {
|
||||
log.warn("Ignoring PaymentSentMessage with invalid signature for {} {}, error={}", trade.getClass().getSimpleName(), trade.getId(), t.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
// save message for reprocessing
|
||||
trade.getBuyer().setPaymentSentMessage(message);
|
||||
trade.requestPersistence();
|
||||
|
||||
// process message on trade thread
|
||||
if (!trade.isInitialized() || trade.isShutDownStarted()) return;
|
||||
ThreadUtils.execute(() -> {
|
||||
// We are more tolerant with expected phase and allow also DEPOSITS_PUBLISHED as it can be the case
|
||||
// that the wallet is still syncing and so the DEPOSITS_CONFIRMED state to yet triggered when we received
|
||||
@ -494,7 +537,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
// TODO A better fix would be to add a listener for the wallet sync state and process
|
||||
// the mailbox msg once wallet is ready and trade state set.
|
||||
synchronized (trade.getLock()) {
|
||||
if (!trade.isInitialized() || trade.isShutDown()) return;
|
||||
if (!trade.isInitialized() || trade.isShutDownStarted()) return;
|
||||
if (trade.getPhase().ordinal() >= Trade.Phase.PAYMENT_SENT.ordinal()) {
|
||||
log.warn("Received another PaymentSentMessage which was already processed for {} {}, ACKing", trade.getClass().getSimpleName(), trade.getId());
|
||||
handleTaskRunnerSuccess(peer, message);
|
||||
@ -509,7 +552,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
return;
|
||||
}
|
||||
latchTrade();
|
||||
expect(anyPhase(Trade.Phase.DEPOSITS_CONFIRMED, Trade.Phase.DEPOSITS_UNLOCKED)
|
||||
expect(anyPhase()
|
||||
.with(message)
|
||||
.from(peer))
|
||||
.setup(tasks(
|
||||
@ -521,7 +564,19 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
handleTaskRunnerSuccess(peer, message);
|
||||
},
|
||||
(errorMessage) -> {
|
||||
handleTaskRunnerFault(peer, message, errorMessage);
|
||||
log.warn("Error processing payment sent message: " + errorMessage);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
// schedule to reprocess message unless deleted
|
||||
if (trade.getBuyer().getPaymentSentMessage() != null) {
|
||||
UserThread.runAfter(() -> {
|
||||
reprocessPaymentSentMessageCount++;
|
||||
maybeReprocessPaymentSentMessage(reprocessOnError);
|
||||
}, trade.getReprocessDelayInSeconds(reprocessPaymentSentMessageCount));
|
||||
} else {
|
||||
handleTaskRunnerFault(peer, message, errorMessage); // otherwise send nack
|
||||
}
|
||||
unlatchTrade();
|
||||
})))
|
||||
.executeTasks(true);
|
||||
awaitTradeLatch();
|
||||
@ -535,15 +590,31 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
}
|
||||
|
||||
private void handle(PaymentReceivedMessage message, NodeAddress peer, boolean reprocessOnError) {
|
||||
System.out.println(getClass().getSimpleName() + ".handle(PaymentReceivedMessage) for " + trade.getClass().getSimpleName() + " " + trade.getShortId());
|
||||
if (!trade.isInitialized() || trade.isShutDown()) return;
|
||||
log.info(LOG_HIGHLIGHT + "handle(PaymentReceivedMessage) for " + trade.getClass().getSimpleName() + " " + trade.getShortId() + " from " + peer);
|
||||
|
||||
// ignore if not buyer or arbitrator
|
||||
if (!(trade instanceof BuyerTrade || trade instanceof ArbitratorTrade)) {
|
||||
log.warn("Ignoring PaymentReceivedMessage since not buyer or arbitrator");
|
||||
return;
|
||||
}
|
||||
|
||||
// validate signature
|
||||
try {
|
||||
HavenoUtils.verifyPaymentReceivedMessage(trade, message);
|
||||
} catch (Throwable t) {
|
||||
log.warn("Ignoring PaymentReceivedMessage with invalid signature for {} {}, error={}", trade.getClass().getSimpleName(), trade.getId(), t.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
// save message for reprocessing
|
||||
trade.getSeller().setPaymentReceivedMessage(message);
|
||||
trade.requestPersistence();
|
||||
|
||||
// process message on trade thread
|
||||
if (!trade.isInitialized() || trade.isShutDownStarted()) return;
|
||||
ThreadUtils.execute(() -> {
|
||||
if (!(trade instanceof BuyerTrade || trade instanceof ArbitratorTrade)) {
|
||||
log.warn("Ignoring PaymentReceivedMessage since not buyer or arbitrator");
|
||||
return;
|
||||
}
|
||||
synchronized (trade.getLock()) {
|
||||
if (!trade.isInitialized() || trade.isShutDown()) return;
|
||||
if (!trade.isInitialized() || trade.isShutDownStarted()) return;
|
||||
latchTrade();
|
||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
processModel.setTradeMessage(message);
|
||||
@ -649,48 +720,84 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
|
||||
private void onAckMessage(AckMessage ackMessage, NodeAddress sender) {
|
||||
|
||||
// handle ack for PaymentSentMessage, which automatically re-sends if not ACKed in a certain time
|
||||
if (ackMessage.getSourceMsgClassName().equals(PaymentSentMessage.class.getSimpleName())) {
|
||||
if (trade.getTradePeer(sender) == trade.getSeller()) {
|
||||
processModel.setPaymentSentAckMessage(ackMessage);
|
||||
trade.setStateIfValidTransitionTo(Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
} else if (trade.getTradePeer(sender) == trade.getArbitrator()) {
|
||||
processModel.setPaymentSentAckMessageArbitrator(ackMessage);
|
||||
} else if (!ackMessage.isSuccess()) {
|
||||
String err = "Received AckMessage with error state for " + ackMessage.getSourceMsgClassName() + " from "+ sender + " with tradeId " + trade.getId() + " and errorMessage=" + ackMessage.getErrorMessage();
|
||||
log.warn(err);
|
||||
return; // log error and ignore nack if not seller
|
||||
}
|
||||
// ignore if trade is completely finished
|
||||
if (trade.isFinished()) return;
|
||||
|
||||
// get trade peer
|
||||
TradePeer peer = trade.getTradePeer(sender);
|
||||
if (peer == null) {
|
||||
if (ackMessage.getSourceUid().equals(HavenoUtils.getDeterministicId(trade, DepositsConfirmedMessage.class, trade.getArbitrator().getNodeAddress()))) peer = trade.getArbitrator();
|
||||
else if (ackMessage.getSourceUid().equals(HavenoUtils.getDeterministicId(trade, DepositsConfirmedMessage.class, trade.getMaker().getNodeAddress()))) peer = trade.getMaker();
|
||||
else if (ackMessage.getSourceUid().equals(HavenoUtils.getDeterministicId(trade, DepositsConfirmedMessage.class, trade.getTaker().getNodeAddress()))) peer = trade.getTaker();
|
||||
}
|
||||
if (peer == null) {
|
||||
if (ackMessage.isSuccess()) log.warn("Received AckMessage from unknown peer for {}, sender={}, trade={} {}, messageUid={}", ackMessage.getSourceMsgClassName(), sender, trade.getClass().getSimpleName(), trade.getId(), ackMessage.getSourceUid());
|
||||
else log.warn("Received AckMessage with error state from unknown peer for {}, sender={}, trade={} {}, messageUid={}, errorMessage={}", ackMessage.getSourceMsgClassName(), sender, trade.getClass().getSimpleName(), trade.getId(), ackMessage.getSourceUid(), ackMessage.getErrorMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
if (ackMessage.isSuccess()) {
|
||||
log.info("Received AckMessage for {}, sender={}, trade={} {}, messageUid={}", ackMessage.getSourceMsgClassName(), sender, trade.getClass().getSimpleName(), trade.getId(), ackMessage.getSourceUid());
|
||||
// update sender's node address
|
||||
if (!peer.getNodeAddress().equals(sender)) {
|
||||
log.info("Updating peer's node address from {} to {} using ACK message to {}", peer.getNodeAddress(), sender, ackMessage.getSourceMsgClassName());
|
||||
peer.setNodeAddress(sender);
|
||||
}
|
||||
|
||||
// handle ack for DepositsConfirmedMessage, which automatically re-sends if not ACKed in a certain time
|
||||
if (ackMessage.getSourceMsgClassName().equals(DepositsConfirmedMessage.class.getSimpleName())) {
|
||||
TradePeer peer = trade.getTradePeer(sender);
|
||||
if (peer == null) {
|
||||
|
||||
// get the applicable peer based on the sourceUid
|
||||
if (ackMessage.getSourceUid().equals(HavenoUtils.getDeterministicId(trade, DepositsConfirmedMessage.class, trade.getArbitrator().getNodeAddress()))) peer = trade.getArbitrator();
|
||||
else if (ackMessage.getSourceUid().equals(HavenoUtils.getDeterministicId(trade, DepositsConfirmedMessage.class, trade.getMaker().getNodeAddress()))) peer = trade.getMaker();
|
||||
else if (ackMessage.getSourceUid().equals(HavenoUtils.getDeterministicId(trade, DepositsConfirmedMessage.class, trade.getTaker().getNodeAddress()))) peer = trade.getTaker();
|
||||
}
|
||||
if (peer == null) log.warn("Received AckMesage for DepositsConfirmedMessage for unknown peer: " + sender);
|
||||
else peer.setDepositsConfirmedMessageAcked(true);
|
||||
}
|
||||
} else {
|
||||
log.warn("Received AckMessage with error state for {}, sender={}, trade={} {}, messageUid={}, errorMessage={}", ackMessage.getSourceMsgClassName(), sender, trade.getClass().getSimpleName(), trade.getId(), ackMessage.getSourceUid(), ackMessage.getErrorMessage());
|
||||
|
||||
// set trade state on deposit request nack
|
||||
if (ackMessage.getSourceMsgClassName().equals(DepositRequest.class.getSimpleName())) {
|
||||
// set trade state on deposit request nack
|
||||
if (ackMessage.getSourceMsgClassName().equals(DepositRequest.class.getSimpleName())) {
|
||||
if (!ackMessage.isSuccess()) {
|
||||
trade.setStateIfValidTransitionTo(Trade.State.PUBLISH_DEPOSIT_TX_REQUEST_FAILED);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
}
|
||||
|
||||
// handle ack for DepositsConfirmedMessage, which automatically re-sends if not ACKed in a certain time
|
||||
if (ackMessage.getSourceMsgClassName().equals(DepositsConfirmedMessage.class.getSimpleName())) {
|
||||
peer.setDepositsConfirmedAckMessage(ackMessage);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
// handle ack for PaymentSentMessage, which automatically re-sends if not ACKed in a certain time
|
||||
if (ackMessage.getSourceMsgClassName().equals(PaymentSentMessage.class.getSimpleName())) {
|
||||
if (trade.getTradePeer(sender) == trade.getSeller()) {
|
||||
trade.getSeller().setPaymentSentAckMessage(ackMessage);
|
||||
if (ackMessage.isSuccess()) trade.setStateIfValidTransitionTo(Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG);
|
||||
else trade.setState(Trade.State.BUYER_SEND_FAILED_PAYMENT_SENT_MSG);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
} else if (trade.getTradePeer(sender) == trade.getArbitrator()) {
|
||||
trade.getArbitrator().setPaymentSentAckMessage(ackMessage);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
} else {
|
||||
log.warn("Received AckMessage from unexpected peer for {}, sender={}, trade={} {}, messageUid={}, success={}, errorMsg={}", ackMessage.getSourceMsgClassName(), sender, trade.getClass().getSimpleName(), trade.getId(), ackMessage.getSourceUid(), ackMessage.isSuccess(), ackMessage.getErrorMessage());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// handle ack for PaymentReceivedMessage, which automatically re-sends if not ACKed in a certain time
|
||||
if (ackMessage.getSourceMsgClassName().equals(PaymentReceivedMessage.class.getSimpleName())) {
|
||||
if (trade.getTradePeer(sender) == trade.getBuyer()) {
|
||||
trade.getBuyer().setPaymentReceivedAckMessage(ackMessage);
|
||||
if (ackMessage.isSuccess()) trade.setStateIfValidTransitionTo(Trade.State.BUYER_RECEIVED_PAYMENT_RECEIVED_MSG);
|
||||
else trade.setState(Trade.State.SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
} else if (trade.getTradePeer(sender) == trade.getArbitrator()) {
|
||||
trade.getArbitrator().setPaymentReceivedAckMessage(ackMessage);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
} else {
|
||||
log.warn("Received AckMessage from unexpected peer for {}, sender={}, trade={} {}, messageUid={}, success={}, errorMsg={}", ackMessage.getSourceMsgClassName(), sender, trade.getClass().getSimpleName(), trade.getId(), ackMessage.getSourceUid(), ackMessage.isSuccess(), ackMessage.getErrorMessage());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// generic handling
|
||||
if (ackMessage.isSuccess()) {
|
||||
log.info("Received AckMessage for {}, sender={}, trade={} {}, messageUid={}", ackMessage.getSourceMsgClassName(), sender, trade.getClass().getSimpleName(), trade.getId(), ackMessage.getSourceUid());
|
||||
} else {
|
||||
log.warn("Received AckMessage with error state for {}, sender={}, trade={} {}, messageUid={}, errorMessage={}", ackMessage.getSourceMsgClassName(), sender, trade.getClass().getSimpleName(), trade.getId(), ackMessage.getSourceUid(), ackMessage.getErrorMessage());
|
||||
handleError(ackMessage.getErrorMessage());
|
||||
}
|
||||
|
||||
// notify trade listeners
|
||||
trade.onAckMessage(ackMessage, sender);
|
||||
}
|
||||
|
||||
protected void sendAckMessage(NodeAddress peer, TradeMessage message, boolean result, @Nullable String errorMessage) {
|
||||
|
@ -44,7 +44,6 @@ import java.util.UUID;
|
||||
@Slf4j
|
||||
public class ArbitratorProcessDepositRequest extends TradeTask {
|
||||
|
||||
private Throwable error;
|
||||
private boolean depositResponsesSent;
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
@ -68,7 +67,7 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
|
||||
processDepositRequest();
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
this.error = t;
|
||||
trade.getProcessModel().error = t;
|
||||
log.error("Error processing deposit request for trade {}: {}\n", trade.getId(), t.getMessage(), t);
|
||||
trade.setStateIfValidTransitionTo(Trade.State.PUBLISH_DEPOSIT_TX_REQUEST_FAILED);
|
||||
failed(t);
|
||||
@ -188,7 +187,7 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
|
||||
trade.stateProperty().addListener((obs, oldState, newState) -> {
|
||||
if (oldState == newState) return;
|
||||
if (newState == Trade.State.PUBLISH_DEPOSIT_TX_REQUEST_FAILED) {
|
||||
sendDepositResponsesOnce(error == null ? "Arbitrator failed to publish deposit txs within timeout for trade " + trade.getId() : error.getMessage());
|
||||
sendDepositResponsesOnce(trade.getProcessModel().error == null ? "Arbitrator failed to publish deposit txs within timeout for trade " + trade.getId() : trade.getProcessModel().error.getMessage());
|
||||
} else if (newState.ordinal() >= Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS.ordinal()) {
|
||||
sendDepositResponsesOnce(null);
|
||||
}
|
||||
@ -230,7 +229,7 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
|
||||
}
|
||||
|
||||
private void sendDepositResponse(NodeAddress nodeAddress, PubKeyRing pubKeyRing, DepositResponse response) {
|
||||
log.info("Sending deposit response to trader={}; offerId={}, error={}", nodeAddress, trade.getId(), error);
|
||||
log.info("Sending deposit response to trader={}; offerId={}, error={}", nodeAddress, trade.getId(), trade.getProcessModel().error);
|
||||
processModel.getP2PService().sendEncryptedDirectMessage(nodeAddress, pubKeyRing, response, new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
|
@ -66,7 +66,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
public abstract class BuyerSendPaymentSentMessage extends SendMailboxMessageTask {
|
||||
private ChangeListener<MessageState> listener;
|
||||
private Timer timer;
|
||||
private static final int MAX_RESEND_ATTEMPTS = 10;
|
||||
private static final int MAX_RESEND_ATTEMPTS = 20;
|
||||
private int delayInMin = 10;
|
||||
private int resendCounter = 0;
|
||||
|
||||
@ -142,26 +142,26 @@ public abstract class BuyerSendPaymentSentMessage extends SendMailboxMessageTask
|
||||
|
||||
@Override
|
||||
protected void setStateSent() {
|
||||
if (trade.getState().ordinal() < Trade.State.BUYER_SENT_PAYMENT_SENT_MSG.ordinal()) trade.setStateIfValidTransitionTo(Trade.State.BUYER_SENT_PAYMENT_SENT_MSG);
|
||||
getReceiver().setPaymentSentMessageState(MessageState.SENT);
|
||||
tryToSendAgainLater();
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateArrived() {
|
||||
trade.setStateIfValidTransitionTo(Trade.State.BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG);
|
||||
getReceiver().setPaymentSentMessageState(MessageState.ARRIVED);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateStoredInMailbox() {
|
||||
trade.setStateIfValidTransitionTo(Trade.State.BUYER_STORED_IN_MAILBOX_PAYMENT_SENT_MSG);
|
||||
getReceiver().setPaymentSentMessageState(MessageState.STORED_IN_MAILBOX);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateFault() {
|
||||
trade.setStateIfValidTransitionTo(Trade.State.BUYER_SEND_FAILED_PAYMENT_SENT_MSG);
|
||||
getReceiver().setPaymentSentMessageState(MessageState.FAILED);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@ -170,7 +170,7 @@ public abstract class BuyerSendPaymentSentMessage extends SendMailboxMessageTask
|
||||
timer.stop();
|
||||
}
|
||||
if (listener != null) {
|
||||
processModel.getPaymentSentMessageStateProperty().removeListener(listener);
|
||||
trade.getSeller().getPaymentReceivedMessageStateProperty().removeListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
@ -185,7 +185,6 @@ public abstract class BuyerSendPaymentSentMessage extends SendMailboxMessageTask
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("We will send the message again to the peer after a delay of {} min.", delayInMin);
|
||||
if (timer != null) {
|
||||
timer.stop();
|
||||
}
|
||||
@ -194,21 +193,30 @@ public abstract class BuyerSendPaymentSentMessage extends SendMailboxMessageTask
|
||||
|
||||
if (resendCounter == 0) {
|
||||
listener = (observable, oldValue, newValue) -> onMessageStateChange(newValue);
|
||||
processModel.getPaymentSentMessageStateProperty().addListener(listener);
|
||||
onMessageStateChange(processModel.getPaymentSentMessageStateProperty().get());
|
||||
getReceiver().getPaymentSentMessageStateProperty().addListener(listener);
|
||||
onMessageStateChange(getReceiver().getPaymentSentMessageStateProperty().get());
|
||||
}
|
||||
|
||||
delayInMin = delayInMin * 2;
|
||||
// first re-send is after 2 minutes, then increase the delay exponentially
|
||||
if (resendCounter == 0) {
|
||||
int shortDelay = 2;
|
||||
log.info("We will send the message again to the peer after a delay of {} min.", shortDelay);
|
||||
timer = UserThread.runAfter(this::run, shortDelay, TimeUnit.MINUTES);
|
||||
} else {
|
||||
log.info("We will send the message again to the peer after a delay of {} min.", delayInMin);
|
||||
timer = UserThread.runAfter(this::run, delayInMin, TimeUnit.MINUTES);
|
||||
delayInMin = (int) ((double) delayInMin * 1.5);
|
||||
}
|
||||
resendCounter++;
|
||||
}
|
||||
|
||||
private void onMessageStateChange(MessageState newValue) {
|
||||
if (newValue == MessageState.ACKNOWLEDGED) {
|
||||
trade.setStateIfValidTransitionTo(Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
if (isAckedByReceiver()) {
|
||||
cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract boolean isAckedByReceiver();
|
||||
protected boolean isAckedByReceiver() {
|
||||
return getReceiver().isPaymentSentMessageAcked();
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,6 @@
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.network.MessageState;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.protocol.TradePeer;
|
||||
import lombok.EqualsAndHashCode;
|
||||
@ -39,26 +38,7 @@ public class BuyerSendPaymentSentMessageToArbitrator extends BuyerSendPaymentSen
|
||||
|
||||
@Override
|
||||
protected void setStateSent() {
|
||||
super.setStateSent();
|
||||
complete(); // don't wait for message to arbitrator
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateFault() {
|
||||
// state only updated on seller message
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateStoredInMailbox() {
|
||||
// state only updated on seller message
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateArrived() {
|
||||
// state only updated on seller message
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isAckedByReceiver() {
|
||||
return trade.getProcessModel().getPaymentSentMessageStatePropertyArbitrator().get() == MessageState.ACKNOWLEDGED;
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,6 @@
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.network.MessageState;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.messages.TradeMessage;
|
||||
import haveno.core.trade.protocol.TradePeer;
|
||||
@ -40,25 +39,25 @@ public class BuyerSendPaymentSentMessageToSeller extends BuyerSendPaymentSentMes
|
||||
|
||||
@Override
|
||||
protected void setStateSent() {
|
||||
trade.getProcessModel().setPaymentSentMessageState(MessageState.SENT);
|
||||
if (trade.getState().ordinal() < Trade.State.BUYER_SENT_PAYMENT_SENT_MSG.ordinal()) trade.setStateIfValidTransitionTo(Trade.State.BUYER_SENT_PAYMENT_SENT_MSG);
|
||||
super.setStateSent();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateArrived() {
|
||||
trade.getProcessModel().setPaymentSentMessageState(MessageState.ARRIVED);
|
||||
trade.setStateIfValidTransitionTo(Trade.State.BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG);
|
||||
super.setStateArrived();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateStoredInMailbox() {
|
||||
trade.getProcessModel().setPaymentSentMessageState(MessageState.STORED_IN_MAILBOX);
|
||||
trade.setStateIfValidTransitionTo(Trade.State.BUYER_STORED_IN_MAILBOX_PAYMENT_SENT_MSG);
|
||||
super.setStateStoredInMailbox();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateFault() {
|
||||
trade.getProcessModel().setPaymentSentMessageState(MessageState.FAILED);
|
||||
trade.setStateIfValidTransitionTo(Trade.State.BUYER_SEND_FAILED_PAYMENT_SENT_MSG);
|
||||
super.setStateFault();
|
||||
}
|
||||
|
||||
@ -69,9 +68,4 @@ public class BuyerSendPaymentSentMessageToSeller extends BuyerSendPaymentSentMes
|
||||
appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
|
||||
complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isAckedByReceiver() {
|
||||
return trade.getState().ordinal() >= Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG.ordinal();
|
||||
}
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ public class MaybeSendSignContractRequest extends TradeTask {
|
||||
Integer subaddressIndex = null;
|
||||
boolean reserveExactAmount = false;
|
||||
if (trade instanceof MakerTrade) {
|
||||
reserveExactAmount = processModel.getOpenOfferManager().getOpenOfferById(trade.getId()).get().isReserveExactAmount();
|
||||
reserveExactAmount = processModel.getOpenOfferManager().getOpenOffer(trade.getId()).get().isReserveExactAmount();
|
||||
if (reserveExactAmount) subaddressIndex = model.getXmrWalletService().getAddressEntry(trade.getId(), XmrAddressEntry.Context.OFFER_FUNDING).get().getSubaddressIndex();
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,6 @@
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
|
||||
import haveno.common.ThreadUtils;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.messages.DepositsConfirmedMessage;
|
||||
@ -63,17 +62,7 @@ public class ProcessDepositsConfirmedMessage extends TradeTask {
|
||||
// update multisig hex
|
||||
if (sender.getUpdatedMultisigHex() == null) {
|
||||
sender.setUpdatedMultisigHex(request.getUpdatedMultisigHex());
|
||||
|
||||
// try to import multisig hex (retry later)
|
||||
if (!trade.isPayoutPublished()) {
|
||||
ThreadUtils.submitToPool(() -> {
|
||||
try {
|
||||
trade.importMultisigHex();
|
||||
} catch (Exception e) {
|
||||
log.warn("Error importing multisig hex on deposits confirmed for trade " + trade.getId() + ": " + e.getMessage() + "\n", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
trade.scheduleImportMultisigHex();
|
||||
}
|
||||
|
||||
// persist
|
||||
|
@ -72,6 +72,7 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
|
||||
// update to the latest peer address of our peer if message is correct
|
||||
trade.getSeller().setNodeAddress(processModel.getTempTradePeerNodeAddress());
|
||||
if (trade.getSeller().getNodeAddress().equals(trade.getBuyer().getNodeAddress())) trade.getBuyer().setNodeAddress(null); // tests can reuse addresses
|
||||
trade.requestPersistence();
|
||||
|
||||
// ack and complete if already processed
|
||||
if (trade.getPhase().ordinal() >= Trade.Phase.PAYMENT_RECEIVED.ordinal() && trade.isPayoutPublished()) {
|
||||
@ -80,8 +81,13 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
|
||||
return;
|
||||
}
|
||||
|
||||
// save message for reprocessing
|
||||
trade.getSeller().setPaymentReceivedMessage(message);
|
||||
// cannot process until wallet sees deposits unlocked
|
||||
if (!trade.isDepositsUnlocked()) {
|
||||
trade.syncAndPollWallet();
|
||||
if (!trade.isDepositsUnlocked()) {
|
||||
throw new RuntimeException("Cannot process PaymentReceivedMessage until wallet sees that deposits are unlocked for " + trade.getClass().getSimpleName() + " " + trade.getId());
|
||||
}
|
||||
}
|
||||
|
||||
// set state
|
||||
trade.getSeller().setUpdatedMultisigHex(message.getUpdatedMultisigHex());
|
||||
@ -128,14 +134,6 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
|
||||
|
||||
private void processPayoutTx(PaymentReceivedMessage message) {
|
||||
|
||||
// adapt from 1.0.6 to 1.0.7 which changes field usage
|
||||
// TODO: remove after future updates to allow old trades to clear
|
||||
if (trade.getPayoutTxHex() != null && trade.getBuyer().getPaymentSentMessage() != null && trade.getPayoutTxHex().equals(trade.getBuyer().getPaymentSentMessage().getPayoutTxHex())) {
|
||||
log.warn("Nullifying payout tx hex after 1.0.7 update {} {}", trade.getClass().getSimpleName(), trade.getShortId());
|
||||
if (trade instanceof BuyerTrade) trade.getSelf().setUnsignedPayoutTxHex(trade.getPayoutTxHex());
|
||||
trade.setPayoutTxHex(null);
|
||||
}
|
||||
|
||||
// update wallet
|
||||
trade.importMultisigHex();
|
||||
trade.syncAndPollWallet();
|
||||
|
@ -46,9 +46,17 @@ public class ProcessPaymentSentMessage extends TradeTask {
|
||||
|
||||
// update latest peer address
|
||||
trade.getBuyer().setNodeAddress(processModel.getTempTradePeerNodeAddress());
|
||||
trade.requestPersistence();
|
||||
|
||||
// cannot process until wallet sees deposits confirmed
|
||||
if (!trade.isDepositsConfirmed()) {
|
||||
trade.syncAndPollWallet();
|
||||
if (!trade.isDepositsConfirmed()) {
|
||||
throw new RuntimeException("Cannot process PaymentSentMessage until wallet sees that deposits are confirmed for " + trade.getClass().getSimpleName() + " " + trade.getId());
|
||||
}
|
||||
}
|
||||
|
||||
// update state from message
|
||||
trade.getBuyer().setPaymentSentMessage(message);
|
||||
trade.getBuyer().setUpdatedMultisigHex(message.getUpdatedMultisigHex());
|
||||
trade.getSeller().setAccountAgeWitness(message.getSellerAccountAgeWitness());
|
||||
String counterCurrencyTxId = message.getCounterCurrencyTxId();
|
||||
|
@ -43,13 +43,6 @@ public class SellerPreparePaymentReceivedMessage extends TradeTask {
|
||||
// handle first time preparation
|
||||
if (trade.getArbitrator().getPaymentReceivedMessage() == null) {
|
||||
|
||||
// adapt from 1.0.6 to 1.0.7 which changes field usage
|
||||
// TODO: remove after future updates to allow old trades to clear
|
||||
if (trade.getPayoutTxHex() != null && trade.getPayoutTxHex().equals(trade.getBuyer().getPaymentSentMessage().getPayoutTxHex())) {
|
||||
log.warn("Nullifying payout tx hex after 1.0.7 update {} {}", trade.getClass().getSimpleName(), trade.getShortId());
|
||||
trade.setPayoutTxHex(null);
|
||||
}
|
||||
|
||||
// synchronize on lock for wallet operations
|
||||
synchronized (trade.getWalletLock()) {
|
||||
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||
|
@ -35,11 +35,15 @@
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
|
||||
import haveno.common.Timer;
|
||||
import haveno.common.UserThread;
|
||||
import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.crypto.Sig;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.account.sign.SignedWitness;
|
||||
import haveno.core.account.witness.AccountAgeWitnessService;
|
||||
import haveno.core.network.MessageState;
|
||||
import haveno.core.trade.HavenoUtils;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.messages.PaymentReceivedMessage;
|
||||
@ -47,15 +51,23 @@ import haveno.core.trade.messages.TradeMailboxMessage;
|
||||
import haveno.core.trade.protocol.TradePeer;
|
||||
import haveno.core.util.JsonUtil;
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public abstract class SellerSendPaymentReceivedMessage extends SendMailboxMessageTask {
|
||||
SignedWitness signedWitness = null;
|
||||
private SignedWitness signedWitness = null;
|
||||
private ChangeListener<MessageState> listener;
|
||||
private Timer timer;
|
||||
private static final int MAX_RESEND_ATTEMPTS = 20;
|
||||
private int delayInMin = 10;
|
||||
private int resendCounter = 0;
|
||||
|
||||
public SellerSendPaymentReceivedMessage(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
@ -77,6 +89,13 @@ public abstract class SellerSendPaymentReceivedMessage extends SendMailboxMessag
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
// skip if already received
|
||||
if (isReceived()) {
|
||||
if (!isCompleted()) complete();
|
||||
return;
|
||||
}
|
||||
|
||||
super.run();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
@ -134,29 +153,85 @@ public abstract class SellerSendPaymentReceivedMessage extends SendMailboxMessag
|
||||
|
||||
@Override
|
||||
protected void setStateSent() {
|
||||
trade.advanceState(Trade.State.SELLER_SENT_PAYMENT_RECEIVED_MSG);
|
||||
log.info("{} sent: tradeId={} at peer {} SignedWitness {}", getClass().getSimpleName(), trade.getId(), getReceiverNodeAddress(), signedWitness);
|
||||
getReceiver().setPaymentReceivedMessageState(MessageState.SENT);
|
||||
tryToSendAgainLater();
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateFault() {
|
||||
trade.advanceState(Trade.State.SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG);
|
||||
log.error("{} failed: tradeId={} at peer {} SignedWitness {}", getClass().getSimpleName(), trade.getId(), getReceiverNodeAddress(), signedWitness);
|
||||
getReceiver().setPaymentReceivedMessageState(MessageState.FAILED);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateStoredInMailbox() {
|
||||
trade.advanceState(Trade.State.SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG);
|
||||
log.info("{} stored in mailbox: tradeId={} at peer {} SignedWitness {}", getClass().getSimpleName(), trade.getId(), getReceiverNodeAddress(), signedWitness);
|
||||
getReceiver().setPaymentReceivedMessageState(MessageState.STORED_IN_MAILBOX);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateArrived() {
|
||||
trade.advanceState(Trade.State.SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG);
|
||||
log.info("{} arrived: tradeId={} at peer {} SignedWitness {}", getClass().getSimpleName(), trade.getId(), getReceiverNodeAddress(), signedWitness);
|
||||
getReceiver().setPaymentReceivedMessageState(MessageState.ARRIVED);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
private void cleanup() {
|
||||
if (timer != null) {
|
||||
timer.stop();
|
||||
}
|
||||
if (listener != null) {
|
||||
trade.getBuyer().getPaymentReceivedMessageStateProperty().removeListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
private void tryToSendAgainLater() {
|
||||
|
||||
// skip if already received
|
||||
if (isReceived()) return;
|
||||
|
||||
if (resendCounter >= MAX_RESEND_ATTEMPTS) {
|
||||
cleanup();
|
||||
log.warn("We never received an ACK message when sending the PaymentReceivedMessage to the peer. We stop trying to send the message.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (timer != null) {
|
||||
timer.stop();
|
||||
}
|
||||
|
||||
timer = UserThread.runAfter(this::run, delayInMin, TimeUnit.MINUTES);
|
||||
|
||||
if (resendCounter == 0) {
|
||||
listener = (observable, oldValue, newValue) -> onMessageStateChange(newValue);
|
||||
getReceiver().getPaymentReceivedMessageStateProperty().addListener(listener);
|
||||
onMessageStateChange(getReceiver().getPaymentReceivedMessageStateProperty().get());
|
||||
}
|
||||
|
||||
// first re-send is after 2 minutes, then increase the delay exponentially
|
||||
if (resendCounter == 0) {
|
||||
int shortDelay = 2;
|
||||
log.info("We will send the message again to the peer after a delay of {} min.", shortDelay);
|
||||
timer = UserThread.runAfter(this::run, shortDelay, TimeUnit.MINUTES);
|
||||
} else {
|
||||
log.info("We will send the message again to the peer after a delay of {} min.", delayInMin);
|
||||
timer = UserThread.runAfter(this::run, delayInMin, TimeUnit.MINUTES);
|
||||
delayInMin = (int) ((double) delayInMin * 1.5);
|
||||
}
|
||||
resendCounter++;
|
||||
}
|
||||
|
||||
private void onMessageStateChange(MessageState newValue) {
|
||||
if (isReceived()) {
|
||||
cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isReceived() {
|
||||
return getReceiver().isPaymentReceivedMessageReceived();
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,30 @@ public class SellerSendPaymentReceivedMessageToBuyer extends SellerSendPaymentRe
|
||||
return trade.getBuyer();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateSent() {
|
||||
trade.advanceState(Trade.State.SELLER_SENT_PAYMENT_RECEIVED_MSG);
|
||||
super.setStateSent();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateFault() {
|
||||
trade.advanceState(Trade.State.SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG);
|
||||
super.setStateFault();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateStoredInMailbox() {
|
||||
trade.advanceState(Trade.State.SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG);
|
||||
super.setStateStoredInMailbox();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateArrived() {
|
||||
trade.advanceState(Trade.State.SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG);
|
||||
super.setStateArrived();
|
||||
}
|
||||
|
||||
// continue execution on fault so payment received message is sent to arbitrator
|
||||
@Override
|
||||
protected void onFault(String errorMessage, TradeMessage message) {
|
||||
|
@ -23,6 +23,7 @@ import haveno.common.Timer;
|
||||
import haveno.common.UserThread;
|
||||
import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.network.MessageState;
|
||||
import haveno.core.trade.HavenoUtils;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.messages.DepositsConfirmedMessage;
|
||||
@ -37,7 +38,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
@Slf4j
|
||||
public abstract class SendDepositsConfirmedMessage extends SendMailboxMessageTask {
|
||||
private Timer timer;
|
||||
private static final int MAX_RESEND_ATTEMPTS = 10;
|
||||
private static final int MAX_RESEND_ATTEMPTS = 20;
|
||||
private int delayInMin = 10;
|
||||
private int resendCounter = 0;
|
||||
|
||||
@ -52,8 +53,8 @@ public abstract class SendDepositsConfirmedMessage extends SendMailboxMessageTas
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
// skip if already acked by receiver
|
||||
if (isAckedByReceiver()) {
|
||||
// skip if already acked or payout published
|
||||
if (isAckedByReceiver() || trade.isPayoutPublished()) {
|
||||
if (!isCompleted()) complete();
|
||||
return;
|
||||
}
|
||||
@ -64,11 +65,17 @@ public abstract class SendDepositsConfirmedMessage extends SendMailboxMessageTas
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected abstract NodeAddress getReceiverNodeAddress();
|
||||
protected abstract TradePeer getReceiver();
|
||||
|
||||
@Override
|
||||
protected abstract PubKeyRing getReceiverPubKeyRing();
|
||||
protected NodeAddress getReceiverNodeAddress() {
|
||||
return getReceiver().getNodeAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PubKeyRing getReceiverPubKeyRing() {
|
||||
return getReceiver().getPubKeyRing();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TradeMailboxMessage getTradeMailboxMessage(String tradeId) {
|
||||
@ -97,23 +104,24 @@ public abstract class SendDepositsConfirmedMessage extends SendMailboxMessageTas
|
||||
|
||||
@Override
|
||||
protected void setStateSent() {
|
||||
getReceiver().setDepositsConfirmedMessageState(MessageState.SENT);
|
||||
tryToSendAgainLater();
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateArrived() {
|
||||
// no additional handling
|
||||
getReceiver().setDepositsConfirmedMessageState(MessageState.ARRIVED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateStoredInMailbox() {
|
||||
// no additional handling
|
||||
getReceiver().setDepositsConfirmedMessageState(MessageState.STORED_IN_MAILBOX);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateFault() {
|
||||
// no additional handling
|
||||
getReceiver().setDepositsConfirmedMessageState(MessageState.FAILED);
|
||||
}
|
||||
|
||||
private void cleanup() {
|
||||
@ -137,7 +145,7 @@ public abstract class SendDepositsConfirmedMessage extends SendMailboxMessageTas
|
||||
timer.stop();
|
||||
}
|
||||
|
||||
// first re-send is after 2 minutes, then double the delay each iteration
|
||||
// first re-send is after 2 minutes, then increase the delay exponentially
|
||||
if (resendCounter == 0) {
|
||||
int shortDelay = 2;
|
||||
log.info("We will send the message again to the peer after a delay of {} min.", shortDelay);
|
||||
@ -145,13 +153,12 @@ public abstract class SendDepositsConfirmedMessage extends SendMailboxMessageTas
|
||||
} else {
|
||||
log.info("We will send the message again to the peer after a delay of {} min.", delayInMin);
|
||||
timer = UserThread.runAfter(this::run, delayInMin, TimeUnit.MINUTES);
|
||||
delayInMin = delayInMin * 2;
|
||||
delayInMin = (int) ((double) delayInMin * 1.5);
|
||||
}
|
||||
resendCounter++;
|
||||
}
|
||||
|
||||
private boolean isAckedByReceiver() {
|
||||
TradePeer peer = trade.getTradePeer(getReceiverNodeAddress());
|
||||
return peer.isDepositsConfirmedMessageAcked();
|
||||
return getReceiver().isDepositsConfirmedMessageAcked();
|
||||
}
|
||||
}
|
||||
|
@ -17,10 +17,9 @@
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
import haveno.core.trade.protocol.TradePeer;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
@ -34,12 +33,7 @@ public class SendDepositsConfirmedMessageToArbitrator extends SendDepositsConfir
|
||||
}
|
||||
|
||||
@Override
|
||||
public NodeAddress getReceiverNodeAddress() {
|
||||
return trade.getArbitrator().getNodeAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PubKeyRing getReceiverPubKeyRing() {
|
||||
return trade.getArbitrator().getPubKeyRing();
|
||||
protected TradePeer getReceiver() {
|
||||
return trade.getArbitrator();
|
||||
}
|
||||
}
|
||||
|
@ -17,10 +17,9 @@
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
import haveno.core.trade.protocol.TradePeer;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
@ -34,12 +33,7 @@ public class SendDepositsConfirmedMessageToBuyer extends SendDepositsConfirmedMe
|
||||
}
|
||||
|
||||
@Override
|
||||
public NodeAddress getReceiverNodeAddress() {
|
||||
return trade.getBuyer().getNodeAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PubKeyRing getReceiverPubKeyRing() {
|
||||
return trade.getBuyer().getPubKeyRing();
|
||||
protected TradePeer getReceiver() {
|
||||
return trade.getBuyer();
|
||||
}
|
||||
}
|
||||
|
@ -17,10 +17,9 @@
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
import haveno.core.trade.protocol.TradePeer;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
@ -34,12 +33,7 @@ public class SendDepositsConfirmedMessageToSeller extends SendDepositsConfirmedM
|
||||
}
|
||||
|
||||
@Override
|
||||
public NodeAddress getReceiverNodeAddress() {
|
||||
return trade.getSeller().getNodeAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PubKeyRing getReceiverPubKeyRing() {
|
||||
return trade.getSeller().getPubKeyRing();
|
||||
protected TradePeer getReceiver() {
|
||||
return trade.getSeller();
|
||||
}
|
||||
}
|
||||
|
@ -70,6 +70,9 @@ public class TakerReserveTradeFunds extends TradeTask {
|
||||
MoneroRpcConnection sourceConnection = trade.getXmrConnectionService().getConnection();
|
||||
try {
|
||||
reserveTx = model.getXmrWalletService().createReserveTx(penaltyFee, takerFee, sendAmount, securityDeposit, returnAddress, false, null);
|
||||
} catch (IllegalStateException e) {
|
||||
log.warn("Illegal state creating reserve tx, offerId={}, error={}", trade.getShortId(), i + 1, e.getMessage());
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.warn("Error creating reserve tx, tradeId={}, attempt={}/{}, error={}", trade.getShortId(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
|
||||
trade.getXmrWalletService().handleWalletError(e, sourceConnection);
|
||||
|
@ -75,8 +75,6 @@ public class XmrNodes {
|
||||
new XmrNode(MoneroNodesOption.PROVIDED, null, null, "127.0.0.1", 38081, 1, "@local"),
|
||||
new XmrNode(MoneroNodesOption.PROVIDED, null, null, "127.0.0.1", 39081, 1, "@local"),
|
||||
new XmrNode(MoneroNodesOption.PROVIDED, null, null, "45.63.8.26", 38081, 2, "@haveno"),
|
||||
new XmrNode(MoneroNodesOption.PROVIDED, null, null, "stagenet.community.rino.io", 38081, 3, "@RINOwallet"),
|
||||
new XmrNode(MoneroNodesOption.PUBLIC, null, null, "stagenet.melo.tools", 38081, 3, null),
|
||||
new XmrNode(MoneroNodesOption.PUBLIC, null, null, "node.sethforprivacy.com", 38089, 3, null),
|
||||
new XmrNode(MoneroNodesOption.PUBLIC, null, null, "node2.sethforprivacy.com", 38089, 3, null),
|
||||
new XmrNode(MoneroNodesOption.PUBLIC, null, "plowsof3t5hogddwabaeiyrno25efmzfxyro2vligremt7sxpsclfaid.onion", null, 38089, 3, null)
|
||||
@ -85,7 +83,6 @@ public class XmrNodes {
|
||||
return Arrays.asList(
|
||||
new XmrNode(MoneroNodesOption.PUBLIC, null, null, "127.0.0.1", 18081, 1, "@local"),
|
||||
new XmrNode(MoneroNodesOption.PUBLIC, null, null, "xmr-node.cakewallet.com", 18081, 2, "@cakewallet"),
|
||||
new XmrNode(MoneroNodesOption.PUBLIC, null, null, "node.community.rino.io", 18081, 2, "@RINOwallet"),
|
||||
new XmrNode(MoneroNodesOption.PUBLIC, null, null, "nodes.hashvault.pro", 18080, 2, "@HashVault"),
|
||||
new XmrNode(MoneroNodesOption.PUBLIC, null, null, "p2pmd.xmrvsbeast.com", 18080, 2, "@xmrvsbeast"),
|
||||
new XmrNode(MoneroNodesOption.PUBLIC, null, null, "node.monerodevs.org", 18089, 2, "@monerodevs.org"),
|
||||
|
@ -135,7 +135,7 @@ public class MoneroWalletRpcManager {
|
||||
|
||||
// stop process
|
||||
String pid = walletRpc.getProcess() == null ? null : String.valueOf(walletRpc.getProcess().pid());
|
||||
log.info("Stopping MoneroWalletRpc path={}, port={}, pid={}", path, port, pid);
|
||||
log.info("Stopping MoneroWalletRpc path={}, port={}, pid={}, force={}", path, port, pid, force);
|
||||
walletRpc.stopProcess(force);
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,7 @@ public class Restrictions {
|
||||
public static final double MAX_SECURITY_DEPOSIT_PCT = 0.5;
|
||||
public static BigInteger MIN_TRADE_AMOUNT = HavenoUtils.xmrToAtomicUnits(0.1);
|
||||
public static BigInteger MIN_SECURITY_DEPOSIT = HavenoUtils.xmrToAtomicUnits(0.1);
|
||||
public static int MAX_EXTRA_INFO_LENGTH = 1500;
|
||||
|
||||
// At mediation we require a min. payout to the losing party to keep incentive for the trader to accept the
|
||||
// mediated payout. For Refund agent cases we do not have that restriction.
|
||||
|
@ -68,7 +68,6 @@ import java.util.stream.Stream;
|
||||
import javafx.beans.property.LongProperty;
|
||||
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import lombok.Getter;
|
||||
import monero.common.MoneroError;
|
||||
import monero.common.MoneroRpcConnection;
|
||||
import monero.common.MoneroRpcError;
|
||||
@ -145,8 +144,7 @@ public class XmrWalletService extends XmrWalletBase {
|
||||
private TradeManager tradeManager;
|
||||
private ExecutorService syncWalletThreadPool = Executors.newFixedThreadPool(10); // TODO: adjust based on connection type
|
||||
|
||||
@Getter
|
||||
public final Object lock = new Object();
|
||||
private final Object lock = new Object();
|
||||
private TaskLooper pollLooper;
|
||||
private boolean pollInProgress;
|
||||
private Long pollPeriodMs;
|
||||
@ -247,7 +245,11 @@ public class XmrWalletService extends XmrWalletBase {
|
||||
|
||||
@Override
|
||||
public void saveWallet() {
|
||||
saveWallet(!(Utilities.isWindows() && wallet != null));
|
||||
saveWallet(shouldBackup(wallet));
|
||||
}
|
||||
|
||||
private boolean shouldBackup(MoneroWallet wallet) {
|
||||
return wallet != null && !Utilities.isWindows(); // TODO: cannot backup on windows because file is locked
|
||||
}
|
||||
|
||||
public void saveWallet(boolean backup) {
|
||||
@ -389,7 +391,7 @@ public class XmrWalletService extends XmrWalletBase {
|
||||
MoneroError err = null;
|
||||
String path = wallet.getPath();
|
||||
try {
|
||||
if (save) saveWallet(wallet, true);
|
||||
if (save) saveWallet(wallet, shouldBackup(wallet));
|
||||
wallet.close();
|
||||
} catch (MoneroError e) {
|
||||
err = e;
|
||||
@ -736,7 +738,7 @@ public class XmrWalletService extends XmrWalletBase {
|
||||
MoneroDaemonRpc daemon = getDaemon();
|
||||
MoneroWallet wallet = getWallet();
|
||||
MoneroTx tx = null;
|
||||
synchronized (daemon) {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
|
||||
// verify tx not submitted to pool
|
||||
@ -765,7 +767,7 @@ public class XmrWalletService extends XmrWalletBase {
|
||||
BigInteger minerFeeEstimate = getFeeEstimate(tx.getWeight());
|
||||
double minerFeeDiff = tx.getFee().subtract(minerFeeEstimate).abs().doubleValue() / minerFeeEstimate.doubleValue();
|
||||
if (minerFeeDiff > MINER_FEE_TOLERANCE) throw new RuntimeException("Miner fee is not within " + (MINER_FEE_TOLERANCE * 100) + "% of estimated fee, expected " + minerFeeEstimate + " but was " + tx.getFee() + ", diff%=" + minerFeeDiff);
|
||||
log.info("Trade tx fee {} is within tolerance, diff%={}", tx.getFee(), minerFeeDiff);
|
||||
log.info("Trade miner fee {} is within tolerance, diff%={}", tx.getFee(), minerFeeDiff);
|
||||
|
||||
// verify proof to fee address
|
||||
BigInteger actualTradeFee = BigInteger.ZERO;
|
||||
@ -783,7 +785,7 @@ public class XmrWalletService extends XmrWalletBase {
|
||||
// verify trade fee amount
|
||||
if (!actualTradeFee.equals(tradeFeeAmount)) {
|
||||
if (equalsWithinFractionError(actualTradeFee, tradeFeeAmount)) {
|
||||
log.warn("Trade tx fee amount is within fraction error, expected " + tradeFeeAmount + " but was " + actualTradeFee);
|
||||
log.warn("Trade fee amount is within fraction error, expected " + tradeFeeAmount + " but was " + actualTradeFee);
|
||||
} else {
|
||||
throw new RuntimeException("Invalid trade fee amount, expected " + tradeFeeAmount + " but was " + actualTradeFee);
|
||||
}
|
||||
@ -922,7 +924,7 @@ public class XmrWalletService extends XmrWalletBase {
|
||||
}
|
||||
|
||||
// shut down threads
|
||||
synchronized (getLock()) {
|
||||
synchronized (lock) {
|
||||
List<Runnable> shutDownThreads = new ArrayList<>();
|
||||
shutDownThreads.add(() -> ThreadUtils.shutDown(THREAD_ID));
|
||||
ThreadUtils.awaitTasks(shutDownThreads);
|
||||
@ -1014,6 +1016,13 @@ public class XmrWalletService extends XmrWalletBase {
|
||||
|
||||
public synchronized void resetAddressEntriesForOpenOffer(String offerId) {
|
||||
log.info("resetAddressEntriesForOpenOffer offerId={}", offerId);
|
||||
|
||||
// skip if failed trade is scheduled for processing // TODO: do not call this function in this case?
|
||||
if (tradeManager.hasFailedScheduledTrade(offerId)) {
|
||||
log.warn("Refusing to reset address entries because trade is scheduled for deletion with offerId={}", offerId);
|
||||
return;
|
||||
}
|
||||
|
||||
swapAddressEntryToAvailable(offerId, XmrAddressEntry.Context.OFFER_FUNDING);
|
||||
|
||||
// swap trade payout to available if applicable
|
||||
@ -1162,7 +1171,7 @@ public class XmrWalletService extends XmrWalletBase {
|
||||
public Stream<XmrAddressEntry> getAddressEntriesForAvailableBalanceStream() {
|
||||
Stream<XmrAddressEntry> available = getFundedAvailableAddressEntries().stream();
|
||||
available = Stream.concat(available, getAddressEntries(XmrAddressEntry.Context.ARBITRATOR).stream());
|
||||
available = Stream.concat(available, getAddressEntries(XmrAddressEntry.Context.OFFER_FUNDING).stream().filter(entry -> !tradeManager.getOpenOfferManager().getOpenOfferById(entry.getOfferId()).isPresent()));
|
||||
available = Stream.concat(available, getAddressEntries(XmrAddressEntry.Context.OFFER_FUNDING).stream().filter(entry -> !tradeManager.getOpenOfferManager().getOpenOffer(entry.getOfferId()).isPresent()));
|
||||
available = Stream.concat(available, getAddressEntries(XmrAddressEntry.Context.TRADE_PAYOUT).stream().filter(entry -> tradeManager.getTrade(entry.getOfferId()) == null || tradeManager.getTrade(entry.getOfferId()).isPayoutUnlocked()));
|
||||
return available.filter(addressEntry -> getBalanceForSubaddress(addressEntry.getSubaddressIndex()).compareTo(BigInteger.ZERO) > 0);
|
||||
}
|
||||
|
@ -134,7 +134,7 @@ shared.noDateAvailable=No date available
|
||||
shared.noDetailsAvailable=No details available
|
||||
shared.notUsedYet=Not used yet
|
||||
shared.date=Date
|
||||
shared.sendFundsDetailsWithFee=Sending: {0}\nTo receiving address: {1}.\nRequired mining fee is: {2}\n\nThe recipient will receive: {3}\n\nAre you sure you want to withdraw this amount?
|
||||
shared.sendFundsDetailsWithFee=Sending: {0}\n\nTo receiving address: {1}\n\nAdditional miner fee: {2}\n\nAre you sure you want to send this amount?
|
||||
# suppress inspection "TrailingSpacesInProperty"
|
||||
shared.sendFundsDetailsDust=Haveno detected that this transaction would create a change output which is below the minimum dust threshold (and therefore not allowed by Monero consensus rules). Instead, this dust ({0} satoshi{1}) will be added to the mining fee.\n\n\n
|
||||
shared.copyToClipboard=Copy to clipboard
|
||||
@ -495,6 +495,7 @@ createOffer.triggerPrice.tooltip=As protection against drastic price movements y
|
||||
deactivates the offer if the market price reaches that value.
|
||||
createOffer.triggerPrice.invalid.tooLow=Value must be higher than {0}
|
||||
createOffer.triggerPrice.invalid.tooHigh=Value must be lower than {0}
|
||||
createOffer.extraInfo.invalid.tooLong=Must not exceed {0} characters.
|
||||
|
||||
# new entries
|
||||
createOffer.placeOfferButton=Review: Place offer to {0} monero
|
||||
@ -672,7 +673,8 @@ portfolio.pending.autoConf.state.ERROR=An error at a service request occurred. N
|
||||
# suppress inspection "UnusedProperty"
|
||||
portfolio.pending.autoConf.state.FAILED=A service returned with a failure. No auto-confirm possible.
|
||||
|
||||
portfolio.pending.step1.info=Deposit transaction has been published.\n{0} need to wait for 10 confirmations (about 20 minutes) before the payment can start.
|
||||
portfolio.pending.step1.info.you=Deposit transaction has been published.\nYou need to wait for 10 confirmations (about 20 minutes) before the payment can start.
|
||||
portfolio.pending.step1.info.buyer=Deposit transaction has been published.\nThe XMR buyer needs to wait for 10 confirmations (about 20 minutes) before the payment can start.
|
||||
portfolio.pending.step1.warn=The deposit transaction is not confirmed yet. This usually takes about 20 minutes, but could be more if the network is congested.
|
||||
portfolio.pending.step1.openForDispute=The deposit transaction is still not confirmed. \
|
||||
If you have been waiting for much longer than 20 minutes, contact Haveno support.
|
||||
@ -962,7 +964,7 @@ portfolio.pending.failedTrade.maker.missingTakerFeeTx=The peer's taker fee trans
|
||||
portfolio.pending.failedTrade.missingDepositTx=The deposit transaction (the 2-of-2 multisig transaction) is missing.\n\n\
|
||||
Without this tx, the trade cannot be completed. No funds have been locked but your trade fee has been paid. \
|
||||
You can make a request to be reimbursed the trade fee here: \
|
||||
[HYPERLINK:https://github.com/bisq-network/support/issues]\n\n\
|
||||
[HYPERLINK:https://github.com/haveno-dex/haveno/issues]\n\n\
|
||||
Feel free to move this trade to failed trades.
|
||||
portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, \
|
||||
but funds have been locked in the deposit transaction.\n\n\
|
||||
@ -972,7 +974,7 @@ portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=T
|
||||
(with seller receiving full trade amount back as well). \
|
||||
This way, there is no security risk, and only trade fees are lost. \n\n\
|
||||
You can request a reimbursement for lost trade fees here: \
|
||||
[HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
[HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing \
|
||||
but funds have been locked in the deposit transaction.\n\n\
|
||||
If the buyer is also missing the delayed payout transaction, they will be instructed to NOT send the payment and open \
|
||||
@ -981,18 +983,18 @@ portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=
|
||||
their security deposits (with seller receiving full trade amount back as well). \
|
||||
Otherwise the trade amount should go to the buyer. \n\n\
|
||||
You can request a reimbursement for lost trade fees here: \
|
||||
[HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
[HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.errorMsgSet=There was an error during trade protocol execution.\n\n\
|
||||
Error: {0}\n\n\
|
||||
It might be that this error is not critical, and the trade can be completed normally. If you are unsure, open a mediation \
|
||||
ticket to get advice from Haveno mediators. \n\n\
|
||||
If the error was critical and the trade cannot be completed, you might have lost your trade fee. \
|
||||
Request a reimbursement for lost trade fees here: \
|
||||
[HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
[HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.missingContract=The trade contract is not set.\n\n\
|
||||
The trade cannot be completed and you might \
|
||||
have lost your trade fee. If so, you can request a reimbursement for lost trade fees here: \
|
||||
[HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
[HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.info.popup=The trade protocol encountered some problems.\n\n{0}
|
||||
portfolio.pending.failedTrade.txChainInvalid.moveToFailed=The trade protocol encountered a serious problem.\n\n{0}\n\n\
|
||||
Do you want to move the trade to failed trades?\n\n\
|
||||
@ -2056,7 +2058,8 @@ closedTradesSummaryWindow.totalTradeFeeInXmr.value={0} ({1} of total trade amoun
|
||||
walletPasswordWindow.headline=Enter password to unlock
|
||||
|
||||
connectionFallback.headline=Connection error
|
||||
connectionFallback.msg=Error connecting to your custom Monero node(s).\n\nDo you want to try the next best available Monero node?
|
||||
connectionFallback.customNode=Error connecting to your custom Monero node(s).\n\nDo you want to use the next best available Monero node?
|
||||
connectionFallback.localNode=Error connecting to your last used local node.\n\nDo you want to use the next best available Monero node?
|
||||
|
||||
torNetworkSettingWindow.header=Tor networks settings
|
||||
torNetworkSettingWindow.noBridges=Don't use bridges
|
||||
@ -3201,14 +3204,16 @@ DOMESTIC_WIRE_TRANSFER=Domestic Wire Transfer
|
||||
# suppress inspection "UnusedProperty"
|
||||
BSQ_SWAP=BSQ Swap
|
||||
|
||||
# Deprecated: Cannot be deleted as it would break old trade history entries
|
||||
# suppress inspection "UnusedProperty"
|
||||
OK_PAY=OKPay
|
||||
# suppress inspection "UnusedProperty"
|
||||
CASH_APP=Cash App
|
||||
# suppress inspection "UnusedProperty"
|
||||
VENMO=Venmo
|
||||
# suppress inspection "UnusedProperty"
|
||||
PAYPAL=PayPal
|
||||
# suppress inspection "UnusedProperty"
|
||||
PAYSAFE=Paysafe
|
||||
|
||||
# suppress inspection "UnusedProperty"
|
||||
UPHOLD_SHORT=Uphold
|
||||
@ -3304,9 +3309,10 @@ OK_PAY_SHORT=OKPay
|
||||
CASH_APP_SHORT=Cash App
|
||||
# suppress inspection "UnusedProperty"
|
||||
VENMO_SHORT=Venmo
|
||||
# suppress inspection "UnusedProperty"
|
||||
PAYPAL_SHORT=PayPal
|
||||
# suppress inspection "UnusedProperty"
|
||||
PAYSAFE=Paysafe
|
||||
PAYSAFE_SHORT=Paysafe
|
||||
|
||||
|
||||
####################################################################
|
||||
|
@ -134,7 +134,7 @@ shared.noDateAvailable=Žádné datum není k dispozici
|
||||
shared.noDetailsAvailable=Detaily nejsou k dispozici
|
||||
shared.notUsedYet=Ještě nepoužito
|
||||
shared.date=Datum
|
||||
shared.sendFundsDetailsWithFee=Odesílání: {0}nNa přijímací adresu: {1}.nPožadován těžební poplatek: {2}\n\nPříjemce dostane: {3}\n\nJste si jisti, že chcete vyplatit tuto částku?
|
||||
shared.sendFundsDetailsWithFee=Odesílání: {0}\n\nNa přijímací adresu: {1}\n\nDalší poplatek pro těžaře: {2}\n\nJste si jisti, že chcete vyplatit tuto částku?
|
||||
# suppress inspection "TrailingSpacesInProperty"
|
||||
shared.sendFundsDetailsDust=Haveno zjistil, že tato transakce by vytvořila drobné mince, které jsou pod limitem drobných mincí (a není to povoleno pravidly pro konsenzus Monero). Místo toho budou tyto drobné mince ({0} satoshi {1}) přidány k poplatku za těžbu.\n\n\n
|
||||
shared.copyToClipboard=Kopírovat do schránky
|
||||
@ -672,7 +672,8 @@ portfolio.pending.autoConf.state.ERROR=Došlo k chybě při požadavku na služb
|
||||
# suppress inspection "UnusedProperty"
|
||||
portfolio.pending.autoConf.state.FAILED=Služba se vrátila se selháním. Není možné automatické potvrzení.
|
||||
|
||||
portfolio.pending.step1.info=Vkladová transakce byla zveřejněna.\n{0} před zahájením platby musíte počkat na alespoň jedno potvrzení na blockchainu.
|
||||
portfolio.pending.step1.info.you=Transakce vkladu byla publikována.\nMusíte počkat na 10 potvrzení (přibližně 20 minut), než bude platba zahájena.
|
||||
portfolio.pending.step1.info.buyer=Transakce vkladu byla publikována.\nKupující XMR musí počkat na 10 potvrzení (asi 20 minut), než bude platba zahájena.
|
||||
portfolio.pending.step1.warn=Vkladová transakce není stále potvrzena. K tomu někdy dochází ve vzácných případech, kdy byl poplatek za financování jednoho obchodníka z externí peněženky příliš nízký.
|
||||
portfolio.pending.step1.openForDispute=Vkladová transakce není stále potvrzena. \
|
||||
Pokud jste čekali mnohem déle než 20 minut, můžete poádat o pomoc podporu Haveno.
|
||||
@ -962,7 +963,7 @@ portfolio.pending.failedTrade.maker.missingTakerFeeTx=Chybí poplatek příjemce
|
||||
portfolio.pending.failedTrade.missingDepositTx=Vkladová transakce (transakce 2-of-2 multisig) chybí.\n\n\
|
||||
Bez této tx nelze obchod dokončit. Nebyly uzamčeny žádné prostředky, ale byl zaplacen váš obchodní poplatek. \
|
||||
Zde můžete požádat o vrácení obchodního poplatku: \
|
||||
[HYPERLINK:https://github.com/bisq-network/support/issues]\n\n\
|
||||
[HYPERLINK:https://github.com/haveno-dex/haveno/issues]\n\n\
|
||||
Klidně můžete přesunout tento obchod do neúspěšných obchodů.
|
||||
portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=Zpožděná výplatní transakce chybí,
|
||||
ale prostředky byly uzamčeny v vkladové transakci.\n\n\
|
||||
@ -972,7 +973,7 @@ portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=Z
|
||||
(přičemž prodejce také obdrží plnou částku obchodu). \
|
||||
Tímto způsobem nehrozí žádné bezpečnostní riziko a jsou ztraceny pouze obchodní poplatky.\n\n\
|
||||
O vrácení ztracených obchodních poplatků můžete požádat zde: \
|
||||
[HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
[HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=Zpožděná výplatní transakce chybí, \
|
||||
ale prostředky byly v depozitní transakci uzamčeny.\n\n\
|
||||
Pokud kupujícímu chybí také odložená výplatní transakce, bude poučen, aby platbu NEPOSLAL a místo toho otevřel \
|
||||
@ -981,18 +982,18 @@ portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=
|
||||
svých bezpečnostních vkladů (přičemž prodejce také obdrží plnou částku obchodu). \
|
||||
Jinak by částka obchodu měla jít kupujícímu.\n\n\
|
||||
O vrácení ztracených obchodních poplatků můžete požádat zde: \
|
||||
[HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
[HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.errorMsgSet=Během provádění obchodního protokolu došlo k chybě.\n\n
|
||||
Chyba: {0}\n\n\
|
||||
Je možné, že tato chyba není kritická a obchod lze dokončit normálně. Pokud si nejste jisti, otevřete si mediační úkol \
|
||||
a získejte radu od mediátorů Haveno.\n\n\
|
||||
Pokud byla chyba kritická a obchod nelze dokončit, možná jste ztratili obchodní poplatek. \
|
||||
O vrácení ztracených obchodních poplatků požádejte zde: \
|
||||
[HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
[HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.missingContract=Obchodní kontrakt není stanoven.\n\n\
|
||||
Obchod nelze dokončit a možná jste ztratili poplatek \
|
||||
za obchodování. Pokud ano, můžete požádat o vrácení ztracených obchodních poplatků zde: \
|
||||
[HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
[HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.info.popup=Obchodní protokol narazil na některé problémy.\n\n{0}
|
||||
portfolio.pending.failedTrade.txChainInvalid.moveToFailed=Obchodní protokol narazil na vážný problém.\n\n{0}\n\n\
|
||||
Chcete obchod přesunout do neúspěšných obchodů?\n\n\
|
||||
@ -2056,7 +2057,7 @@ closedTradesSummaryWindow.totalTradeFeeInXmr.value={0} ({1} z celkového objemu
|
||||
walletPasswordWindow.headline=Pro odemknutí zadejte heslo
|
||||
|
||||
connectionFallback.headline=Chyba připojení
|
||||
connectionFallback.msg=Chyba při připojování k vlastním uzlům Monero.\n\nChcete vyzkoušet další nejlepší dostupný uzel Monero?
|
||||
connectionFallback.customNode=Chyba při připojování k vlastním uzlům Monero.\n\nChcete vyzkoušet další nejlepší dostupný uzel Monero?
|
||||
|
||||
torNetworkSettingWindow.header=Nastavení sítě Tor
|
||||
torNetworkSettingWindow.noBridges=Nepoužívat most (bridge)
|
||||
|
@ -125,6 +125,7 @@ shared.noDateAvailable=Kein Datum verfügbar
|
||||
shared.noDetailsAvailable=Keine Details vorhanden
|
||||
shared.notUsedYet=Noch ungenutzt
|
||||
shared.date=Datum
|
||||
shared.sendFundsDetailsWithFee=Senden: {0}\n\nAn die Empfangsadresse: {1}\n\nZusätzliche Miner-Gebühr: {2}\n\nSind Sie sicher, dass Sie diesen Betrag senden möchten?
|
||||
# suppress inspection "TrailingSpacesInProperty"
|
||||
shared.sendFundsDetailsDust=Diese Transaktion würde ein Wechselgeld erzeugen das unterhalb des Dust-Grenzwerts liegt (und daher von den Monero-Konsensregeln nicht erlaubt wäre). Stattdessen wird dieser Dust ({0} Satoshi{1}) der Mining-Gebühr hinzugefügt.\n\n\n
|
||||
shared.copyToClipboard=In Zwischenablage kopieren
|
||||
@ -615,7 +616,8 @@ portfolio.pending.autoConf.state.ERROR=An einer Service-Abfrage ist ein Fehler a
|
||||
# suppress inspection "UnusedProperty"
|
||||
portfolio.pending.autoConf.state.FAILED=Eine Service-Abfrage ist ausgefallen. Eine Automatische Bestätigung ist nicht mehr möglich.
|
||||
|
||||
portfolio.pending.step1.info=Die Kautionstransaktion wurde veröffentlicht.\n{0} muss auf wenigstens eine Blockchain-Bestätigung warten, bevor die Zahlung beginnt.
|
||||
portfolio.pending.step1.info.you=Die Einzahlungstransaktion wurde veröffentlicht.\nSie müssen 10 Bestätigungen abwarten (etwa 20 Minuten), bevor die Zahlung beginnen kann.
|
||||
portfolio.pending.step1.info.buyer=Die Einzahlungstransaktion wurde veröffentlicht.\nDer XMR-Käufer muss 10 Bestätigungen abwarten (ca. 20 Minuten), bevor die Zahlung gestartet werden kann.
|
||||
portfolio.pending.step1.warn=Die Kautionstransaktion ist noch nicht bestätigt. Dies geschieht manchmal in seltenen Fällen, wenn die Finanzierungsgebühr aus der externen Wallet eines Traders zu niedrig war.
|
||||
portfolio.pending.step1.openForDispute=Die Kautionstransaktion ist noch nicht bestätigt. Sie können länger warten oder den Vermittler um Hilfe bitten.
|
||||
|
||||
@ -818,11 +820,11 @@ portfolio.pending.mediationResult.popup.alreadyAccepted=Sie haben bereits akzept
|
||||
|
||||
portfolio.pending.failedTrade.taker.missingTakerFeeTx=Die Transaktion der Abnehmer-Gebühr fehlt.\n\nOhne diese tx kann der Handel nicht abgeschlossen werden. Keine Gelder wurden gesperrt und keine Handelsgebühr wurde bezahlt. Sie können diesen Handel zu den fehlgeschlagenen Händeln verschieben.
|
||||
portfolio.pending.failedTrade.maker.missingTakerFeeTx=Die Transaktion der Abnehmer-Gebühr fehlt.\n\nOhne diese tx kann der Handel nicht abgeschlossen werden. Keine Gelder wurden gesperrt. Ihr Angebot ist für andere Händler weiterhin verfügbar. Sie haben die Ersteller-Gebühr also nicht verloren. Sie können diesen Handel zu den fehlgeschlagenen Händeln verschieben.
|
||||
portfolio.pending.failedTrade.missingDepositTx=Die Einzahlungstransaktion (die 2-of-2 Multisig-Transaktion) fehlt.\n\nOhne diese tx kann der Handel nicht abgeschlossen werden. Keine Gelder wurden gesperrt aber die Handels-Gebühr wurde bezahlt. Sie können eine Anfrage für eine Rückerstattung der Handels-Gebühr hier einreichen: [HYPERLINK:https://github.com/bisq-network/support/issues]\n\nSie können diesen Handel gerne zu den fehlgeschlagenen Händeln verschieben.
|
||||
portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=Die verzögerte Auszahlungstransaktion fehlt, aber die Gelder wurden in der Einzahlungstransaktion gesperrt.\n\nBitte schicken Sie KEINE Geld-(Traditional-) oder Crypto-Zahlungen an den XMR Verkäufer, weil ohne die verzögerte Auszahlungstransaktion später kein Schlichtungsverfahren eröffnet werden kann. Stattdessen öffnen Sie ein Vermittlungs-Ticket mit Cmd/Strg+o. Der Vermittler sollte vorschlagen, dass beide Handelspartner ihre vollständige Sicherheitskaution zurückerstattet bekommen (und der Verkäufer auch seinen Handels-Betrag). Durch diese Vorgehensweise entsteht kein Sicherheitsrisiko und es geht ausschließlich die Handelsgebühr verloren.\n\nSie können eine Rückerstattung der verlorenen gegangenen Handelsgebühren hier erbitten: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=Die verzögerte Auszahlungstransaktion fehlt, aber die Gelder wurden in der Einzahlungstransaktion gesperrt.\n\nWenn dem Käufer die verzögerte Auszahlungstransaktion auch fehlt, wird er dazu aufgefordert die Bezahlung NICHT zu schicken und stattdessen ein Vermittlungs-Ticket zu eröffnen. Sie sollten auch ein Vermittlungs-Ticket mit Cmd/Strg+o öffnen.\n\nWenn der Käufer die Zahlung noch nicht geschickt hat, sollte der Vermittler vorschlagen, dass beide Handelspartner ihre Sicherheitskaution vollständig zurückerhalten (und der Verkäufer auch den Handels-Betrag). Anderenfalls sollte der Handels-Betrag an den Käufer gehen.\n\nSie können eine Rückerstattung der verlorenen gegangenen Handelsgebühren hier erbitten: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.errorMsgSet=Während der Ausführung des Handel-Protokolls ist ein Fehler aufgetreten.\n\nFehler: {0}\n\nEs kann sein, dass dieser Fehler nicht gravierend ist und der Handel ganz normal abgeschlossen werden kann. Wenn Sie sich unsicher sind, öffnen Sie ein Vermittlungs-Ticket um den Rat eines Haveno Vermittlers zu erhalten.\n\nWenn der Fehler gravierend war, kann der Handel nicht abgeschlossen werden und Sie haben vielleicht die Handelsgebühr verloren. Sie können eine Rückerstattung der verlorenen gegangenen Handelsgebühren hier erbitten: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.missingContract=Der Handelsvertrag ist nicht festgelegt.\n\nDer Handel kann nicht abgeschlossen werden und Sie haben möglicherweise die Handelsgebühr verloren. Sollte das der Fall sein, können Sie eine Rückerstattung der verlorenen gegangenen Handelsgebühren hier beantragen: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.missingDepositTx=Die Einzahlungstransaktion (die 2-of-2 Multisig-Transaktion) fehlt.\n\nOhne diese tx kann der Handel nicht abgeschlossen werden. Keine Gelder wurden gesperrt aber die Handels-Gebühr wurde bezahlt. Sie können eine Anfrage für eine Rückerstattung der Handels-Gebühr hier einreichen: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]\n\nSie können diesen Handel gerne zu den fehlgeschlagenen Händeln verschieben.
|
||||
portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=Die verzögerte Auszahlungstransaktion fehlt, aber die Gelder wurden in der Einzahlungstransaktion gesperrt.\n\nBitte schicken Sie KEINE Geld-(Traditional-) oder Crypto-Zahlungen an den XMR Verkäufer, weil ohne die verzögerte Auszahlungstransaktion später kein Schlichtungsverfahren eröffnet werden kann. Stattdessen öffnen Sie ein Vermittlungs-Ticket mit Cmd/Strg+o. Der Vermittler sollte vorschlagen, dass beide Handelspartner ihre vollständige Sicherheitskaution zurückerstattet bekommen (und der Verkäufer auch seinen Handels-Betrag). Durch diese Vorgehensweise entsteht kein Sicherheitsrisiko und es geht ausschließlich die Handelsgebühr verloren.\n\nSie können eine Rückerstattung der verlorenen gegangenen Handelsgebühren hier erbitten: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=Die verzögerte Auszahlungstransaktion fehlt, aber die Gelder wurden in der Einzahlungstransaktion gesperrt.\n\nWenn dem Käufer die verzögerte Auszahlungstransaktion auch fehlt, wird er dazu aufgefordert die Bezahlung NICHT zu schicken und stattdessen ein Vermittlungs-Ticket zu eröffnen. Sie sollten auch ein Vermittlungs-Ticket mit Cmd/Strg+o öffnen.\n\nWenn der Käufer die Zahlung noch nicht geschickt hat, sollte der Vermittler vorschlagen, dass beide Handelspartner ihre Sicherheitskaution vollständig zurückerhalten (und der Verkäufer auch den Handels-Betrag). Anderenfalls sollte der Handels-Betrag an den Käufer gehen.\n\nSie können eine Rückerstattung der verlorenen gegangenen Handelsgebühren hier erbitten: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.errorMsgSet=Während der Ausführung des Handel-Protokolls ist ein Fehler aufgetreten.\n\nFehler: {0}\n\nEs kann sein, dass dieser Fehler nicht gravierend ist und der Handel ganz normal abgeschlossen werden kann. Wenn Sie sich unsicher sind, öffnen Sie ein Vermittlungs-Ticket um den Rat eines Haveno Vermittlers zu erhalten.\n\nWenn der Fehler gravierend war, kann der Handel nicht abgeschlossen werden und Sie haben vielleicht die Handelsgebühr verloren. Sie können eine Rückerstattung der verlorenen gegangenen Handelsgebühren hier erbitten: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.missingContract=Der Handelsvertrag ist nicht festgelegt.\n\nDer Handel kann nicht abgeschlossen werden und Sie haben möglicherweise die Handelsgebühr verloren. Sollte das der Fall sein, können Sie eine Rückerstattung der verlorenen gegangenen Handelsgebühren hier beantragen: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.info.popup=Das Handels-Protokoll hat ein paar Probleme gefunden.\n\n{0}
|
||||
portfolio.pending.failedTrade.txChainInvalid.moveToFailed=Das Handels-Protokoll hat ein schwerwiegendes Problem gefunden.\n\n{0}\n\nWollen Sie den Handel zu den fehlgeschlagenen Händeln verschieben?\n\nSie können keine Vermittlungs- oder Schlichtungsverfahren auf der Seite für fehlgeschlagene Händel eröffnen, aber Sie können einen fehlgeschlagene Handel wieder auf die Seite der offenen Händeln zurück verschieben.
|
||||
portfolio.pending.failedTrade.txChainValid.moveToFailed=Das Handels-Protokoll hat ein paar Probleme gefunden.\n\n{0}\n\nDie Transaktionen des Handels wurden veröffentlicht und die Gelder sind gesperrt. Verschieben Sie den Handel nur dann zu den fehlgeschlagenen Händeln, wenn Sie sich wirklich sicher sind. Dies könnte Optionen zur Behebung des Problems verhindern.\n\nWollen Sie den Handel zu den fehlgeschlagenen Händeln verschieben?\n\nSie können keine Vermittlungs- oder Schlichtungsverfahren auf der Seite für fehlgeschlagene Händel eröffnen, aber Sie können einen fehlgeschlagene Handel wieder auf die Seite der offenen Händeln zurück verschieben.
|
||||
|
@ -125,6 +125,7 @@ shared.noDateAvailable=Sin fecha disponible
|
||||
shared.noDetailsAvailable=Sin detalles disponibles
|
||||
shared.notUsedYet=Sin usar aún
|
||||
shared.date=Fecha
|
||||
shared.sendFundsDetailsWithFee=Enviando: {0}\n\nA la dirección receptora: {1}\n\nTarifa adicional para el minero: {2}\n\n¿Estás seguro de que deseas enviar esta cantidad?
|
||||
# suppress inspection "TrailingSpacesInProperty"
|
||||
shared.sendFundsDetailsDust=Haveno detectó que esta transacción crearía una salida que está por debajo del umbral mínimo considerada polvo (y no está permitida por las reglas de consenso en Monero). En cambio, esta transacción polvo ({0} satoshi {1}) se agregará a la tarifa de minería.\n\n\n
|
||||
shared.copyToClipboard=Copiar al portapapeles
|
||||
@ -615,7 +616,8 @@ portfolio.pending.autoConf.state.ERROR=Ocurrió un error en el servicio solicit
|
||||
# suppress inspection "UnusedProperty"
|
||||
portfolio.pending.autoConf.state.FAILED=Un servicio volvió con algún fallo. No es posible la autoconfirmación.
|
||||
|
||||
portfolio.pending.step1.info=La transacción de depósito ha sido publicada.\n{0} tiene que esperar al menos una confirmación en la cadena de bloques antes de comenzar el pago.
|
||||
portfolio.pending.step1.info.you=La transacción de depósito ha sido publicada.\nNecesitas esperar 10 confirmaciones (aproximadamente 20 minutos) antes de que el pago pueda comenzar.
|
||||
portfolio.pending.step1.info.buyer=La transacción de depósito ha sido publicada.\nEl comprador de XMR necesita esperar 10 confirmaciones (aproximadamente 20 minutos) antes de que el pago pueda comenzar.
|
||||
portfolio.pending.step1.warn=La transacción del depósito aún no se ha confirmado.\nEsto puede suceder en raras ocasiones cuando la tasa de depósito de un comerciante desde una cartera externa es demasiado baja.
|
||||
portfolio.pending.step1.openForDispute=La transacción de depósito aún no ha sido confirmada. Puede esperar más o contactar con el mediador para obtener asistencia.
|
||||
|
||||
@ -818,11 +820,11 @@ portfolio.pending.mediationResult.popup.alreadyAccepted=Ya ha aceptado
|
||||
|
||||
portfolio.pending.failedTrade.taker.missingTakerFeeTx=Falta la transacción de tasa de tomador\n\nSin esta tx, el intercambio no se puede completar. No se han bloqueado fondos y no se ha pagado ninguna tasa de intercambio. Puede mover esta operación a intercambios fallidos.
|
||||
portfolio.pending.failedTrade.maker.missingTakerFeeTx=Falta la transacción de tasa de tomador de su par.\n\nSin esta tx, el intercambio no se puede completar. No se han bloqueado fondos. Su oferta aún está disponible para otros comerciantes, por lo que no ha perdido la tasa de tomador. Puede mover este intercambio a intercambios fallidos.
|
||||
portfolio.pending.failedTrade.missingDepositTx=Falta la transacción de depósito (la transacción multifirma 2 de 2).\n\nSin esta tx, el intercambio no se puede completar. No se han bloqueado fondos, pero se ha pagado su tarifa comercial. Puede hacer una solicitud para que se le reembolse la tarifa comercial aquí: [HYPERLINK:https://github.com/bisq-network/support/issues].\n\nSiéntase libre de mover esta operación a operaciones fallidas.
|
||||
portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=Falta la transacción de pago demorado, pero los fondos se han bloqueado en la transacción de depósito.\n\nNO envíe el pago traditional o crypto al vendedor de XMR, porque sin el tx de pago demorado, no se puede abrir el arbitraje. En su lugar, abra un ticket de mediación con Cmd / Ctrl + o. El mediador debe sugerir que ambos pares recuperen el monto total de sus depósitos de seguridad (y el vendedor también recibirá el monto total de la operación). De esta manera, no hay riesgo en la seguridad y solo se pierden las tarifas comerciales.\n\nPuede solicitar un reembolso por las tarifas comerciales perdidas aquí: [HYPERLINK:https://github.com/bisq-network/support/issues].
|
||||
portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=Falta la transacción del pago demorado, pero los fondos se han bloqueado en la transacción de depósito.\n\nSi al comprador también le falta la transacción de pago demorado, se le indicará que NO envíe el pago y abra un ticket de mediación. También debe abrir un ticket de mediación con Cmd / Ctrl + o.\n\nSi el comprador aún no ha enviado el pago, el mediador debe sugerir que ambos pares recuperen el monto total de sus depósitos de seguridad (y el vendedor también recibirá el monto total de la operación). De lo contrario, el monto comercial debe ir al comprador.\n\nPuede solicitar un reembolso por las tarifas comerciales perdidas aquí: [HYPERLINK:https://github.com/bisq-network/support/issues].
|
||||
portfolio.pending.failedTrade.missingDepositTx=Falta la transacción de depósito (la transacción multifirma 2 de 2).\n\nSin esta tx, el intercambio no se puede completar. No se han bloqueado fondos, pero se ha pagado su tarifa comercial. Puede hacer una solicitud para que se le reembolse la tarifa comercial aquí: [HYPERLINK:https://github.com/haveno-dex/haveno/issues].\n\nSiéntase libre de mover esta operación a operaciones fallidas.
|
||||
portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=Falta la transacción de pago demorado, pero los fondos se han bloqueado en la transacción de depósito.\n\nNO envíe el pago traditional o crypto al vendedor de XMR, porque sin el tx de pago demorado, no se puede abrir el arbitraje. En su lugar, abra un ticket de mediación con Cmd / Ctrl + o. El mediador debe sugerir que ambos pares recuperen el monto total de sus depósitos de seguridad (y el vendedor también recibirá el monto total de la operación). De esta manera, no hay riesgo en la seguridad y solo se pierden las tarifas comerciales.\n\nPuede solicitar un reembolso por las tarifas comerciales perdidas aquí: [HYPERLINK:https://github.com/haveno-dex/haveno/issues].
|
||||
portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=Falta la transacción del pago demorado, pero los fondos se han bloqueado en la transacción de depósito.\n\nSi al comprador también le falta la transacción de pago demorado, se le indicará que NO envíe el pago y abra un ticket de mediación. También debe abrir un ticket de mediación con Cmd / Ctrl + o.\n\nSi el comprador aún no ha enviado el pago, el mediador debe sugerir que ambos pares recuperen el monto total de sus depósitos de seguridad (y el vendedor también recibirá el monto total de la operación). De lo contrario, el monto comercial debe ir al comprador.\n\nPuede solicitar un reembolso por las tarifas comerciales perdidas aquí: [HYPERLINK:https://github.com/haveno-dex/haveno/issues].
|
||||
portfolio.pending.failedTrade.errorMsgSet=Hubo un error durante la ejecución del protocolo de intercambio.\n\nError: {0}\n\nPuede ser que este error no sea crítico y que el intercambio se pueda completar normalmente. Si no está seguro, abra un ticket de mediación para obtener consejos de los mediadores de Haveno.\n\nSi el error fue crítico y la operación no se puede completar, es posible que haya perdido su tarifa de operación. Solicite un reembolso por las tarifas comerciales perdidas aquí: [HYPERLINK:ttps://github.com/bisq-network/support/issues].
|
||||
portfolio.pending.failedTrade.missingContract=El contrato del intercambio no está establecido.\n\nLa operación no se puede completar y es posible que haya perdido su tarifa de operación. Si es así, puede solicitar un reembolso por las tarifas comerciales perdidas aquí: [HYPERLINK:https://github.com/bisq-network/support/issues].
|
||||
portfolio.pending.failedTrade.missingContract=El contrato del intercambio no está establecido.\n\nLa operación no se puede completar y es posible que haya perdido su tarifa de operación. Si es así, puede solicitar un reembolso por las tarifas comerciales perdidas aquí: [HYPERLINK:https://github.com/haveno-dex/haveno/issues].
|
||||
portfolio.pending.failedTrade.info.popup=El protocolo de intercambio encontró algunos problemas.\n\n{0}
|
||||
portfolio.pending.failedTrade.txChainInvalid.moveToFailed=El protocolo de intercambio encontró un problema grave.\n\n{0}\n\n¿Quiere mover la operación a intercambios fallidos?\n\nNo puede abrir mediación o arbitraje desde la vista de operaciones fallidas, pero puede mover un intercambio fallido a la pantalla de intercambios abiertos en cualquier momento.
|
||||
portfolio.pending.failedTrade.txChainValid.moveToFailed=El protocolo de intercambio encontró algunos problemas.\n\n{0}\n\nLas transacciones del intercambio se han publicado y los fondos están bloqueados. Mueva la operación a operaciones fallidas solo si está realmente seguro. Podría impedir opciones para resolver el problema.\n\n¿Quiere mover la operación a operaciones fallidas?\n\nNo puede abrir mediación o el arbitraje desde la vista de intercambios fallidos, pero puede mover un intercambio fallido a la pantalla de intercambios abiertos en cualquier momento.
|
||||
|
@ -125,6 +125,7 @@ shared.noDateAvailable=تاریخ موجود نیست
|
||||
shared.noDetailsAvailable=جزئیاتی در دسترس نیست
|
||||
shared.notUsedYet=هنوز مورد استفاده قرار نگرفته
|
||||
shared.date=تاریخ
|
||||
shared.sendFundsDetailsWithFee=ارسال: {0}\n\nبه آدرس گیرنده: {1}\n\nهزینه اضافی ماینر: {2}\n\nآیا مطمئن هستید که میخواهید این مبلغ را ارسال کنید؟
|
||||
# suppress inspection "TrailingSpacesInProperty"
|
||||
shared.sendFundsDetailsDust=Haveno detected that this transaction would create a change output which is below the minimum dust threshold (and therefore not allowed by Monero consensus rules). Instead, this dust ({0} satoshi{1}) will be added to the mining fee.\n\n\n
|
||||
shared.copyToClipboard=کپی در کلیپبورد
|
||||
@ -614,7 +615,8 @@ portfolio.pending.autoConf.state.ERROR=An error at a service request occurred. N
|
||||
# suppress inspection "UnusedProperty"
|
||||
portfolio.pending.autoConf.state.FAILED=A service returned with a failure. No auto-confirm possible.
|
||||
|
||||
portfolio.pending.step1.info=تراکنش سپرده منتشر شده است.\nباید برای حداقل یک تأییدیه بلاک چین قبل از آغاز پرداخت، {0} صبر کنید.
|
||||
portfolio.pending.step1.info.you=تراکنش واریز منتشر شده است.\nشما باید منتظر 10 تاییدیه (حدود 20 دقیقه) باشید تا پرداخت آغاز شود.
|
||||
portfolio.pending.step1.info.buyer=تراکنش واریز منتشر شده است.\nخریدار XMR باید منتظر ۱۰ تاییدیه (حدود ۲۰ دقیقه) باشد تا پرداخت آغاز شود.
|
||||
portfolio.pending.step1.warn=The deposit transaction is still not confirmed. This sometimes happens in rare cases when the funding fee of one trader from an external wallet was too low.
|
||||
portfolio.pending.step1.openForDispute=The deposit transaction is still not confirmed. You can wait longer or contact the mediator for assistance.
|
||||
|
||||
@ -817,11 +819,11 @@ portfolio.pending.mediationResult.popup.alreadyAccepted=You've already accepted
|
||||
|
||||
portfolio.pending.failedTrade.taker.missingTakerFeeTx=The taker fee transaction is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked and no trade fee has been paid. You can move this trade to failed trades.
|
||||
portfolio.pending.failedTrade.maker.missingTakerFeeTx=The peer's taker fee transaction is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked. Your offer is still available to other traders, so you have not lost the maker fee. You can move this trade to failed trades.
|
||||
portfolio.pending.failedTrade.missingDepositTx=The deposit transaction (the 2-of-2 multisig transaction) is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked but your trade fee has been paid. You can make a request to be reimbursed the trade fee here: [HYPERLINK:https://github.com/bisq-network/support/issues]\n\nFeel free to move this trade to failed trades.
|
||||
portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the traditional or crypto payment to the XMR seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing but funds have been locked in the deposit transaction.\n\nIf the buyer is also missing the delayed payout transaction, they will be instructed to NOT send the payment and open a mediation ticket instead. You should also open a mediation ticket with Cmd/Ctrl+o. \n\nIf the buyer has not sent payment yet, the mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). Otherwise the trade amount should go to the buyer. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.errorMsgSet=There was an error during trade protocol execution.\n\nError: {0}\n\nIt might be that this error is not critical, and the trade can be completed normally. If you are unsure, open a mediation ticket to get advice from Haveno mediators. \n\nIf the error was critical and the trade cannot be completed, you might have lost your trade fee. Request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.missingContract=The trade contract is not set.\n\nThe trade cannot be completed and you might have lost your trade fee. If so, you can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.missingDepositTx=The deposit transaction (the 2-of-2 multisig transaction) is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked but your trade fee has been paid. You can make a request to be reimbursed the trade fee here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]\n\nFeel free to move this trade to failed trades.
|
||||
portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the traditional or crypto payment to the XMR seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing but funds have been locked in the deposit transaction.\n\nIf the buyer is also missing the delayed payout transaction, they will be instructed to NOT send the payment and open a mediation ticket instead. You should also open a mediation ticket with Cmd/Ctrl+o. \n\nIf the buyer has not sent payment yet, the mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). Otherwise the trade amount should go to the buyer. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.errorMsgSet=There was an error during trade protocol execution.\n\nError: {0}\n\nIt might be that this error is not critical, and the trade can be completed normally. If you are unsure, open a mediation ticket to get advice from Haveno mediators. \n\nIf the error was critical and the trade cannot be completed, you might have lost your trade fee. Request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.missingContract=The trade contract is not set.\n\nThe trade cannot be completed and you might have lost your trade fee. If so, you can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.info.popup=The trade protocol encountered some problems.\n\n{0}
|
||||
portfolio.pending.failedTrade.txChainInvalid.moveToFailed=The trade protocol encountered a serious problem.\n\n{0}\n\nDo you want to move the trade to failed trades?\n\nYou cannot open mediation or arbitration from the failed trades view, but you can move a failed trade back to the open trades screen any time.
|
||||
portfolio.pending.failedTrade.txChainValid.moveToFailed=The trade protocol encountered some problems.\n\n{0}\n\nThe trade transactions have been published and funds are locked. Only move the trade to failed trades if you are really sure. It might prevent options to resolve the problem.\n\nDo you want to move the trade to failed trades?\n\nYou cannot open mediation or arbitration from the failed trades view, but you can move a failed trade back to the open trades screen any time.
|
||||
|
@ -125,6 +125,7 @@ shared.noDateAvailable=Pas de date disponible
|
||||
shared.noDetailsAvailable=Pas de détails disponibles
|
||||
shared.notUsedYet=Pas encore utilisé
|
||||
shared.date=Date
|
||||
shared.sendFundsDetailsWithFee=Envoyer : {0}\n\nÀ l'adresse de réception : {1}\n\nFrais supplémentaires pour le mineur : {2}\n\nÊtes-vous sûr de vouloir envoyer ce montant ?
|
||||
# suppress inspection "TrailingSpacesInProperty"
|
||||
shared.sendFundsDetailsDust=Haveno détecte que la transaction produira une sortie inférieure au seuil de fraction minimum (non autorisé par les règles de consensus Monero). Au lieu de cela, ces fractions ({0} satoshi {1}) seront ajoutées aux frais de traitement minier.\n\n\n
|
||||
shared.copyToClipboard=Copier dans le presse-papiers
|
||||
@ -615,7 +616,8 @@ portfolio.pending.autoConf.state.ERROR=Une erreur lors de la demande du service
|
||||
# suppress inspection "UnusedProperty"
|
||||
portfolio.pending.autoConf.state.FAILED=Un service a retourné un échec. L'auto-confirmation n'est pas possible.
|
||||
|
||||
portfolio.pending.step1.info=La transaction de dépôt à été publiée.\n{0} devez attendre au moins une confirmation de la blockchain avant d''initier le paiement.
|
||||
portfolio.pending.step1.info.you=La transaction de dépôt a été publiée.\nVous devez attendre 10 confirmations (environ 20 minutes) avant que le paiement ne puisse commencer.
|
||||
portfolio.pending.step1.info.buyer=La transaction de dépôt a été publiée.\nL'acheteur XMR doit attendre 10 confirmations (environ 20 minutes) avant que le paiement puisse commencer.
|
||||
portfolio.pending.step1.warn=La transaction de dépôt n'est toujours pas confirmée. Cela se produit parfois dans de rares occasions lorsque les frais de financement d'un trader en provenance d'un portefeuille externe sont trop bas.
|
||||
portfolio.pending.step1.openForDispute=La transaction de dépôt n'est toujours pas confirmée. Vous pouvez attendre plus longtemps ou contacter le médiateur pour obtenir de l'aide.
|
||||
|
||||
@ -819,11 +821,11 @@ portfolio.pending.mediationResult.popup.alreadyAccepted=Vous avez déjà accept
|
||||
|
||||
portfolio.pending.failedTrade.taker.missingTakerFeeTx=Le frais de transaction du preneur est manquant.\n\nSans ce tx, le trade ne peut être complété. Aucun fonds ont été verrouillés et aucun frais de trade a été payé. Vous pouvez déplacer ce trade vers les trade échoués.
|
||||
portfolio.pending.failedTrade.maker.missingTakerFeeTx=Le frais de transaction du pair preneur est manquant.\n\nSans ce tx, le trade ne peut être complété. Aucun fonds ont été verrouillés. Votre offre est toujours valable pour les autres traders, vous n'avez donc pas perdu le frais de maker. Vous pouvez déplacer ce trade vers les trades échoués.
|
||||
portfolio.pending.failedTrade.missingDepositTx=Cette transaction de marge (transaction multi-signature de 2 à 2) est manquante.\n\nSans ce tx, la transaction ne peut pas être complétée. Aucun fonds n'est bloqué, mais vos frais de transaction sont toujours payés. Vous pouvez lancer une demande de compensation des frais de transaction ici: [HYPERLINK:https://github.com/bisq-network/support/issues] \nN'hésitez pas à déplacer la transaction vers la transaction échouée.
|
||||
portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=La transaction de paiement différée est manquante, mais les fonds ont été verrouillés dans la transaction de dépôt.\n\nVeuillez NE PAS envoyer de Fiat ou d'crypto au vendeur de XMR, car avec le tx de paiement différé, le jugemenbt ne peut être ouvert. À la place, ouvrez un ticket de médiation avec Cmd/Ctrl+O. Le médiateur devrait suggérer que les deux pair reçoivent tous les deux le montant total de leurs dépôts de sécurité (le vendeur aussi doit reçevoir le montant total du trade). De cette manière, il n'y a pas de risque de non sécurité, et seuls les frais du trade sont perdus.\n\nVous pouvez demander le remboursement des frais de trade perdus ici;\n[LIEN:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=La transaction de paiement différée est manquante, mais les fonds ont été verrouillés dans la transaction de dépôt.\n\nSi l'acheteur n'a pas non plus la transaction de paiement différée, il sera informé du fait de ne PAS envoyer le paiement et d'ouvrir un ticket de médiation à la place. Vous devriez aussi ouvrir un ticket de médiation avec Cmd/Ctrl+o.\n\nSi l'acheteur n'a pas encore envoyé le paiement, le médiateur devrait suggérer que les deux pairs reçoivent le montant total de leurs dépôts de sécurité (le vendeur doit aussi reçevoir le montant total du trade). Sinon, le montant du trade revient à l'acheteur.\n\nVous pouvez effectuer une demande de remboursement pour les frais de trade perdus ici: [LIEN:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.errorMsgSet=Il y'a eu une erreur durant l'exécution du protocole de trade.\n\nErreur: {0}\n\nIl est possible que cette erreur ne soit pas critique, et que le trade puisse être complété normalement. Si vous n'en êtes pas sûr, ouvrez un ticket de médiation pour avoir des conseils de la part des médiateurs de Haveno.\n\nSi cette erreur est critique et que le trade ne peut être complété, il est possible que vous ayez perdu le frais du trade. Effectuez une demande de remboursement ici: [LIEN:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.missingContract=Le contrat de trade n'est pas complété.\n\nCe trade ne peut être complété et il est possible que vous ayiez perdu votre frais de trade. Dans ce cas, vous pouvez demander un remboursement des frais de trade perdus ici: [LIEN:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.missingDepositTx=Cette transaction de marge (transaction multi-signature de 2 à 2) est manquante.\n\nSans ce tx, la transaction ne peut pas être complétée. Aucun fonds n'est bloqué, mais vos frais de transaction sont toujours payés. Vous pouvez lancer une demande de compensation des frais de transaction ici: [HYPERLINK:https://github.com/haveno-dex/haveno/issues] \nN'hésitez pas à déplacer la transaction vers la transaction échouée.
|
||||
portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=La transaction de paiement différée est manquante, mais les fonds ont été verrouillés dans la transaction de dépôt.\n\nVeuillez NE PAS envoyer de Fiat ou d'crypto au vendeur de XMR, car avec le tx de paiement différé, le jugemenbt ne peut être ouvert. À la place, ouvrez un ticket de médiation avec Cmd/Ctrl+O. Le médiateur devrait suggérer que les deux pair reçoivent tous les deux le montant total de leurs dépôts de sécurité (le vendeur aussi doit reçevoir le montant total du trade). De cette manière, il n'y a pas de risque de non sécurité, et seuls les frais du trade sont perdus.\n\nVous pouvez demander le remboursement des frais de trade perdus ici;\n[LIEN:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=La transaction de paiement différée est manquante, mais les fonds ont été verrouillés dans la transaction de dépôt.\n\nSi l'acheteur n'a pas non plus la transaction de paiement différée, il sera informé du fait de ne PAS envoyer le paiement et d'ouvrir un ticket de médiation à la place. Vous devriez aussi ouvrir un ticket de médiation avec Cmd/Ctrl+o.\n\nSi l'acheteur n'a pas encore envoyé le paiement, le médiateur devrait suggérer que les deux pairs reçoivent le montant total de leurs dépôts de sécurité (le vendeur doit aussi reçevoir le montant total du trade). Sinon, le montant du trade revient à l'acheteur.\n\nVous pouvez effectuer une demande de remboursement pour les frais de trade perdus ici: [LIEN:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.errorMsgSet=Il y'a eu une erreur durant l'exécution du protocole de trade.\n\nErreur: {0}\n\nIl est possible que cette erreur ne soit pas critique, et que le trade puisse être complété normalement. Si vous n'en êtes pas sûr, ouvrez un ticket de médiation pour avoir des conseils de la part des médiateurs de Haveno.\n\nSi cette erreur est critique et que le trade ne peut être complété, il est possible que vous ayez perdu le frais du trade. Effectuez une demande de remboursement ici: [LIEN:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.missingContract=Le contrat de trade n'est pas complété.\n\nCe trade ne peut être complété et il est possible que vous ayiez perdu votre frais de trade. Dans ce cas, vous pouvez demander un remboursement des frais de trade perdus ici: [LIEN:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.info.popup=Le protocole de trade a rencontré quelques problèmes/\n\n{0}
|
||||
portfolio.pending.failedTrade.txChainInvalid.moveToFailed=Le protocole de trade a rencontré un problème critique.\n\n{0}\n\nVoulez-vous déplacer ce trade vers les trades échoués?\n\nVous ne pouvez pas ouvrir de médiations ou de jugements depuis la liste des trades échoués, mais vous pouvez redéplacer un trade échoué vers l'écran des trades ouverts quand vous le souhaitez.
|
||||
portfolio.pending.failedTrade.txChainValid.moveToFailed=Il y a des problèmes avec cet accord de transaction. \n\n{0}\n\nLa transaction de devis a été validée et les fonds ont été bloqués. Déplacer la transaction vers une transaction échouée uniquement si elle est certaine. Cela peut empêcher les options disponibles pour résoudre le problème. \n\nÊtes-vous sûr de vouloir déplacer cette transaction vers la transaction échouée? \n\nVous ne pouvez pas ouvrir une médiation ou un arbitrage dans une transaction échouée, mais vous pouvez déplacer une transaction échouée vers la transaction incomplète à tout moment.
|
||||
|
@ -125,6 +125,7 @@ shared.noDateAvailable=Nessuna data disponibile
|
||||
shared.noDetailsAvailable=Dettagli non disponibili
|
||||
shared.notUsedYet=Non ancora usato
|
||||
shared.date=Data
|
||||
shared.sendFundsDetailsWithFee=Invio: {0}\n\nAll'indirizzo di ricezione: {1}\n\nCommissione mineraria aggiuntiva: {2}\n\nSei sicuro di voler inviare questa somma?
|
||||
# suppress inspection "TrailingSpacesInProperty"
|
||||
shared.sendFundsDetailsDust=Haveno detected that this transaction would create a change output which is below the minimum dust threshold (and therefore not allowed by Monero consensus rules). Instead, this dust ({0} satoshi{1}) will be added to the mining fee.\n\n\n
|
||||
shared.copyToClipboard=Copia negli appunti
|
||||
@ -614,7 +615,8 @@ portfolio.pending.autoConf.state.ERROR=An error at a service request occurred. N
|
||||
# suppress inspection "UnusedProperty"
|
||||
portfolio.pending.autoConf.state.FAILED=A service returned with a failure. No auto-confirm possible.
|
||||
|
||||
portfolio.pending.step1.info=La transazione di deposito è stata pubblicata.\n {0} deve attendere almeno una conferma dalla blockchain prima di avviare il pagamento.
|
||||
portfolio.pending.step1.info.you=La transazione di deposito è stata pubblicata.\nDevi aspettare 10 conferme (circa 20 minuti) prima che il pagamento possa iniziare.
|
||||
portfolio.pending.step1.info.buyer=La transazione di deposito è stata pubblicata.\nL'acquirente XMR deve aspettare 10 conferme (circa 20 minuti) prima che il pagamento possa iniziare.
|
||||
portfolio.pending.step1.warn=La transazione di deposito non è ancora confermata. Questo accade raramente e nel caso in cui la commissione di transazione di un trader proveniente da un portafoglio esterno è troppo bassa.
|
||||
portfolio.pending.step1.openForDispute=La transazione di deposito non è ancora confermata. Puoi attendere più a lungo o contattare il mediatore per ricevere assistenza.
|
||||
|
||||
@ -817,11 +819,11 @@ portfolio.pending.mediationResult.popup.alreadyAccepted=Hai già accettato
|
||||
|
||||
portfolio.pending.failedTrade.taker.missingTakerFeeTx=The taker fee transaction is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked and no trade fee has been paid. You can move this trade to failed trades.
|
||||
portfolio.pending.failedTrade.maker.missingTakerFeeTx=The peer's taker fee transaction is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked. Your offer is still available to other traders, so you have not lost the maker fee. You can move this trade to failed trades.
|
||||
portfolio.pending.failedTrade.missingDepositTx=The deposit transaction (the 2-of-2 multisig transaction) is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked but your trade fee has been paid. You can make a request to be reimbursed the trade fee here: [HYPERLINK:https://github.com/bisq-network/support/issues]\n\nFeel free to move this trade to failed trades.
|
||||
portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the traditional or crypto payment to the XMR seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing but funds have been locked in the deposit transaction.\n\nIf the buyer is also missing the delayed payout transaction, they will be instructed to NOT send the payment and open a mediation ticket instead. You should also open a mediation ticket with Cmd/Ctrl+o. \n\nIf the buyer has not sent payment yet, the mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). Otherwise the trade amount should go to the buyer. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.errorMsgSet=There was an error during trade protocol execution.\n\nError: {0}\n\nIt might be that this error is not critical, and the trade can be completed normally. If you are unsure, open a mediation ticket to get advice from Haveno mediators. \n\nIf the error was critical and the trade cannot be completed, you might have lost your trade fee. Request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.missingContract=The trade contract is not set.\n\nThe trade cannot be completed and you might have lost your trade fee. If so, you can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.missingDepositTx=The deposit transaction (the 2-of-2 multisig transaction) is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked but your trade fee has been paid. You can make a request to be reimbursed the trade fee here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]\n\nFeel free to move this trade to failed trades.
|
||||
portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the traditional or crypto payment to the XMR seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing but funds have been locked in the deposit transaction.\n\nIf the buyer is also missing the delayed payout transaction, they will be instructed to NOT send the payment and open a mediation ticket instead. You should also open a mediation ticket with Cmd/Ctrl+o. \n\nIf the buyer has not sent payment yet, the mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). Otherwise the trade amount should go to the buyer. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.errorMsgSet=There was an error during trade protocol execution.\n\nError: {0}\n\nIt might be that this error is not critical, and the trade can be completed normally. If you are unsure, open a mediation ticket to get advice from Haveno mediators. \n\nIf the error was critical and the trade cannot be completed, you might have lost your trade fee. Request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.missingContract=The trade contract is not set.\n\nThe trade cannot be completed and you might have lost your trade fee. If so, you can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.info.popup=The trade protocol encountered some problems.\n\n{0}
|
||||
portfolio.pending.failedTrade.txChainInvalid.moveToFailed=The trade protocol encountered a serious problem.\n\n{0}\n\nDo you want to move the trade to failed trades?\n\nYou cannot open mediation or arbitration from the failed trades view, but you can move a failed trade back to the open trades screen any time.
|
||||
portfolio.pending.failedTrade.txChainValid.moveToFailed=The trade protocol encountered some problems.\n\n{0}\n\nThe trade transactions have been published and funds are locked. Only move the trade to failed trades if you are really sure. It might prevent options to resolve the problem.\n\nDo you want to move the trade to failed trades?\n\nYou cannot open mediation or arbitration from the failed trades view, but you can move a failed trade back to the open trades screen any time.
|
||||
|
@ -125,6 +125,7 @@ shared.noDateAvailable=日付がありません
|
||||
shared.noDetailsAvailable=詳細不明
|
||||
shared.notUsedYet=未使用
|
||||
shared.date=日付
|
||||
shared.sendFundsDetailsWithFee=送信中: {0}\n\n受取アドレス: {1}\n\n追加のマイナー手数料: {2}\n\nこの金額を送信してもよろしいですか?
|
||||
# suppress inspection "TrailingSpacesInProperty"
|
||||
shared.sendFundsDetailsDust=Havenoがこのトランザクションはダストの最小閾値以下のおつりアウトプットを生じることを検出しました(それにしたがって、ビットコインのコンセンサス・ルールによって許されない)。代わりに、その ({0} satoshi{1}) のダストはマイニング手数料に追加されます。\n\n\n
|
||||
shared.copyToClipboard=クリップボードにコピー
|
||||
@ -615,7 +616,8 @@ portfolio.pending.autoConf.state.ERROR=サービスリクエストにはエラ
|
||||
# suppress inspection "UnusedProperty"
|
||||
portfolio.pending.autoConf.state.FAILED=サービスは失敗を返しました。自動確認できません。
|
||||
|
||||
portfolio.pending.step1.info=デポジットトランザクションが発行されました。\n{0}は、支払いを開始する前に少なくとも1つのブロックチェーンの承認を待つ必要があります。
|
||||
portfolio.pending.step1.info.you=入金トランザクションが公開されました。\n支払いが開始されるまで、10回の確認(約20分)を待つ必要があります。
|
||||
portfolio.pending.step1.info.buyer=入金トランザクションが公開されました。\nXMRの購入者は、支払いを開始する前に10回の確認(約20分)を待つ必要があります。
|
||||
portfolio.pending.step1.warn=デポジットトランザクションがまだ承認されていません。外部ウォレットからの取引者の資金調達手数料が低すぎるときには、例外的なケースで起こるかもしれません。
|
||||
portfolio.pending.step1.openForDispute=デポジットトランザクションがまだ承認されていません。もう少し待つか、助けを求めて調停人に連絡できます。
|
||||
|
||||
@ -818,11 +820,11 @@ portfolio.pending.mediationResult.popup.alreadyAccepted=すでに受け入れて
|
||||
|
||||
portfolio.pending.failedTrade.taker.missingTakerFeeTx=欠測テイカー手数料のトランザクション。\n\nこのtxがなければ、トレードを完了できません。資金はロックされず、トレード手数料は支払いませんでした。「失敗トレード」へ送ることができます。
|
||||
portfolio.pending.failedTrade.maker.missingTakerFeeTx=ピアのテイカー手数料のトランザクションは欠測します。\n\nこのtxがなければ、トレードを完了できません。資金はロックされませんでした。あなたのオファーがまだ他の取引者には有効ですので、メイカー手数料は失っていません。このトレードを「失敗トレード」へ送ることができます。
|
||||
portfolio.pending.failedTrade.missingDepositTx=入金トランザクション(2-of-2マルチシグトランザクション)は欠測します。\n\nこのtxがなければ、トレードを完了できません。資金はロックされませんでしたが、トレード手数料は支払いました。トレード手数料の返済要求はここから提出できます: [HYPERLINK:https://github.com/bisq-network/support/issues]\n\nこのトレードを「失敗トレード」へ送れます。
|
||||
portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=遅延支払いトランザクションは欠測しますが、資金は入金トランザクションにロックされました。\n\nこの法定通貨・アルトコイン支払いをXMR売り手に送信しないで下さい。遅延支払いtxがなければ、係争仲裁は開始されることができません。代りに、「Cmd/Ctrl+o」で調停チケットをオープンして下さい。調停者はおそらく両方のピアへセキュリティデポジットの全額を払い戻しを提案します(売り手はトレード金額も払い戻しを受ける)。このような方法でセキュリティーのリスクがなし、トレード手数料のみが失われます。\n\n失われたトレード手数料の払い戻し要求はここから提出できます: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=遅延支払いトランザクションは欠測しますが、資金は入金トランザクションにロックされました。\n\n買い手の遅延支払いトランザクションが同じく欠測される場合、相手は支払いを送信せず調停チケットをオープンするように指示されます。同様に「Cmd/Ctrl+o」で調停チケットをオープンするのは賢明でしょう。\n\n買い手はまだ支払いを送信しなかった場合、調停者はおそらく両方のピアへセキュリティデポジットの全額を払い戻しを提案します(売り手はトレード金額も払い戻しを受ける)。さもなければ、トレード金額は買い手に支払われるでしょう。\n\n失われたトレード手数料の払い戻し要求はここから提出できます: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.errorMsgSet=トレードプロトコルの実行にはエラーが生じました。\n\nエラー: {0}\n\nクリティカル・エラーではない可能性はあり、トレードは普通に完了できるかもしれない。迷う場合は調停チケットをオープンして、Haveno調停者からアドバイスを受けることができます。\n\nクリティカル・エラーでトレードが完了できなかった場合はトレード手数料は失われた可能性があります。失われたトレード手数料の払い戻し要求はここから提出できます: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.missingContract=トレード契約書は設定されません。\n\nトレードは完了できません。トレード手数料は失われた可能性もあります。その場合は失われたトレード手数料の払い戻し要求はここから提出できます: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.missingDepositTx=入金トランザクション(2-of-2マルチシグトランザクション)は欠測します。\n\nこのtxがなければ、トレードを完了できません。資金はロックされませんでしたが、トレード手数料は支払いました。トレード手数料の返済要求はここから提出できます: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]\n\nこのトレードを「失敗トレード」へ送れます。
|
||||
portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=遅延支払いトランザクションは欠測しますが、資金は入金トランザクションにロックされました。\n\nこの法定通貨・アルトコイン支払いをXMR売り手に送信しないで下さい。遅延支払いtxがなければ、係争仲裁は開始されることができません。代りに、「Cmd/Ctrl+o」で調停チケットをオープンして下さい。調停者はおそらく両方のピアへセキュリティデポジットの全額を払い戻しを提案します(売り手はトレード金額も払い戻しを受ける)。このような方法でセキュリティーのリスクがなし、トレード手数料のみが失われます。\n\n失われたトレード手数料の払い戻し要求はここから提出できます: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=遅延支払いトランザクションは欠測しますが、資金は入金トランザクションにロックされました。\n\n買い手の遅延支払いトランザクションが同じく欠測される場合、相手は支払いを送信せず調停チケットをオープンするように指示されます。同様に「Cmd/Ctrl+o」で調停チケットをオープンするのは賢明でしょう。\n\n買い手はまだ支払いを送信しなかった場合、調停者はおそらく両方のピアへセキュリティデポジットの全額を払い戻しを提案します(売り手はトレード金額も払い戻しを受ける)。さもなければ、トレード金額は買い手に支払われるでしょう。\n\n失われたトレード手数料の払い戻し要求はここから提出できます: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.errorMsgSet=トレードプロトコルの実行にはエラーが生じました。\n\nエラー: {0}\n\nクリティカル・エラーではない可能性はあり、トレードは普通に完了できるかもしれない。迷う場合は調停チケットをオープンして、Haveno調停者からアドバイスを受けることができます。\n\nクリティカル・エラーでトレードが完了できなかった場合はトレード手数料は失われた可能性があります。失われたトレード手数料の払い戻し要求はここから提出できます: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.missingContract=トレード契約書は設定されません。\n\nトレードは完了できません。トレード手数料は失われた可能性もあります。その場合は失われたトレード手数料の払い戻し要求はここから提出できます: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.info.popup=トレードプロトコルは問題に遭遇しました。\n\n{0}
|
||||
portfolio.pending.failedTrade.txChainInvalid.moveToFailed=トレードプロトコルは深刻な問題に遭遇しました。\n\n{0}\n\nトレードを「失敗トレード」へ送りますか?\n\n「失敗トレード」画面から調停・仲裁を開始できませんけど、失敗トレードがいつでも「オープントレード」へ戻されることができます。
|
||||
portfolio.pending.failedTrade.txChainValid.moveToFailed=トレードプロトコルは問題に遭遇しました。\n\n{0}\n\nトレードのトランザクションは公開され、資金はロックされました。絶対に確信している場合のみにトレードを「失敗トレード」へ送りましょう。問題を解決できる選択肢に邪魔する可能性はあります。\n\nトレードを「失敗トレード」へ送りますか?\n\n「失敗トレード」画面から調停・仲裁を開始できませんけど、失敗トレードがいつでも「オープントレード」へ戻されることができます。
|
||||
|
@ -125,6 +125,7 @@ shared.noDateAvailable=Sem data disponível
|
||||
shared.noDetailsAvailable=Sem detalhes disponíveis
|
||||
shared.notUsedYet=Ainda não usado
|
||||
shared.date=Data
|
||||
shared.sendFundsDetailsWithFee=Enviando: {0}\n\nPara o endereço de recebimento: {1}\n\nTaxa adicional do minerador: {2}\n\nTem certeza de que deseja enviar esse valor?
|
||||
# suppress inspection "TrailingSpacesInProperty"
|
||||
shared.sendFundsDetailsDust=Haveno detected that this transaction would create a change output which is below the minimum dust threshold (and therefore not allowed by Monero consensus rules). Instead, this dust ({0} satoshi{1}) will be added to the mining fee.\n\n\n
|
||||
shared.copyToClipboard=Copiar para área de transferência
|
||||
@ -617,7 +618,8 @@ portfolio.pending.autoConf.state.ERROR=An error at a service request occurred. N
|
||||
# suppress inspection "UnusedProperty"
|
||||
portfolio.pending.autoConf.state.FAILED=A service returned with a failure. No auto-confirm possible.
|
||||
|
||||
portfolio.pending.step1.info=A transação de depósito foi publicada\n{0} precisa esperar ao menos uma confirmação da blockchain antes de iniciar o pagamento.
|
||||
portfolio.pending.step1.info.you=A transação de depósito foi publicada.\nVocê precisa aguardar 10 confirmações (cerca de 20 minutos) antes que o pagamento possa começar.
|
||||
portfolio.pending.step1.info.buyer=A transação de depósito foi publicada.\nO comprador de XMR precisa aguardar 10 confirmações (cerca de 20 minutos) antes que o pagamento possa ser iniciado.
|
||||
portfolio.pending.step1.warn=A transação do depósito ainda não foi confirmada.\nIsto pode ocorrer em casos raros em que a taxa de financiamento de um dos negociadores enviada a partir de uma carteira externa foi muito baixa.
|
||||
portfolio.pending.step1.openForDispute=A transação de depósito ainda não foi confirmada. Você pode aguardar um pouco mais ou entrar em contato com o mediador para pedir assistência.
|
||||
|
||||
@ -820,11 +822,11 @@ portfolio.pending.mediationResult.popup.alreadyAccepted=Você já aceitou
|
||||
|
||||
portfolio.pending.failedTrade.taker.missingTakerFeeTx=The taker fee transaction is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked and no trade fee has been paid. You can move this trade to failed trades.
|
||||
portfolio.pending.failedTrade.maker.missingTakerFeeTx=The peer's taker fee transaction is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked. Your offer is still available to other traders, so you have not lost the maker fee. You can move this trade to failed trades.
|
||||
portfolio.pending.failedTrade.missingDepositTx=The deposit transaction (the 2-of-2 multisig transaction) is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked but your trade fee has been paid. You can make a request to be reimbursed the trade fee here: [HYPERLINK:https://github.com/bisq-network/support/issues]\n\nFeel free to move this trade to failed trades.
|
||||
portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the traditional or crypto payment to the XMR seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing but funds have been locked in the deposit transaction.\n\nIf the buyer is also missing the delayed payout transaction, they will be instructed to NOT send the payment and open a mediation ticket instead. You should also open a mediation ticket with Cmd/Ctrl+o. \n\nIf the buyer has not sent payment yet, the mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). Otherwise the trade amount should go to the buyer. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.errorMsgSet=There was an error during trade protocol execution.\n\nError: {0}\n\nIt might be that this error is not critical, and the trade can be completed normally. If you are unsure, open a mediation ticket to get advice from Haveno mediators. \n\nIf the error was critical and the trade cannot be completed, you might have lost your trade fee. Request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.missingContract=The trade contract is not set.\n\nThe trade cannot be completed and you might have lost your trade fee. If so, you can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.missingDepositTx=The deposit transaction (the 2-of-2 multisig transaction) is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked but your trade fee has been paid. You can make a request to be reimbursed the trade fee here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]\n\nFeel free to move this trade to failed trades.
|
||||
portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the traditional or crypto payment to the XMR seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing but funds have been locked in the deposit transaction.\n\nIf the buyer is also missing the delayed payout transaction, they will be instructed to NOT send the payment and open a mediation ticket instead. You should also open a mediation ticket with Cmd/Ctrl+o. \n\nIf the buyer has not sent payment yet, the mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). Otherwise the trade amount should go to the buyer. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.errorMsgSet=There was an error during trade protocol execution.\n\nError: {0}\n\nIt might be that this error is not critical, and the trade can be completed normally. If you are unsure, open a mediation ticket to get advice from Haveno mediators. \n\nIf the error was critical and the trade cannot be completed, you might have lost your trade fee. Request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.missingContract=The trade contract is not set.\n\nThe trade cannot be completed and you might have lost your trade fee. If so, you can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.info.popup=The trade protocol encountered some problems.\n\n{0}
|
||||
portfolio.pending.failedTrade.txChainInvalid.moveToFailed=The trade protocol encountered a serious problem.\n\n{0}\n\nDo you want to move the trade to failed trades?\n\nYou cannot open mediation or arbitration from the failed trades view, but you can move a failed trade back to the open trades screen any time.
|
||||
portfolio.pending.failedTrade.txChainValid.moveToFailed=The trade protocol encountered some problems.\n\n{0}\n\nThe trade transactions have been published and funds are locked. Only move the trade to failed trades if you are really sure. It might prevent options to resolve the problem.\n\nDo you want to move the trade to failed trades?\n\nYou cannot open mediation or arbitration from the failed trades view, but you can move a failed trade back to the open trades screen any time.
|
||||
|
@ -125,6 +125,7 @@ shared.noDateAvailable=Sem dada disponível
|
||||
shared.noDetailsAvailable=Sem detalhes disponíveis
|
||||
shared.notUsedYet=Ainda não usado
|
||||
shared.date=Data
|
||||
shared.sendFundsDetailsWithFee=Enviando: {0}\n\nPara o endereço de recebimento: {1}\n\nTaxa adicional do minerador: {2}\n\nTem certeza de que deseja enviar este valor?
|
||||
# suppress inspection "TrailingSpacesInProperty"
|
||||
shared.sendFundsDetailsDust=Haveno detected that this transaction would create a change output which is below the minimum dust threshold (and therefore not allowed by Monero consensus rules). Instead, this dust ({0} satoshi{1}) will be added to the mining fee.\n\n\n
|
||||
shared.copyToClipboard=Copiar para área de transferência
|
||||
@ -614,7 +615,8 @@ portfolio.pending.autoConf.state.ERROR=An error at a service request occurred. N
|
||||
# suppress inspection "UnusedProperty"
|
||||
portfolio.pending.autoConf.state.FAILED=A service returned with a failure. No auto-confirm possible.
|
||||
|
||||
portfolio.pending.step1.info=A transação de depósito foi publicada.\n{0} precisa aguardar pelo menos uma confirmação da blockchain antes de iniciar o pagamento.
|
||||
portfolio.pending.step1.info.you=A transação de depósito foi publicada.\nVocê precisa aguardar 10 confirmações (cerca de 20 minutos) antes que o pagamento possa começar.
|
||||
portfolio.pending.step1.info.buyer=A transação de depósito foi publicada.\nO comprador de XMR precisa aguardar 10 confirmações (cerca de 20 minutos) antes que o pagamento possa ser iniciado.
|
||||
portfolio.pending.step1.warn=A transação de depósito ainda não foi confirmada. Isso pode acontecer em casos raros, quando a taxa de financiamento de um negociador proveniente de uma carteira externa foi muito baixa.
|
||||
portfolio.pending.step1.openForDispute=A transação de depósito ainda não foi confirmada. Você pode esperar mais tempo ou entrar em contato com o mediador para obter assistência.
|
||||
|
||||
@ -817,11 +819,11 @@ portfolio.pending.mediationResult.popup.alreadyAccepted=Você já aceitou
|
||||
|
||||
portfolio.pending.failedTrade.taker.missingTakerFeeTx=The taker fee transaction is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked and no trade fee has been paid. You can move this trade to failed trades.
|
||||
portfolio.pending.failedTrade.maker.missingTakerFeeTx=The peer's taker fee transaction is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked. Your offer is still available to other traders, so you have not lost the maker fee. You can move this trade to failed trades.
|
||||
portfolio.pending.failedTrade.missingDepositTx=The deposit transaction (the 2-of-2 multisig transaction) is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked but your trade fee has been paid. You can make a request to be reimbursed the trade fee here: [HYPERLINK:https://github.com/bisq-network/support/issues]\n\nFeel free to move this trade to failed trades.
|
||||
portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the traditional or crypto payment to the XMR seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing but funds have been locked in the deposit transaction.\n\nIf the buyer is also missing the delayed payout transaction, they will be instructed to NOT send the payment and open a mediation ticket instead. You should also open a mediation ticket with Cmd/Ctrl+o. \n\nIf the buyer has not sent payment yet, the mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). Otherwise the trade amount should go to the buyer. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.errorMsgSet=There was an error during trade protocol execution.\n\nError: {0}\n\nIt might be that this error is not critical, and the trade can be completed normally. If you are unsure, open a mediation ticket to get advice from Haveno mediators. \n\nIf the error was critical and the trade cannot be completed, you might have lost your trade fee. Request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.missingContract=The trade contract is not set.\n\nThe trade cannot be completed and you might have lost your trade fee. If so, you can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.missingDepositTx=The deposit transaction (the 2-of-2 multisig transaction) is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked but your trade fee has been paid. You can make a request to be reimbursed the trade fee here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]\n\nFeel free to move this trade to failed trades.
|
||||
portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the traditional or crypto payment to the XMR seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing but funds have been locked in the deposit transaction.\n\nIf the buyer is also missing the delayed payout transaction, they will be instructed to NOT send the payment and open a mediation ticket instead. You should also open a mediation ticket with Cmd/Ctrl+o. \n\nIf the buyer has not sent payment yet, the mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). Otherwise the trade amount should go to the buyer. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.errorMsgSet=There was an error during trade protocol execution.\n\nError: {0}\n\nIt might be that this error is not critical, and the trade can be completed normally. If you are unsure, open a mediation ticket to get advice from Haveno mediators. \n\nIf the error was critical and the trade cannot be completed, you might have lost your trade fee. Request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.missingContract=The trade contract is not set.\n\nThe trade cannot be completed and you might have lost your trade fee. If so, you can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.info.popup=The trade protocol encountered some problems.\n\n{0}
|
||||
portfolio.pending.failedTrade.txChainInvalid.moveToFailed=The trade protocol encountered a serious problem.\n\n{0}\n\nDo you want to move the trade to failed trades?\n\nYou cannot open mediation or arbitration from the failed trades view, but you can move a failed trade back to the open trades screen any time.
|
||||
portfolio.pending.failedTrade.txChainValid.moveToFailed=The trade protocol encountered some problems.\n\n{0}\n\nThe trade transactions have been published and funds are locked. Only move the trade to failed trades if you are really sure. It might prevent options to resolve the problem.\n\nDo you want to move the trade to failed trades?\n\nYou cannot open mediation or arbitration from the failed trades view, but you can move a failed trade back to the open trades screen any time.
|
||||
|
@ -125,6 +125,7 @@ shared.noDateAvailable=Дата не указана
|
||||
shared.noDetailsAvailable=Подробности не указаны
|
||||
shared.notUsedYet=Ещё не использовано
|
||||
shared.date=Дата
|
||||
shared.sendFundsDetailsWithFee=Отправка: {0}\n\nНа получающий адрес: {1}\n\nДополнительная комиссия майнера: {2}\n\nВы уверены, что хотите отправить эту сумму?
|
||||
# suppress inspection "TrailingSpacesInProperty"
|
||||
shared.sendFundsDetailsDust=Haveno detected that this transaction would create a change output which is below the minimum dust threshold (and therefore not allowed by Monero consensus rules). Instead, this dust ({0} satoshi{1}) will be added to the mining fee.\n\n\n
|
||||
shared.copyToClipboard=Скопировать в буфер
|
||||
@ -614,7 +615,8 @@ portfolio.pending.autoConf.state.ERROR=An error at a service request occurred. N
|
||||
# suppress inspection "UnusedProperty"
|
||||
portfolio.pending.autoConf.state.FAILED=A service returned with a failure. No auto-confirm possible.
|
||||
|
||||
portfolio.pending.step1.info=Депозитная транзакция опубликована.\n{0} должен дождаться хотя бы одного подтверждения в блокчейне перед началом платежа.
|
||||
portfolio.pending.step1.info.you=Транзакция депозита была опубликована.\nВам нужно дождаться 10 подтверждений (около 20 минут), прежде чем платеж сможет начаться.
|
||||
portfolio.pending.step1.info.buyer=Транзакция депозита была опубликована.\nПокупатель XMR должен подождать 10 подтверждений (около 20 минут), прежде чем платеж может быть начат.
|
||||
portfolio.pending.step1.warn=The deposit transaction is still not confirmed. This sometimes happens in rare cases when the funding fee of one trader from an external wallet was too low.
|
||||
portfolio.pending.step1.openForDispute=The deposit transaction is still not confirmed. You can wait longer or contact the mediator for assistance.
|
||||
|
||||
@ -817,11 +819,11 @@ portfolio.pending.mediationResult.popup.alreadyAccepted=You've already accepted
|
||||
|
||||
portfolio.pending.failedTrade.taker.missingTakerFeeTx=The taker fee transaction is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked and no trade fee has been paid. You can move this trade to failed trades.
|
||||
portfolio.pending.failedTrade.maker.missingTakerFeeTx=The peer's taker fee transaction is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked. Your offer is still available to other traders, so you have not lost the maker fee. You can move this trade to failed trades.
|
||||
portfolio.pending.failedTrade.missingDepositTx=The deposit transaction (the 2-of-2 multisig transaction) is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked but your trade fee has been paid. You can make a request to be reimbursed the trade fee here: [HYPERLINK:https://github.com/bisq-network/support/issues]\n\nFeel free to move this trade to failed trades.
|
||||
portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the traditional or crypto payment to the XMR seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing but funds have been locked in the deposit transaction.\n\nIf the buyer is also missing the delayed payout transaction, they will be instructed to NOT send the payment and open a mediation ticket instead. You should also open a mediation ticket with Cmd/Ctrl+o. \n\nIf the buyer has not sent payment yet, the mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). Otherwise the trade amount should go to the buyer. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.errorMsgSet=There was an error during trade protocol execution.\n\nError: {0}\n\nIt might be that this error is not critical, and the trade can be completed normally. If you are unsure, open a mediation ticket to get advice from Haveno mediators. \n\nIf the error was critical and the trade cannot be completed, you might have lost your trade fee. Request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.missingContract=The trade contract is not set.\n\nThe trade cannot be completed and you might have lost your trade fee. If so, you can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.missingDepositTx=The deposit transaction (the 2-of-2 multisig transaction) is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked but your trade fee has been paid. You can make a request to be reimbursed the trade fee here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]\n\nFeel free to move this trade to failed trades.
|
||||
portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the traditional or crypto payment to the XMR seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing but funds have been locked in the deposit transaction.\n\nIf the buyer is also missing the delayed payout transaction, they will be instructed to NOT send the payment and open a mediation ticket instead. You should also open a mediation ticket with Cmd/Ctrl+o. \n\nIf the buyer has not sent payment yet, the mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). Otherwise the trade amount should go to the buyer. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.errorMsgSet=There was an error during trade protocol execution.\n\nError: {0}\n\nIt might be that this error is not critical, and the trade can be completed normally. If you are unsure, open a mediation ticket to get advice from Haveno mediators. \n\nIf the error was critical and the trade cannot be completed, you might have lost your trade fee. Request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.missingContract=The trade contract is not set.\n\nThe trade cannot be completed and you might have lost your trade fee. If so, you can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.info.popup=The trade protocol encountered some problems.\n\n{0}
|
||||
portfolio.pending.failedTrade.txChainInvalid.moveToFailed=The trade protocol encountered a serious problem.\n\n{0}\n\nDo you want to move the trade to failed trades?\n\nYou cannot open mediation or arbitration from the failed trades view, but you can move a failed trade back to the open trades screen any time.
|
||||
portfolio.pending.failedTrade.txChainValid.moveToFailed=The trade protocol encountered some problems.\n\n{0}\n\nThe trade transactions have been published and funds are locked. Only move the trade to failed trades if you are really sure. It might prevent options to resolve the problem.\n\nDo you want to move the trade to failed trades?\n\nYou cannot open mediation or arbitration from the failed trades view, but you can move a failed trade back to the open trades screen any time.
|
||||
|
@ -125,6 +125,7 @@ shared.noDateAvailable=ไม่มีวันที่ให้แสดง
|
||||
shared.noDetailsAvailable=ไม่มีรายละเอียด
|
||||
shared.notUsedYet=ยังไม่ได้ใช้งาน
|
||||
shared.date=วันที่
|
||||
shared.sendFundsDetailsWithFee=กำลังส่ง: {0}\n\nไปยังที่อยู่ผู้รับ: {1}\n\nค่าธรรมเนียมเหมืองเพิ่มเติม: {2}\n\nคุณแน่ใจหรือไม่ว่าต้องการส่งจำนวนนี้?
|
||||
# suppress inspection "TrailingSpacesInProperty"
|
||||
shared.sendFundsDetailsDust=Haveno detected that this transaction would create a change output which is below the minimum dust threshold (and therefore not allowed by Monero consensus rules). Instead, this dust ({0} satoshi{1}) will be added to the mining fee.\n\n\n
|
||||
shared.copyToClipboard=คัดลอกไปที่คลิปบอร์ด
|
||||
@ -614,7 +615,8 @@ portfolio.pending.autoConf.state.ERROR=An error at a service request occurred. N
|
||||
# suppress inspection "UnusedProperty"
|
||||
portfolio.pending.autoConf.state.FAILED=A service returned with a failure. No auto-confirm possible.
|
||||
|
||||
portfolio.pending.step1.info=ธุรกรรมเงินฝากได้รับการเผยแพร่แล้ว\n{0} ต้องรอการยืนยันของบล็อกเชนอย่างน้อยหนึ่งครั้งก่อนที่จะเริ่มการชำระเงิน
|
||||
portfolio.pending.step1.info.you=ธุรกรรมการฝากเงินได้รับการเผยแพร่แล้ว\nคุณต้องรอ 10 คอนเฟิร์ม (ประมาณ 20 นาที) ก่อนที่การชำระเงินจะเริ่มต้น
|
||||
portfolio.pending.step1.info.buyer=การทำธุรกรรมฝากเงินได้รับการเผยแพร่แล้ว。\nผู้ซื้อ XMR จำเป็นต้องรอการยืนยัน 10 ครั้ง (ประมาณ 20 นาที) ก่อนที่การชำระเงินจะเริ่มต้นได้
|
||||
portfolio.pending.step1.warn=The deposit transaction is still not confirmed. This sometimes happens in rare cases when the funding fee of one trader from an external wallet was too low.
|
||||
portfolio.pending.step1.openForDispute=The deposit transaction is still not confirmed. You can wait longer or contact the mediator for assistance.
|
||||
|
||||
@ -817,11 +819,11 @@ portfolio.pending.mediationResult.popup.alreadyAccepted=You've already accepted
|
||||
|
||||
portfolio.pending.failedTrade.taker.missingTakerFeeTx=The taker fee transaction is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked and no trade fee has been paid. You can move this trade to failed trades.
|
||||
portfolio.pending.failedTrade.maker.missingTakerFeeTx=The peer's taker fee transaction is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked. Your offer is still available to other traders, so you have not lost the maker fee. You can move this trade to failed trades.
|
||||
portfolio.pending.failedTrade.missingDepositTx=The deposit transaction (the 2-of-2 multisig transaction) is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked but your trade fee has been paid. You can make a request to be reimbursed the trade fee here: [HYPERLINK:https://github.com/bisq-network/support/issues]\n\nFeel free to move this trade to failed trades.
|
||||
portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the traditional or crypto payment to the XMR seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing but funds have been locked in the deposit transaction.\n\nIf the buyer is also missing the delayed payout transaction, they will be instructed to NOT send the payment and open a mediation ticket instead. You should also open a mediation ticket with Cmd/Ctrl+o. \n\nIf the buyer has not sent payment yet, the mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). Otherwise the trade amount should go to the buyer. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.errorMsgSet=There was an error during trade protocol execution.\n\nError: {0}\n\nIt might be that this error is not critical, and the trade can be completed normally. If you are unsure, open a mediation ticket to get advice from Haveno mediators. \n\nIf the error was critical and the trade cannot be completed, you might have lost your trade fee. Request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.missingContract=The trade contract is not set.\n\nThe trade cannot be completed and you might have lost your trade fee. If so, you can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.missingDepositTx=The deposit transaction (the 2-of-2 multisig transaction) is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked but your trade fee has been paid. You can make a request to be reimbursed the trade fee here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]\n\nFeel free to move this trade to failed trades.
|
||||
portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the traditional or crypto payment to the XMR seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing but funds have been locked in the deposit transaction.\n\nIf the buyer is also missing the delayed payout transaction, they will be instructed to NOT send the payment and open a mediation ticket instead. You should also open a mediation ticket with Cmd/Ctrl+o. \n\nIf the buyer has not sent payment yet, the mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). Otherwise the trade amount should go to the buyer. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.errorMsgSet=There was an error during trade protocol execution.\n\nError: {0}\n\nIt might be that this error is not critical, and the trade can be completed normally. If you are unsure, open a mediation ticket to get advice from Haveno mediators. \n\nIf the error was critical and the trade cannot be completed, you might have lost your trade fee. Request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.missingContract=The trade contract is not set.\n\nThe trade cannot be completed and you might have lost your trade fee. If so, you can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.info.popup=The trade protocol encountered some problems.\n\n{0}
|
||||
portfolio.pending.failedTrade.txChainInvalid.moveToFailed=The trade protocol encountered a serious problem.\n\n{0}\n\nDo you want to move the trade to failed trades?\n\nYou cannot open mediation or arbitration from the failed trades view, but you can move a failed trade back to the open trades screen any time.
|
||||
portfolio.pending.failedTrade.txChainValid.moveToFailed=The trade protocol encountered some problems.\n\n{0}\n\nThe trade transactions have been published and funds are locked. Only move the trade to failed trades if you are really sure. It might prevent options to resolve the problem.\n\nDo you want to move the trade to failed trades?\n\nYou cannot open mediation or arbitration from the failed trades view, but you can move a failed trade back to the open trades screen any time.
|
||||
|
@ -134,7 +134,7 @@ shared.noDateAvailable=Geçerli tarih yok
|
||||
shared.noDetailsAvailable=Geçerli detay yok
|
||||
shared.notUsedYet=Henüz kullanılmadı
|
||||
shared.date=Tarih
|
||||
shared.sendFundsDetailsWithFee=Gönderiliyor: {0}\nAlıcı adresine: {1}.\nGerekli madencilik ücreti: {2}\n\nAlıcı alacak: {3}\n\nBu miktarı çekmek istediğinizden emin misiniz?
|
||||
shared.sendFundsDetailsWithFee=Gönderilen: {0}\n\nAlıcı adresi: {1}\n\nEk madenci ücreti: {2}\n\nBu tutarı göndermek istediğinizden emin misiniz?
|
||||
# suppress inspection "TrailingSpacesInProperty"
|
||||
shared.sendFundsDetailsDust=Haveno, bu işlemin minimum toz eşiğinin altında bir değişim çıktısı oluşturacağını (ve bu nedenle Monero konsensüs kuralları tarafından izin verilmediğini) tespit etti. Bunun yerine, bu toz ({0} satoshi{1}) madencilik ücretine eklenecektir.\n\n\n
|
||||
shared.copyToClipboard=Panoya kopyala
|
||||
@ -669,7 +669,8 @@ portfolio.pending.autoConf.state.ERROR=Bir hizmet talebinde hata oluştu. Otomat
|
||||
# suppress inspection "UnusedProperty"
|
||||
portfolio.pending.autoConf.state.FAILED=Bir hizmet başarısızlıkla sonuçlandı. Otomatik onay mümkün değil.
|
||||
|
||||
portfolio.pending.step1.info=Yatırım işlemi yayımlandı.\n{0} ödemeye başlamadan önce 10 onay (yaklaşık 20 dakika) beklemeniz gerekiyor.
|
||||
portfolio.pending.step1.info.you=Depozito işlemi yayımlandı.\nÖdemenin başlayabilmesi için 10 onay beklemeniz gerekiyor (yaklaşık 20 dakika).
|
||||
portfolio.pending.step1.info.buyer=Depozito işlemi yayınlandı.\nXMR alıcısının ödeme işlemine başlanmadan önce 10 onay beklemesi gerekiyor (yaklaşık 20 dakika).
|
||||
portfolio.pending.step1.warn=Yatırım işlemi henüz onaylanmadı. Bu genellikle yaklaşık 20 dakika sürer, ancak ağ yoğunsa daha uzun sürebilir.
|
||||
portfolio.pending.step1.openForDispute=Yatırım işlemi hala onaylanmadı. \
|
||||
20 dakikadan çok daha uzun süre beklediyseniz, Haveno desteği ile iletişime geçin.
|
||||
@ -959,7 +960,7 @@ portfolio.pending.failedTrade.maker.missingTakerFeeTx=Karşı tarafın alıcı
|
||||
portfolio.pending.failedTrade.missingDepositTx=Para yatırma işlemi (2-of-2 multisig işlemi) eksik.\n\n\
|
||||
Bu işlem olmadan, ticaret tamamlanamaz. Hiçbir fon kilitlenmedi ancak ticaret ücretiniz ödendi. \
|
||||
Ticaret ücretinin geri ödenmesi için burada talepte bulunabilirsiniz: \
|
||||
[HYPERLINK:https://github.com/bisq-network/support/issues]\n\n\
|
||||
[HYPERLINK:https://github.com/haveno-dex/haveno/issues]\n\n\
|
||||
Bu ticareti başarısız ticaretler arasına taşımakta özgürsünüz.
|
||||
portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=Gecikmiş ödeme işlemi eksik, \
|
||||
ancak fonlar depozito işleminde kilitlendi.\n\n\
|
||||
@ -969,7 +970,7 @@ portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=G
|
||||
(satıcı da tam ticaret miktarını geri alır). \
|
||||
Bu şekilde, güvenlik riski yoktur ve yalnızca ticaret ücretleri kaybedilir. \n\n\
|
||||
Kaybedilen ticaret ücretleri için burada geri ödeme talebinde bulunabilirsiniz: \
|
||||
[HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
[HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=Gecikmiş ödeme işlemi eksik \
|
||||
ancak fonlar depozito işleminde kilitlendi.\n\n\
|
||||
Eğer alıcı da gecikmiş ödeme işlemini eksikse, onlara ödemeyi göndermemeleri ve \
|
||||
@ -978,18 +979,18 @@ portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=
|
||||
tamamını geri almasını önermelidir (satıcı da tam ticaret miktarını geri alır). \
|
||||
Aksi takdirde ticaret miktarı alıcıya gitmelidir. \n\n\
|
||||
Kaybedilen ticaret ücretleri için burada geri ödeme talebinde bulunabilirsiniz: \
|
||||
[HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
[HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.errorMsgSet=Ticaret protokolü yürütülürken bir hata oluştu.\n\n\
|
||||
Hata: {0}\n\n\
|
||||
Bu hata kritik olmayabilir ve ticaret normal şekilde tamamlanabilir. Emin değilseniz, \
|
||||
Haveno arabulucularından tavsiye almak için bir arabuluculuk bileti açın. \n\n\
|
||||
Eğer hata kritikse ve ticaret tamamlanamazsa, ticaret ücretinizi kaybetmiş olabilirsiniz. \
|
||||
Kaybedilen ticaret ücretleri için burada geri ödeme talebinde bulunun: \
|
||||
[HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
[HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.missingContract=Ticaret sözleşmesi ayarlanmadı.\n\n\
|
||||
Ticaret tamamlanamaz ve ticaret ücretinizi kaybetmiş olabilirsiniz. \
|
||||
Eğer öyleyse, kaybedilen ticaret ücretleri için burada geri ödeme talebinde bulunun: \
|
||||
[HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
[HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.info.popup=Ticaret protokolü bazı sorunlarla karşılaştı.\n\n{0}
|
||||
portfolio.pending.failedTrade.txChainInvalid.moveToFailed=Ticaret protokolü ciddi bir sorunla karşılaştı.\n\n{0}\n\n\
|
||||
Ticareti başarısız ticaretler arasına taşımak ister misiniz?\n\n\
|
||||
|
@ -125,6 +125,7 @@ shared.noDateAvailable=Ngày tháng không hiển thị
|
||||
shared.noDetailsAvailable=Không có thông tin
|
||||
shared.notUsedYet=Chưa được sử dụng
|
||||
shared.date=Ngày
|
||||
shared.sendFundsDetailsWithFee=Đang gửi: {0}\n\nĐến địa chỉ nhận: {1}\n\nPhí thợ đào bổ sung: {2}\n\nBạn có chắc chắn muốn gửi số tiền này không?
|
||||
# suppress inspection "TrailingSpacesInProperty"
|
||||
shared.sendFundsDetailsDust=Haveno detected that this transaction would create a change output which is below the minimum dust threshold (and therefore not allowed by Monero consensus rules). Instead, this dust ({0} satoshi{1}) will be added to the mining fee.\n\n\n
|
||||
shared.copyToClipboard=Sao chép đến clipboard
|
||||
@ -614,7 +615,8 @@ portfolio.pending.autoConf.state.ERROR=An error at a service request occurred. N
|
||||
# suppress inspection "UnusedProperty"
|
||||
portfolio.pending.autoConf.state.FAILED=A service returned with a failure. No auto-confirm possible.
|
||||
|
||||
portfolio.pending.step1.info=Giao dịch đặt cọc đã được công bố.\n{0} Bạn cần đợi ít nhất một xác nhận blockchain trước khi bắt đầu thanh toán.
|
||||
portfolio.pending.step1.info.you=Giao dịch nạp tiền đã được công bố.\nBạn cần đợi 10 xác nhận (khoảng 20 phút) trước khi thanh toán có thể bắt đầu.
|
||||
portfolio.pending.step1.info.buyer=Giao dịch gửi tiền đã được công bố.\nNgười mua XMR cần chờ 10 xác nhận (khoảng 20 phút) trước khi thanh toán có thể bắt đầu.
|
||||
portfolio.pending.step1.warn=The deposit transaction is still not confirmed. This sometimes happens in rare cases when the funding fee of one trader from an external wallet was too low.
|
||||
portfolio.pending.step1.openForDispute=The deposit transaction is still not confirmed. You can wait longer or contact the mediator for assistance.
|
||||
|
||||
@ -817,11 +819,11 @@ portfolio.pending.mediationResult.popup.alreadyAccepted=You've already accepted
|
||||
|
||||
portfolio.pending.failedTrade.taker.missingTakerFeeTx=The taker fee transaction is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked and no trade fee has been paid. You can move this trade to failed trades.
|
||||
portfolio.pending.failedTrade.maker.missingTakerFeeTx=The peer's taker fee transaction is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked. Your offer is still available to other traders, so you have not lost the maker fee. You can move this trade to failed trades.
|
||||
portfolio.pending.failedTrade.missingDepositTx=The deposit transaction (the 2-of-2 multisig transaction) is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked but your trade fee has been paid. You can make a request to be reimbursed the trade fee here: [HYPERLINK:https://github.com/bisq-network/support/issues]\n\nFeel free to move this trade to failed trades.
|
||||
portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the traditional or crypto payment to the XMR seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing but funds have been locked in the deposit transaction.\n\nIf the buyer is also missing the delayed payout transaction, they will be instructed to NOT send the payment and open a mediation ticket instead. You should also open a mediation ticket with Cmd/Ctrl+o. \n\nIf the buyer has not sent payment yet, the mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). Otherwise the trade amount should go to the buyer. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.errorMsgSet=There was an error during trade protocol execution.\n\nError: {0}\n\nIt might be that this error is not critical, and the trade can be completed normally. If you are unsure, open a mediation ticket to get advice from Haveno mediators. \n\nIf the error was critical and the trade cannot be completed, you might have lost your trade fee. Request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.missingContract=The trade contract is not set.\n\nThe trade cannot be completed and you might have lost your trade fee. If so, you can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/bisq-network/support/issues]
|
||||
portfolio.pending.failedTrade.missingDepositTx=The deposit transaction (the 2-of-2 multisig transaction) is missing.\n\nWithout this tx, the trade cannot be completed. No funds have been locked but your trade fee has been paid. You can make a request to be reimbursed the trade fee here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]\n\nFeel free to move this trade to failed trades.
|
||||
portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, but funds have been locked in the deposit transaction.\n\nPlease do NOT send the traditional or crypto payment to the XMR seller, because without the delayed payout tx, arbitration cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. The mediator should suggest that both peers each get back the the full amount of their security deposits (with seller receiving full trade amount back as well). This way, there is no security risk, and only trade fees are lost. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing but funds have been locked in the deposit transaction.\n\nIf the buyer is also missing the delayed payout transaction, they will be instructed to NOT send the payment and open a mediation ticket instead. You should also open a mediation ticket with Cmd/Ctrl+o. \n\nIf the buyer has not sent payment yet, the mediator should suggest that both peers each get back the full amount of their security deposits (with seller receiving full trade amount back as well). Otherwise the trade amount should go to the buyer. \n\nYou can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.errorMsgSet=There was an error during trade protocol execution.\n\nError: {0}\n\nIt might be that this error is not critical, and the trade can be completed normally. If you are unsure, open a mediation ticket to get advice from Haveno mediators. \n\nIf the error was critical and the trade cannot be completed, you might have lost your trade fee. Request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.missingContract=The trade contract is not set.\n\nThe trade cannot be completed and you might have lost your trade fee. If so, you can request a reimbursement for lost trade fees here: [HYPERLINK:https://github.com/haveno-dex/haveno/issues]
|
||||
portfolio.pending.failedTrade.info.popup=The trade protocol encountered some problems.\n\n{0}
|
||||
portfolio.pending.failedTrade.txChainInvalid.moveToFailed=The trade protocol encountered a serious problem.\n\n{0}\n\nDo you want to move the trade to failed trades?\n\nYou cannot open mediation or arbitration from the failed trades view, but you can move a failed trade back to the open trades screen any time.
|
||||
portfolio.pending.failedTrade.txChainValid.moveToFailed=The trade protocol encountered some problems.\n\n{0}\n\nThe trade transactions have been published and funds are locked. Only move the trade to failed trades if you are really sure. It might prevent options to resolve the problem.\n\nDo you want to move the trade to failed trades?\n\nYou cannot open mediation or arbitration from the failed trades view, but you can move a failed trade back to the open trades screen any time.
|
||||
|
@ -125,6 +125,7 @@ shared.noDateAvailable=没有可用数据
|
||||
shared.noDetailsAvailable=没有可用详细
|
||||
shared.notUsedYet=尚未使用
|
||||
shared.date=日期
|
||||
shared.sendFundsDetailsWithFee=TOD发送:{0}\n\n接收地址:{1}\n\n额外矿工费:{2}\n\n您确定要发送此金额吗?O
|
||||
# suppress inspection "TrailingSpacesInProperty"
|
||||
shared.sendFundsDetailsDust=Haveno 检测到,该交易将产生一个低于最低零头阈值的输出(不被比特币共识规则所允许)。相反,这些零头({0}satoshi{1})将被添加到挖矿手续费中。
|
||||
shared.copyToClipboard=复制到剪贴板
|
||||
@ -615,7 +616,8 @@ portfolio.pending.autoConf.state.ERROR=您请求的服务发生了错误。没
|
||||
# suppress inspection "UnusedProperty"
|
||||
portfolio.pending.autoConf.state.FAILED=服务返回失败。没有自动确认。
|
||||
|
||||
portfolio.pending.step1.info=存款交易已经发布。\n开始付款之前,{0} 需要等待至少一个区块链确认。
|
||||
portfolio.pending.step1.info.you=存款交易已发布。\n您需要等待 10 次确认(约 20 分钟)后付款才能开始。
|
||||
portfolio.pending.step1.info.buyer=存款交易已发布。\nXMR 买家需要等待 10 次确认(大约 20 分钟),然后才能开始付款。
|
||||
portfolio.pending.step1.warn=保证金交易仍未得到确认。这种情况可能会发生在外部钱包转账时使用的交易手续费用较低造成的。
|
||||
portfolio.pending.step1.openForDispute=保证金交易仍未得到确认。请联系调解员协助。
|
||||
|
||||
@ -818,11 +820,11 @@ portfolio.pending.mediationResult.popup.alreadyAccepted=您已经接受了。
|
||||
|
||||
portfolio.pending.failedTrade.taker.missingTakerFeeTx=吃单交易费未找到。\n\n如果没有 tx,交易不能完成。没有资金被锁定以及没有支付交易费用。你可以将交易移至失败的交易。
|
||||
portfolio.pending.failedTrade.maker.missingTakerFeeTx=挂单费交易未找到。\n\n如果没有 tx,交易不能完成。没有资金被锁定以及没有支付交易费用。你可以将交易移至失败的交易。
|
||||
portfolio.pending.failedTrade.missingDepositTx=这个保证金交易(2 对 2 多重签名交易)缺失\n\n没有该 tx,交易不能完成。没有资金被锁定但是您的交易手续费仍然已支出。您可以发起一个请求去赔偿改交易手续费在这里:https://github.com/bisq-network/support/issues\n\n请随意的将该交易移至失败交易
|
||||
portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=延迟支付交易缺失,但是资金仍然被锁定在保证金交易中。\n\n请不要给比特币卖家发送法币或数字货币,因为没有延迟交易 tx,不能开启仲裁。使用 Cmd/Ctrl+o开启调解协助。调解员应该建议交易双方分别退回全部的保证金(卖方支付的交易金额也会全数返还)。这样的话不会有任何的安全问题只会损失交易手续费。\n\n你可以在这里为失败的交易提出赔偿要求:https://github.com/bisq-network/support/issues
|
||||
portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=延迟支付交易确实但是资金仍然被锁定在保证金交易中。\n\n如果卖家仍然缺失延迟支付交易,他会接到请勿付款的指示并开启一个调节帮助。你也应该使用 Cmd/Ctrl+O 去打开一个调节协助\n\n如果买家还没有发送付款,调解员应该会建议交易双方分别退回全部的保证金(卖方支付的交易金额也会全数返还)。否则交易额应该判给买方。\n\n你可以在这里为失败的交易提出赔偿要求:https://github.com/bisq-network/support/issues
|
||||
portfolio.pending.failedTrade.errorMsgSet=在处理交易协议是发生了一个错误\n\n错误:{0}\n\n这应该不是致命错误,您可以正常的完成交易。如果你仍担忧,打开一个调解协助并从 Haveno 调解员处得到建议。\n\n如果这个错误是致命的那么这个交易就无法完成,你可能会损失交易费。可以在这里为失败的交易提出赔偿要求:https://github.com/bisq-network/support/issues
|
||||
portfolio.pending.failedTrade.missingContract=没有设置交易合同。\n\n这个交易无法完成,你可能会损失交易手续费。可以在这里为失败的交易提出赔偿要求:https://github.com/bisq-network/support/issues
|
||||
portfolio.pending.failedTrade.missingDepositTx=这个保证金交易(2 对 2 多重签名交易)缺失\n\n没有该 tx,交易不能完成。没有资金被锁定但是您的交易手续费仍然已支出。您可以发起一个请求去赔偿改交易手续费在这里:https://github.com/haveno-dex/haveno/issues\n\n请随意的将该交易移至失败交易
|
||||
portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=延迟支付交易缺失,但是资金仍然被锁定在保证金交易中。\n\n请不要给比特币卖家发送法币或数字货币,因为没有延迟交易 tx,不能开启仲裁。使用 Cmd/Ctrl+o开启调解协助。调解员应该建议交易双方分别退回全部的保证金(卖方支付的交易金额也会全数返还)。这样的话不会有任何的安全问题只会损失交易手续费。\n\n你可以在这里为失败的交易提出赔偿要求:https://github.com/haveno-dex/haveno/issues
|
||||
portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=延迟支付交易确实但是资金仍然被锁定在保证金交易中。\n\n如果卖家仍然缺失延迟支付交易,他会接到请勿付款的指示并开启一个调节帮助。你也应该使用 Cmd/Ctrl+O 去打开一个调节协助\n\n如果买家还没有发送付款,调解员应该会建议交易双方分别退回全部的保证金(卖方支付的交易金额也会全数返还)。否则交易额应该判给买方。\n\n你可以在这里为失败的交易提出赔偿要求:https://github.com/haveno-dex/haveno/issues
|
||||
portfolio.pending.failedTrade.errorMsgSet=在处理交易协议是发生了一个错误\n\n错误:{0}\n\n这应该不是致命错误,您可以正常的完成交易。如果你仍担忧,打开一个调解协助并从 Haveno 调解员处得到建议。\n\n如果这个错误是致命的那么这个交易就无法完成,你可能会损失交易费。可以在这里为失败的交易提出赔偿要求:https://github.com/haveno-dex/haveno/issues
|
||||
portfolio.pending.failedTrade.missingContract=没有设置交易合同。\n\n这个交易无法完成,你可能会损失交易手续费。可以在这里为失败的交易提出赔偿要求:https://github.com/haveno-dex/haveno/issues
|
||||
portfolio.pending.failedTrade.info.popup=交易协议出现了问题。\n\n{0}
|
||||
portfolio.pending.failedTrade.txChainInvalid.moveToFailed=交易协议出现了严重问题。\n\n{0}\n\n您确定想要将该交易移至失败的交易吗?\n\n您不能在失败的交易中打开一个调解或仲裁,但是你随时可以将失败的交易重新移至未完成交易。
|
||||
portfolio.pending.failedTrade.txChainValid.moveToFailed=这个交易协议存在一些问题。\n\n{0}\n\n这个报价交易已经被发布以及资金已被锁定。只有在确定情况下将该交易移至失败交易。这可能会阻止解决问题的可用选项。\n\n您确定想要将该交易移至失败的交易吗?\n\n您不能在失败的交易中打开一个调解或仲裁,但是你随时可以将失败的交易重新移至未完成交易。
|
||||
|
@ -125,6 +125,7 @@ shared.noDateAvailable=沒有可用數據
|
||||
shared.noDetailsAvailable=沒有可用詳細
|
||||
shared.notUsedYet=尚未使用
|
||||
shared.date=日期
|
||||
shared.sendFundsDetailsWithFee=發送中:{0}\n\n至接收地址:{1}\n\n額外礦工費:{2}\n\n您確定要發送此金額嗎?
|
||||
# suppress inspection "TrailingSpacesInProperty"
|
||||
shared.sendFundsDetailsDust=Haveno 檢測到,該交易將產生一個低於最低零頭閾值的輸出(不被比特幣共識規則所允許)。相反,這些零頭({0}satoshi{1})將被添加到挖礦手續費中。
|
||||
shared.copyToClipboard=複製到剪貼板
|
||||
@ -615,7 +616,8 @@ portfolio.pending.autoConf.state.ERROR=您請求的服務發生了錯誤。沒
|
||||
# suppress inspection "UnusedProperty"
|
||||
portfolio.pending.autoConf.state.FAILED=服務返回失敗。沒有自動確認。
|
||||
|
||||
portfolio.pending.step1.info=存款交易已經發布。\n開始付款之前,{0} 需要等待至少一個區塊鏈確認。
|
||||
portfolio.pending.step1.info.you=存款交易已發布。\n您需要等待 10 次確認(約 20 分鐘)後,付款才能開始。
|
||||
portfolio.pending.step1.info.buyer=存款交易已發佈。\nXMR 購買者需要等待 10 次確認(大約 20 分鐘)才能開始付款。
|
||||
portfolio.pending.step1.warn=保證金交易仍未得到確認。這種情況可能會發生在外部錢包轉賬時使用的交易手續費用較低造成的。
|
||||
portfolio.pending.step1.openForDispute=保證金交易仍未得到確認。請聯繫調解員協助。
|
||||
|
||||
@ -818,11 +820,11 @@ portfolio.pending.mediationResult.popup.alreadyAccepted=您已經接受了。
|
||||
|
||||
portfolio.pending.failedTrade.taker.missingTakerFeeTx=吃單交易費未找到。\n\n如果沒有 tx,交易不能完成。沒有資金被鎖定以及沒有支付交易費用。你可以將交易移至失敗的交易。
|
||||
portfolio.pending.failedTrade.maker.missingTakerFeeTx=掛單費交易未找到。\n\n如果沒有 tx,交易不能完成。沒有資金被鎖定以及沒有支付交易費用。你可以將交易移至失敗的交易。
|
||||
portfolio.pending.failedTrade.missingDepositTx=這個保證金交易(2 對 2 多重簽名交易)缺失\n\n沒有該 tx,交易不能完成。沒有資金被鎖定但是您的交易手續費仍然已支出。您可以發起一個請求去賠償改交易手續費在這裏:https://github.com/bisq-network/support/issues\n\n請隨意的將該交易移至失敗交易
|
||||
portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=延遲支付交易缺失,但是資金仍然被鎖定在保證金交易中。\n\n請不要給比特幣賣家發送法幣或數字貨幣,因為沒有延遲交易 tx,不能開啟仲裁。使用 Cmd/Ctrl+o開啟調解協助。調解員應該建議交易雙方分別退回全部的保證金(賣方支付的交易金額也會全數返還)。這樣的話不會有任何的安全問題只會損失交易手續費。\n\n你可以在這裏為失敗的交易提出賠償要求:https://github.com/bisq-network/support/issues
|
||||
portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=延遲支付交易確實但是資金仍然被鎖定在保證金交易中。\n\n如果賣家仍然缺失延遲支付交易,他會接到請勿付款的指示並開啟一個調節幫助。你也應該使用 Cmd/Ctrl+O 去打開一個調節協助\n\n如果買家還沒有發送付款,調解員應該會建議交易雙方分別退回全部的保證金(賣方支付的交易金額也會全數返還)。否則交易額應該判給買方。\n\n你可以在這裏為失敗的交易提出賠償要求:https://github.com/bisq-network/support/issues
|
||||
portfolio.pending.failedTrade.errorMsgSet=在處理交易協議是發生了一個錯誤\n\n錯誤:{0}\n\n這應該不是致命錯誤,您可以正常的完成交易。如果你仍擔憂,打開一個調解協助並從 Haveno 調解員處得到建議。\n\n如果這個錯誤是致命的那麼這個交易就無法完成,你可能會損失交易費。可以在這裏為失敗的交易提出賠償要求:https://github.com/bisq-network/support/issues
|
||||
portfolio.pending.failedTrade.missingContract=沒有設置交易合同。\n\n這個交易無法完成,你可能會損失交易手續費。可以在這裏為失敗的交易提出賠償要求:https://github.com/bisq-network/support/issues
|
||||
portfolio.pending.failedTrade.missingDepositTx=這個保證金交易(2 對 2 多重簽名交易)缺失\n\n沒有該 tx,交易不能完成。沒有資金被鎖定但是您的交易手續費仍然已支出。您可以發起一個請求去賠償改交易手續費在這裏:https://github.com/haveno-dex/haveno/issues\n\n請隨意的將該交易移至失敗交易
|
||||
portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=延遲支付交易缺失,但是資金仍然被鎖定在保證金交易中。\n\n請不要給比特幣賣家發送法幣或數字貨幣,因為沒有延遲交易 tx,不能開啟仲裁。使用 Cmd/Ctrl+o開啟調解協助。調解員應該建議交易雙方分別退回全部的保證金(賣方支付的交易金額也會全數返還)。這樣的話不會有任何的安全問題只會損失交易手續費。\n\n你可以在這裏為失敗的交易提出賠償要求:https://github.com/haveno-dex/haveno/issues
|
||||
portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx=延遲支付交易確實但是資金仍然被鎖定在保證金交易中。\n\n如果賣家仍然缺失延遲支付交易,他會接到請勿付款的指示並開啟一個調節幫助。你也應該使用 Cmd/Ctrl+O 去打開一個調節協助\n\n如果買家還沒有發送付款,調解員應該會建議交易雙方分別退回全部的保證金(賣方支付的交易金額也會全數返還)。否則交易額應該判給買方。\n\n你可以在這裏為失敗的交易提出賠償要求:https://github.com/haveno-dex/haveno/issues
|
||||
portfolio.pending.failedTrade.errorMsgSet=在處理交易協議是發生了一個錯誤\n\n錯誤:{0}\n\n這應該不是致命錯誤,您可以正常的完成交易。如果你仍擔憂,打開一個調解協助並從 Haveno 調解員處得到建議。\n\n如果這個錯誤是致命的那麼這個交易就無法完成,你可能會損失交易費。可以在這裏為失敗的交易提出賠償要求:https://github.com/haveno-dex/haveno/issues
|
||||
portfolio.pending.failedTrade.missingContract=沒有設置交易合同。\n\n這個交易無法完成,你可能會損失交易手續費。可以在這裏為失敗的交易提出賠償要求:https://github.com/haveno-dex/haveno/issues
|
||||
portfolio.pending.failedTrade.info.popup=交易協議出現了問題。\n\n{0}
|
||||
portfolio.pending.failedTrade.txChainInvalid.moveToFailed=交易協議出現了嚴重問題。\n\n{0}\n\n您確定想要將該交易移至失敗的交易嗎?\n\n您不能在失敗的交易中打開一個調解或仲裁,但是你隨時可以將失敗的交易重新移至未完成交易。
|
||||
portfolio.pending.failedTrade.txChainValid.moveToFailed=這個交易協議存在一些問題。\n\n{0}\n\n這個報價交易已經被髮布以及資金已被鎖定。只有在確定情況下將該交易移至失敗交易。這可能會阻止解決問題的可用選項。\n\n您確定想要將該交易移至失敗的交易嗎?\n\n您不能在失敗的交易中打開一個調解或仲裁,但是你隨時可以將失敗的交易重新移至未完成交易。
|
||||
|
@ -24,6 +24,7 @@ import haveno.core.locale.CountryUtil;
|
||||
import haveno.core.locale.CryptoCurrency;
|
||||
import haveno.core.locale.CurrencyUtil;
|
||||
import haveno.core.locale.TraditionalCurrency;
|
||||
import haveno.core.xmr.nodes.XmrNodes;
|
||||
import haveno.core.locale.GlobalSettings;
|
||||
import haveno.core.locale.Res;
|
||||
import javafx.collections.ObservableList;
|
||||
@ -45,6 +46,7 @@ public class PreferencesTest {
|
||||
|
||||
private Preferences preferences;
|
||||
private PersistenceManager persistenceManager;
|
||||
private XmrNodes xmrNodes;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
@ -53,12 +55,12 @@ public class PreferencesTest {
|
||||
GlobalSettings.setLocale(en_US);
|
||||
Res.setBaseCurrencyCode("XMR");
|
||||
Res.setBaseCurrencyName("Monero");
|
||||
|
||||
persistenceManager = mock(PersistenceManager.class);
|
||||
Config config = new Config();
|
||||
XmrLocalNode xmrLocalNode = new XmrLocalNode(config, preferences);
|
||||
preferences = new Preferences(
|
||||
persistenceManager, config, null, null);
|
||||
xmrNodes = new XmrNodes();
|
||||
XmrLocalNode xmrLocalNode = new XmrLocalNode(config, preferences, xmrNodes);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -203,12 +203,12 @@ class GrpcOffersService extends OffersImplBase {
|
||||
return getCustomRateMeteringInterceptor(coreApi.getConfig().appDataDir, this.getClass())
|
||||
.or(() -> Optional.of(CallRateMeteringInterceptor.valueOf(
|
||||
new HashMap<>() {{
|
||||
put(getGetOfferMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 10 : 3, SECONDS));
|
||||
put(getGetMyOfferMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 10 : 3, SECONDS));
|
||||
put(getGetOffersMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 30 : 3, SECONDS));
|
||||
put(getGetMyOffersMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 30 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
|
||||
put(getPostOfferMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 30 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
|
||||
put(getCancelOfferMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 10 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
|
||||
put(getGetOfferMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 75 : 3, SECONDS));
|
||||
put(getGetMyOfferMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 75 : 3, SECONDS));
|
||||
put(getGetOffersMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 75 : 3, SECONDS));
|
||||
put(getGetMyOffersMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 75 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
|
||||
put(getPostOfferMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 75 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
|
||||
put(getCancelOfferMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 75 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
|
||||
}}
|
||||
)));
|
||||
}
|
||||
|
@ -251,15 +251,15 @@ class GrpcTradesService extends TradesImplBase {
|
||||
return getCustomRateMeteringInterceptor(coreApi.getConfig().appDataDir, this.getClass())
|
||||
.or(() -> Optional.of(CallRateMeteringInterceptor.valueOf(
|
||||
new HashMap<>() {{
|
||||
put(getGetTradeMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 30 : 1, SECONDS));
|
||||
put(getGetTradesMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 10 : 1, SECONDS));
|
||||
put(getTakeOfferMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 20 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
|
||||
put(getConfirmPaymentSentMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 10 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
|
||||
put(getConfirmPaymentReceivedMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 10 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
|
||||
put(getCompleteTradeMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 10 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
|
||||
put(getGetTradeMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 75 : 1, SECONDS));
|
||||
put(getGetTradesMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 75 : 1, SECONDS));
|
||||
put(getTakeOfferMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 75 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
|
||||
put(getConfirmPaymentSentMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 75 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
|
||||
put(getConfirmPaymentReceivedMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 75 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
|
||||
put(getCompleteTradeMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 75 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
|
||||
put(getWithdrawFundsMethod().getFullMethodName(), new GrpcCallRateMeter(3, MINUTES));
|
||||
put(getGetChatMessagesMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 10 : 4, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
|
||||
put(getSendChatMessageMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 10 : 4, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
|
||||
put(getGetChatMessagesMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 75 : 4, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
|
||||
put(getSendChatMessageMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 75 : 4, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
|
||||
}}
|
||||
)));
|
||||
}
|
||||
|
@ -1,8 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
|
||||
<conversionRule conversionWord="hl2" converterClass="haveno.common.app.LogHighlighter" />
|
||||
|
||||
<appender name="CONSOLE_APPENDER" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%highlight(%d{MMM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{30}: %msg %xEx%n)</pattern>
|
||||
<pattern>%hl2(%d{MMM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{40}: %msg %xEx%n)</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
|
@ -60,6 +60,6 @@
|
||||
</content_rating>
|
||||
|
||||
<releases>
|
||||
<release version="1.0.18" date="2025-01-19"/>
|
||||
<release version="1.0.19" date="2025-03-10"/>
|
||||
</releases>
|
||||
</component>
|
||||
|
@ -5,10 +5,10 @@
|
||||
<!-- See: https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -->
|
||||
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0.18</string>
|
||||
<string>1.0.19</string>
|
||||
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.18</string>
|
||||
<string>1.0.19</string>
|
||||
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>Haveno</string>
|
||||
|
@ -0,0 +1,140 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno 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.
|
||||
*
|
||||
* Haveno 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 Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.desktop.components;
|
||||
|
||||
|
||||
import com.jfoenix.controls.JFXTextArea;
|
||||
import haveno.core.util.validation.InputValidator;
|
||||
import haveno.desktop.util.validation.JFXInputValidator;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.control.Skin;
|
||||
|
||||
/**
|
||||
* TextArea with validation support.
|
||||
* If validator is set it supports on focus out validation with that validator. If a more sophisticated validation is
|
||||
* needed the validationResultProperty can be used for applying validation result done by external validation.
|
||||
* In case the isValid property in validationResultProperty get set to false we display a red border and an error
|
||||
* message within the errorMessageDisplay placed on the right of the text area.
|
||||
* The errorMessageDisplay gets closed when the ValidatingTextArea instance gets removed from the scene graph or when
|
||||
* hideErrorMessageDisplay() is called.
|
||||
* There can be only 1 errorMessageDisplays at a time we use static field for it.
|
||||
* The position is derived from the position of the textArea itself or if set from the layoutReference node.
|
||||
*/
|
||||
//TODO There are some rare situation where it behaves buggy. Needs further investigation and improvements.
|
||||
public class InputTextArea extends JFXTextArea {
|
||||
|
||||
private final ObjectProperty<InputValidator.ValidationResult> validationResult = new SimpleObjectProperty<>
|
||||
(new InputValidator.ValidationResult(true));
|
||||
|
||||
private final JFXInputValidator jfxValidationWrapper = new JFXInputValidator();
|
||||
|
||||
private InputValidator validator;
|
||||
private String errorMessage = null;
|
||||
|
||||
|
||||
public InputValidator getValidator() {
|
||||
return validator;
|
||||
}
|
||||
|
||||
public void setValidator(InputValidator validator) {
|
||||
this.validator = validator;
|
||||
}
|
||||
|
||||
public void setErrorMessage(String errorMessage) {
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public InputTextArea() {
|
||||
super();
|
||||
|
||||
getValidators().add(jfxValidationWrapper);
|
||||
|
||||
validationResult.addListener((ov, oldValue, newValue) -> {
|
||||
if (newValue != null) {
|
||||
jfxValidationWrapper.resetValidation();
|
||||
if (!newValue.isValid) {
|
||||
if (!newValue.errorMessageEquals(oldValue)) { // avoid blinking
|
||||
validate(); // ensure that the new error message replaces the old one
|
||||
}
|
||||
if (this.errorMessage != null) {
|
||||
jfxValidationWrapper.applyErrorMessage(this.errorMessage);
|
||||
} else {
|
||||
jfxValidationWrapper.applyErrorMessage(newValue);
|
||||
}
|
||||
}
|
||||
validate();
|
||||
}
|
||||
});
|
||||
|
||||
textProperty().addListener((o, oldValue, newValue) -> {
|
||||
refreshValidation();
|
||||
});
|
||||
|
||||
focusedProperty().addListener((o, oldValue, newValue) -> {
|
||||
if (validator != null) {
|
||||
if (!oldValue && newValue) {
|
||||
this.validationResult.set(new InputValidator.ValidationResult(true));
|
||||
} else {
|
||||
this.validationResult.set(validator.validate(getText()));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Public methods
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void resetValidation() {
|
||||
jfxValidationWrapper.resetValidation();
|
||||
|
||||
String input = getText();
|
||||
if (input.isEmpty()) {
|
||||
validationResult.set(new InputValidator.ValidationResult(true));
|
||||
} else {
|
||||
validationResult.set(validator.validate(input));
|
||||
}
|
||||
}
|
||||
|
||||
public void refreshValidation() {
|
||||
if (validator != null) {
|
||||
this.validationResult.set(validator.validate(getText()));
|
||||
}
|
||||
}
|
||||
|
||||
public void setInvalid(String message) {
|
||||
validationResult.set(new InputValidator.ValidationResult(false, message));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Getters
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public ObjectProperty<InputValidator.ValidationResult> validationResultProperty() {
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new JFXTextAreaSkinHavenoStyle(this);
|
||||
}
|
||||
}
|
@ -501,15 +501,15 @@ tree-table-view:focused {
|
||||
-jfx-default-color: -bs-color-primary;
|
||||
}
|
||||
|
||||
.jfx-date-picker .jfx-text-field {
|
||||
.jfx-date-picker .jfx-text-field .jfx-text-area {
|
||||
-fx-padding: 0.333333em 0em 0.333333em 0em;
|
||||
}
|
||||
|
||||
.jfx-date-picker .jfx-text-field > .input-line {
|
||||
.jfx-date-picker .jfx-text-field .jfx-text-area > .input-line {
|
||||
-fx-translate-x: 0em;
|
||||
}
|
||||
|
||||
.jfx-date-picker .jfx-text-field > .input-focused-line {
|
||||
.jfx-date-picker .jfx-text-field .jfx-text-area > .input-focused-line {
|
||||
-fx-translate-x: 0em;
|
||||
}
|
||||
|
||||
|
@ -39,10 +39,6 @@
|
||||
-fx-image: url("../../images/remove.png");
|
||||
}
|
||||
|
||||
#image-edit {
|
||||
-fx-image: url("../../images/edit.png");
|
||||
}
|
||||
|
||||
#image-buy-white {
|
||||
-fx-image: url("../../images/buy_white.png");
|
||||
}
|
||||
|
@ -335,25 +335,23 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
|
||||
tacWindow.onAction(acceptedHandler::run).show();
|
||||
}, 1));
|
||||
|
||||
havenoSetup.setDisplayMoneroConnectionFallbackHandler(show -> {
|
||||
if (moneroConnectionFallbackPopup == null) {
|
||||
havenoSetup.setDisplayMoneroConnectionFallbackHandler(fallbackMsg -> {
|
||||
if (fallbackMsg != null && !fallbackMsg.isEmpty()) {
|
||||
moneroConnectionFallbackPopup = new Popup()
|
||||
.headLine(Res.get("connectionFallback.headline"))
|
||||
.warning(Res.get("connectionFallback.msg"))
|
||||
.closeButtonText(Res.get("shared.no"))
|
||||
.actionButtonText(Res.get("shared.yes"))
|
||||
.onAction(() -> {
|
||||
havenoSetup.getConnectionServiceFallbackHandlerActive().set(false);
|
||||
new Thread(() -> HavenoUtils.xmrConnectionService.fallbackToBestConnection()).start();
|
||||
})
|
||||
.onClose(() -> {
|
||||
log.warn("User has declined to fallback to the next best available Monero node.");
|
||||
havenoSetup.getConnectionServiceFallbackHandlerActive().set(false);
|
||||
});
|
||||
}
|
||||
if (show) {
|
||||
.headLine(Res.get("connectionFallback.headline"))
|
||||
.warning(fallbackMsg)
|
||||
.closeButtonText(Res.get("shared.no"))
|
||||
.actionButtonText(Res.get("shared.yes"))
|
||||
.onAction(() -> {
|
||||
havenoSetup.getConnectionServiceFallbackHandler().set("");
|
||||
new Thread(() -> HavenoUtils.xmrConnectionService.fallbackToBestConnection()).start();
|
||||
})
|
||||
.onClose(() -> {
|
||||
log.warn("User has declined to fallback to the next best available Monero node.");
|
||||
havenoSetup.getConnectionServiceFallbackHandler().set("");
|
||||
});
|
||||
moneroConnectionFallbackPopup.show();
|
||||
} else if (moneroConnectionFallbackPopup.isDisplayed()) {
|
||||
} else if (moneroConnectionFallbackPopup != null && moneroConnectionFallbackPopup.isDisplayed()) {
|
||||
moneroConnectionFallbackPopup.hide();
|
||||
}
|
||||
});
|
||||
@ -420,7 +418,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
|
||||
|
||||
havenoSetup.setRejectedTxErrorMessageHandler(msg -> new Popup().width(850).warning(msg).show());
|
||||
|
||||
havenoSetup.setShowPopupIfInvalidBtcConfigHandler(this::showPopupIfInvalidBtcConfig);
|
||||
havenoSetup.setShowPopupIfInvalidXmrConfigHandler(this::showPopupIfInvalidXmrConfig);
|
||||
|
||||
havenoSetup.setRevolutAccountsUpdateHandler(revolutAccountList -> {
|
||||
// We copy the array as we will mutate it later
|
||||
@ -536,7 +534,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
|
||||
});
|
||||
}
|
||||
|
||||
private void showPopupIfInvalidBtcConfig() {
|
||||
private void showPopupIfInvalidXmrConfig() {
|
||||
preferences.setMoneroNodesOptionOrdinal(0);
|
||||
new Popup().warning(Res.get("settings.net.warn.invalidXmrConfig"))
|
||||
.hideCloseButton()
|
||||
|
@ -22,7 +22,7 @@ import haveno.common.taskrunner.Task;
|
||||
import haveno.common.util.Tuple2;
|
||||
import haveno.core.offer.availability.tasks.ProcessOfferAvailabilityResponse;
|
||||
import haveno.core.offer.availability.tasks.SendOfferAvailabilityRequest;
|
||||
import haveno.core.offer.placeoffer.tasks.AddToOfferBook;
|
||||
import haveno.core.offer.placeoffer.tasks.MaybeAddToOfferBook;
|
||||
import haveno.core.offer.placeoffer.tasks.MakerReserveOfferFunds;
|
||||
import haveno.core.offer.placeoffer.tasks.ValidateOffer;
|
||||
import haveno.core.trade.protocol.tasks.ApplyFilter;
|
||||
@ -72,7 +72,7 @@ public class DebugView extends InitializableView<GridPane, Void> {
|
||||
FXCollections.observableArrayList(Arrays.asList(
|
||||
ValidateOffer.class,
|
||||
MakerReserveOfferFunds.class,
|
||||
AddToOfferBook.class)
|
||||
MaybeAddToOfferBook.class)
|
||||
));
|
||||
|
||||
|
||||
|
@ -225,8 +225,8 @@ public class LockedView extends ActivatableView<VBox, Void> {
|
||||
Optional<Trade> tradeOptional = tradeManager.getOpenTrade(offerId);
|
||||
if (tradeOptional.isPresent()) {
|
||||
return Optional.of(tradeOptional.get());
|
||||
} else if (openOfferManager.getOpenOfferById(offerId).isPresent()) {
|
||||
return Optional.of(openOfferManager.getOpenOfferById(offerId).get());
|
||||
} else if (openOfferManager.getOpenOffer(offerId).isPresent()) {
|
||||
return Optional.of(openOfferManager.getOpenOffer(offerId).get());
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
@ -224,8 +224,8 @@ public class ReservedView extends ActivatableView<VBox, Void> {
|
||||
Optional<Trade> tradeOptional = tradeManager.getOpenTrade(offerId);
|
||||
if (tradeOptional.isPresent()) {
|
||||
return Optional.of(tradeOptional.get());
|
||||
} else if (openOfferManager.getOpenOfferById(offerId).isPresent()) {
|
||||
return Optional.of(openOfferManager.getOpenOfferById(offerId).get());
|
||||
} else if (openOfferManager.getOpenOffer(offerId).isPresent()) {
|
||||
return Optional.of(openOfferManager.getOpenOffer(offerId).get());
|
||||
} else {
|
||||
return Optional.<Tradable>empty();
|
||||
}
|
||||
|
@ -302,10 +302,9 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
||||
BigInteger receiverAmount = tx.getOutgoingTransfer().getDestinations().get(0).getAmount();
|
||||
BigInteger fee = tx.getFee();
|
||||
String messageText = Res.get("shared.sendFundsDetailsWithFee",
|
||||
HavenoUtils.formatXmr(receiverAmount.add(fee), true),
|
||||
HavenoUtils.formatXmr(receiverAmount, true),
|
||||
withdrawToAddress,
|
||||
HavenoUtils.formatXmr(fee, true),
|
||||
HavenoUtils.formatXmr(receiverAmount, true));
|
||||
HavenoUtils.formatXmr(fee, true));
|
||||
|
||||
// popup confirmation message
|
||||
Popup popup = new Popup();
|
||||
|
@ -208,29 +208,29 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
||||
|
||||
root.getChildren().addAll(toolBox, priceChartPane, volumeChartPane, tableView, footer);
|
||||
|
||||
timeUnitChangeListener = (observable, oldValue, newValue) -> {
|
||||
timeUnitChangeListener = (observable, oldValue, newValue) -> UserThread.execute(() -> {
|
||||
if (newValue != null) {
|
||||
model.setTickUnit((TradesChartsViewModel.TickUnit) newValue.getUserData());
|
||||
priceAxisX.setTickLabelFormatter(getTimeAxisStringConverter());
|
||||
volumeAxisX.setTickLabelFormatter(getTimeAxisStringConverter());
|
||||
volumeInUsdAxisX.setTickLabelFormatter(getTimeAxisStringConverter());
|
||||
}
|
||||
};
|
||||
priceAxisYWidthListener = (observable, oldValue, newValue) -> {
|
||||
});
|
||||
priceAxisYWidthListener = (observable, oldValue, newValue) -> UserThread.execute(() -> {
|
||||
priceAxisYWidth = (double) newValue;
|
||||
layoutChart();
|
||||
};
|
||||
volumeAxisYWidthListener = (observable, oldValue, newValue) -> {
|
||||
});
|
||||
volumeAxisYWidthListener = (observable, oldValue, newValue) -> UserThread.execute(() -> {
|
||||
volumeAxisYWidth = (double) newValue;
|
||||
layoutChart();
|
||||
};
|
||||
tradeStatisticsByCurrencyListener = c -> {
|
||||
});
|
||||
tradeStatisticsByCurrencyListener = c -> UserThread.execute(() -> {
|
||||
nrOfTradeStatisticsLabel.setText(Res.get("market.trades.nrOfTrades", model.tradeStatisticsByCurrency.size()));
|
||||
fillList();
|
||||
};
|
||||
parentHeightListener = (observable, oldValue, newValue) -> layout();
|
||||
});
|
||||
parentHeightListener = (observable, oldValue, newValue) -> UserThread.execute(this::layout);
|
||||
|
||||
priceColumnLabelListener = (o, oldVal, newVal) -> priceColumn.setGraphic(new AutoTooltipLabel(newVal));
|
||||
priceColumnLabelListener = (o, oldVal, newVal) -> UserThread.execute(() -> priceColumn.setGraphic(new AutoTooltipLabel(newVal)));
|
||||
|
||||
// Need to render on next frame as otherwise there are issues in the chart rendering
|
||||
itemsChangeListener = c -> UserThread.execute(this::updateChartData);
|
||||
@ -238,31 +238,34 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
||||
currencySelectionBinding = EasyBind.combine(
|
||||
model.showAllTradeCurrenciesProperty, model.selectedTradeCurrencyProperty,
|
||||
(showAll, selectedTradeCurrency) -> {
|
||||
priceChart.setVisible(!showAll);
|
||||
priceChart.setManaged(!showAll);
|
||||
priceColumn.setSortable(!showAll);
|
||||
UserThread.execute(() -> {
|
||||
priceChart.setVisible(!showAll);
|
||||
priceChart.setManaged(!showAll);
|
||||
priceColumn.setSortable(!showAll);
|
||||
|
||||
if (showAll) {
|
||||
volumeColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.amount")));
|
||||
priceColumnLabel.set(Res.get("shared.price"));
|
||||
if (!tableView.getColumns().contains(marketColumn))
|
||||
tableView.getColumns().add(1, marketColumn);
|
||||
if (showAll) {
|
||||
volumeColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.amount")));
|
||||
priceColumnLabel.set(Res.get("shared.price"));
|
||||
if (!tableView.getColumns().contains(marketColumn))
|
||||
tableView.getColumns().add(1, marketColumn);
|
||||
|
||||
volumeChart.setPrefHeight(volumeChart.getMaxHeight());
|
||||
volumeInUsdChart.setPrefHeight(volumeInUsdChart.getMaxHeight());
|
||||
} else {
|
||||
volumeChart.setPrefHeight(volumeChart.getMinHeight());
|
||||
volumeInUsdChart.setPrefHeight(volumeInUsdChart.getMinHeight());
|
||||
priceSeries.setName(selectedTradeCurrency.getName());
|
||||
String code = selectedTradeCurrency.getCode();
|
||||
volumeColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.amountWithCur", code)));
|
||||
|
||||
priceColumnLabel.set(CurrencyUtil.getPriceWithCurrencyCode(code));
|
||||
|
||||
tableView.getColumns().remove(marketColumn);
|
||||
}
|
||||
|
||||
layout();
|
||||
});
|
||||
|
||||
volumeChart.setPrefHeight(volumeChart.getMaxHeight());
|
||||
volumeInUsdChart.setPrefHeight(volumeInUsdChart.getMaxHeight());
|
||||
} else {
|
||||
volumeChart.setPrefHeight(volumeChart.getMinHeight());
|
||||
volumeInUsdChart.setPrefHeight(volumeInUsdChart.getMinHeight());
|
||||
priceSeries.setName(selectedTradeCurrency.getName());
|
||||
String code = selectedTradeCurrency.getCode();
|
||||
volumeColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.amountWithCur", code)));
|
||||
|
||||
priceColumnLabel.set(CurrencyUtil.getPriceWithCurrencyCode(code));
|
||||
|
||||
tableView.getColumns().remove(marketColumn);
|
||||
}
|
||||
|
||||
layout();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
@ -286,14 +289,14 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
||||
currencyComboBox.getSelectionModel().select(model.getSelectedCurrencyListItem().get());
|
||||
currencyComboBox.getEditor().setText(new CurrencyStringConverter(currencyComboBox).toString(currencyComboBox.getSelectionModel().getSelectedItem()));
|
||||
|
||||
currencyComboBox.setOnChangeConfirmed(e -> {
|
||||
currencyComboBox.setOnChangeConfirmed(e -> UserThread.execute(() -> {
|
||||
if (currencyComboBox.getEditor().getText().isEmpty())
|
||||
currencyComboBox.getSelectionModel().select(SHOW_ALL);
|
||||
CurrencyListItem selectedItem = currencyComboBox.getSelectionModel().getSelectedItem();
|
||||
if (selectedItem != null) {
|
||||
model.onSetTradeCurrency(selectedItem.tradeCurrency);
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
toggleGroup.getToggles().get(model.tickUnit.ordinal()).setSelected(true);
|
||||
|
||||
|
@ -44,8 +44,8 @@ import haveno.desktop.components.AutoTooltipLabel;
|
||||
import haveno.desktop.components.BalanceTextField;
|
||||
import haveno.desktop.components.BusyAnimation;
|
||||
import haveno.desktop.components.FundsTextField;
|
||||
import haveno.desktop.components.HavenoTextArea;
|
||||
import haveno.desktop.components.InfoInputTextField;
|
||||
import haveno.desktop.components.InputTextArea;
|
||||
import haveno.desktop.components.InputTextField;
|
||||
import haveno.desktop.components.TitledGroupBg;
|
||||
import haveno.desktop.main.MainView;
|
||||
@ -76,7 +76,6 @@ import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.Separator;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import javafx.scene.control.Tooltip;
|
||||
@ -140,7 +139,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
||||
private BalanceTextField balanceTextField;
|
||||
private ToggleButton reserveExactAmountSlider;
|
||||
private ToggleButton buyerAsTakerWithoutDepositSlider;
|
||||
protected TextArea extraInfoTextArea;
|
||||
protected InputTextArea extraInfoTextArea;
|
||||
private FundsTextField totalToPayTextField;
|
||||
private Label amountDescriptionLabel, priceCurrencyLabel, priceDescriptionLabel, volumeDescriptionLabel,
|
||||
waitingForFundsLabel, marketBasedPriceLabel, percentagePriceDescriptionLabel, tradeFeeDescriptionLabel,
|
||||
@ -211,7 +210,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
||||
|
||||
createListeners();
|
||||
|
||||
balanceTextField.setFormatter(model.getBtcFormatter());
|
||||
balanceTextField.setFormatter(model.getXmrFormatter());
|
||||
|
||||
paymentAccountsComboBox.setConverter(GUIUtil.getPaymentAccountsComboBoxStringConverter());
|
||||
paymentAccountsComboBox.setButtonCell(GUIUtil.getComboBoxButtonCell(Res.get("shared.chooseTradingAccount"),
|
||||
@ -263,6 +262,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
||||
|
||||
buyerAsTakerWithoutDepositSlider.setSelected(model.dataModel.getBuyerAsTakerWithoutDeposit().get());
|
||||
|
||||
triggerPriceInputTextField.setText(model.triggerPrice.get());
|
||||
extraInfoTextArea.setText(model.dataModel.extraInfo.get());
|
||||
}
|
||||
}
|
||||
@ -592,6 +592,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
||||
triggerPriceInputTextField.validationResultProperty().bind(model.triggerPriceValidationResult);
|
||||
volumeTextField.validationResultProperty().bind(model.volumeValidationResult);
|
||||
securityDepositInputTextField.validationResultProperty().bind(model.securityDepositValidationResult);
|
||||
extraInfoTextArea.validationResultProperty().bind(model.extraInfoValidationResult);
|
||||
|
||||
// funding
|
||||
fundingHBox.visibleProperty().bind(model.getDataModel().getIsXmrWalletFunded().not().and(model.showPayFundsScreenDisplayed));
|
||||
@ -713,7 +714,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
||||
triggerPriceInputTextField.setText(model.triggerPrice.get());
|
||||
};
|
||||
extraInfoFocusedListener = (observable, oldValue, newValue) -> {
|
||||
model.onFocusOutExtraInfoTextField(oldValue, newValue);
|
||||
model.onFocusOutExtraInfoTextArea(oldValue, newValue);
|
||||
extraInfoTextArea.setText(model.extraInfo.get());
|
||||
};
|
||||
|
||||
@ -1097,7 +1098,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
||||
Res.get("payment.shared.optionalExtra"), 25 + heightAdjustment);
|
||||
GridPane.setColumnSpan(extraInfoTitledGroupBg, 3);
|
||||
|
||||
extraInfoTextArea = new HavenoTextArea();
|
||||
extraInfoTextArea = new InputTextArea();
|
||||
extraInfoTextArea.setPromptText(Res.get("payment.shared.extraInfo.prompt.offer"));
|
||||
extraInfoTextArea.getStyleClass().add("text-area");
|
||||
extraInfoTextArea.setWrapText(true);
|
||||
@ -1109,7 +1110,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
||||
GridPane.setColumnSpan(extraInfoTextArea, GridPane.REMAINING);
|
||||
GridPane.setColumnIndex(extraInfoTextArea, 0);
|
||||
GridPane.setHalignment(extraInfoTextArea, HPos.LEFT);
|
||||
GridPane.setMargin(extraInfoTextArea, new Insets(Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE, 0, 0, 0));
|
||||
GridPane.setMargin(extraInfoTextArea, new Insets(Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE, 0, 10, 0));
|
||||
gridPane.getChildren().add(extraInfoTextArea);
|
||||
}
|
||||
|
||||
|
@ -99,7 +99,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
private final AccountAgeWitnessService accountAgeWitnessService;
|
||||
private final Navigation navigation;
|
||||
private final Preferences preferences;
|
||||
protected final CoinFormatter btcFormatter;
|
||||
protected final CoinFormatter xmrFormatter;
|
||||
private final FiatVolumeValidator fiatVolumeValidator;
|
||||
private final AmountValidator4Decimals amountValidator4Decimals;
|
||||
private final AmountValidator8Decimals amountValidator8Decimals;
|
||||
@ -160,6 +160,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
final ObjectProperty<InputValidator.ValidationResult> triggerPriceValidationResult = new SimpleObjectProperty<>(new InputValidator.ValidationResult(true));
|
||||
final ObjectProperty<InputValidator.ValidationResult> volumeValidationResult = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<InputValidator.ValidationResult> securityDepositValidationResult = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<InputValidator.ValidationResult> extraInfoValidationResult = new SimpleObjectProperty<>();
|
||||
|
||||
private ChangeListener<String> amountStringListener;
|
||||
private ChangeListener<String> minAmountStringListener;
|
||||
@ -195,26 +196,26 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
FiatVolumeValidator fiatVolumeValidator,
|
||||
AmountValidator4Decimals amountValidator4Decimals,
|
||||
AmountValidator8Decimals amountValidator8Decimals,
|
||||
XmrValidator btcValidator,
|
||||
XmrValidator xmrValidator,
|
||||
SecurityDepositValidator securityDepositValidator,
|
||||
PriceFeedService priceFeedService,
|
||||
AccountAgeWitnessService accountAgeWitnessService,
|
||||
Navigation navigation,
|
||||
Preferences preferences,
|
||||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter,
|
||||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter xmrFormatter,
|
||||
OfferUtil offerUtil) {
|
||||
super(dataModel);
|
||||
|
||||
this.fiatVolumeValidator = fiatVolumeValidator;
|
||||
this.amountValidator4Decimals = amountValidator4Decimals;
|
||||
this.amountValidator8Decimals = amountValidator8Decimals;
|
||||
this.xmrValidator = btcValidator;
|
||||
this.xmrValidator = xmrValidator;
|
||||
this.securityDepositValidator = securityDepositValidator;
|
||||
this.priceFeedService = priceFeedService;
|
||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||
this.navigation = navigation;
|
||||
this.preferences = preferences;
|
||||
this.btcFormatter = btcFormatter;
|
||||
this.xmrFormatter = xmrFormatter;
|
||||
this.offerUtil = offerUtil;
|
||||
|
||||
paymentLabel = Res.get("createOffer.fundsBox.paymentLabel", dataModel.shortOfferId);
|
||||
@ -502,8 +503,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
extraInfoStringListener = (ov, oldValue, newValue) -> {
|
||||
if (newValue != null) {
|
||||
extraInfo.set(newValue);
|
||||
} else {
|
||||
extraInfo.set("");
|
||||
onExtraInfoTextAreaChanged();
|
||||
}
|
||||
};
|
||||
|
||||
@ -531,7 +531,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
tradeFee.set(HavenoUtils.formatXmr(makerFee));
|
||||
tradeFeeInXmrWithFiat.set(OfferViewModelUtil.getTradeFeeWithFiatEquivalent(offerUtil,
|
||||
dataModel.getMaxMakerFee(),
|
||||
btcFormatter));
|
||||
xmrFormatter));
|
||||
}
|
||||
|
||||
|
||||
@ -585,6 +585,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
dataModel.getVolume().removeListener(volumeListener);
|
||||
dataModel.getSecurityDepositPct().removeListener(securityDepositAsDoubleListener);
|
||||
dataModel.getBuyerAsTakerWithoutDeposit().removeListener(buyerAsTakerWithoutDepositListener);
|
||||
dataModel.getExtraInfo().removeListener(extraInfoStringListener);
|
||||
|
||||
//dataModel.feeFromFundingTxProperty.removeListener(feeFromFundingTxListener);
|
||||
dataModel.getIsXmrWalletFunded().removeListener(isWalletFundedListener);
|
||||
@ -665,7 +666,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
createOfferRequested = false;
|
||||
createOfferCanceled = true;
|
||||
OpenOfferManager openOfferManager = HavenoUtils.openOfferManager;
|
||||
Optional<OpenOffer> openOffer = openOfferManager.getOpenOfferById(offer.getId());
|
||||
Optional<OpenOffer> openOffer = openOfferManager.getOpenOffer(offer.getId());
|
||||
if (openOffer.isPresent()) {
|
||||
openOfferManager.cancelOpenOffer(openOffer.get(), () -> {
|
||||
UserThread.execute(() -> {
|
||||
@ -836,9 +837,17 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
}
|
||||
}
|
||||
|
||||
public void onFocusOutExtraInfoTextField(boolean oldValue, boolean newValue) {
|
||||
public void onFocusOutExtraInfoTextArea(boolean oldValue, boolean newValue) {
|
||||
if (oldValue && !newValue) {
|
||||
dataModel.setExtraInfo(extraInfo.get());
|
||||
onExtraInfoTextAreaChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void onExtraInfoTextAreaChanged() {
|
||||
extraInfoValidationResult.set(getExtraInfoValidationResult());
|
||||
updateButtonDisableState();
|
||||
if (extraInfoValidationResult.get().isValid) {
|
||||
setExtraInfoToModel();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1045,8 +1054,8 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
.show();
|
||||
}
|
||||
|
||||
CoinFormatter getBtcFormatter() {
|
||||
return btcFormatter;
|
||||
CoinFormatter getXmrFormatter() {
|
||||
return xmrFormatter;
|
||||
}
|
||||
|
||||
public boolean isShownAsBuyOffer() {
|
||||
@ -1064,7 +1073,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
public String getTradeAmount() {
|
||||
return OfferViewModelUtil.getTradeFeeWithFiatEquivalent(offerUtil,
|
||||
dataModel.getAmount().get(),
|
||||
btcFormatter);
|
||||
xmrFormatter);
|
||||
}
|
||||
|
||||
public String getSecurityDepositLabel() {
|
||||
@ -1084,7 +1093,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
return OfferViewModelUtil.getTradeFeeWithFiatEquivalentAndPercentage(offerUtil,
|
||||
dataModel.getSecurityDeposit(),
|
||||
dataModel.getAmount().get(),
|
||||
btcFormatter
|
||||
xmrFormatter
|
||||
);
|
||||
}
|
||||
|
||||
@ -1097,7 +1106,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
return OfferViewModelUtil.getTradeFeeWithFiatEquivalentAndPercentage(offerUtil,
|
||||
dataModel.getMaxMakerFee(),
|
||||
dataModel.getAmount().get(),
|
||||
btcFormatter);
|
||||
xmrFormatter);
|
||||
}
|
||||
|
||||
public String getMakerFeePercentage() {
|
||||
@ -1108,7 +1117,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
public String getTotalToPayInfo() {
|
||||
return OfferViewModelUtil.getTradeFeeWithFiatEquivalent(offerUtil,
|
||||
dataModel.totalToPay.get(),
|
||||
btcFormatter);
|
||||
xmrFormatter);
|
||||
}
|
||||
|
||||
public String getFundsStructure() {
|
||||
@ -1181,7 +1190,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
|
||||
private void setAmountToModel() {
|
||||
if (amount.get() != null && !amount.get().isEmpty()) {
|
||||
BigInteger amount = HavenoUtils.coinToAtomicUnits(DisplayUtils.parseToCoinWith4Decimals(this.amount.get(), btcFormatter));
|
||||
BigInteger amount = HavenoUtils.coinToAtomicUnits(DisplayUtils.parseToCoinWith4Decimals(this.amount.get(), xmrFormatter));
|
||||
|
||||
long maxTradeLimit = dataModel.getMaxTradeLimit();
|
||||
Price price = dataModel.getPrice().get();
|
||||
@ -1202,7 +1211,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
|
||||
private void setMinAmountToModel() {
|
||||
if (minAmount.get() != null && !minAmount.get().isEmpty()) {
|
||||
BigInteger minAmount = HavenoUtils.coinToAtomicUnits(DisplayUtils.parseToCoinWith4Decimals(this.minAmount.get(), btcFormatter));
|
||||
BigInteger minAmount = HavenoUtils.coinToAtomicUnits(DisplayUtils.parseToCoinWith4Decimals(this.minAmount.get(), xmrFormatter));
|
||||
|
||||
Price price = dataModel.getPrice().get();
|
||||
long maxTradeLimit = dataModel.getMaxTradeLimit();
|
||||
@ -1343,10 +1352,20 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
inputDataValid = inputDataValid && securityDepositValidator.validate(securityDeposit.get()).isValid;
|
||||
}
|
||||
|
||||
inputDataValid = inputDataValid && getExtraInfoValidationResult().isValid;
|
||||
|
||||
isNextButtonDisabled.set(!inputDataValid);
|
||||
isPlaceOfferButtonDisabled.set(createOfferRequested || !inputDataValid || !dataModel.getIsXmrWalletFunded().get());
|
||||
}
|
||||
|
||||
private ValidationResult getExtraInfoValidationResult() {
|
||||
if (extraInfo.get() != null && !extraInfo.get().isEmpty() && extraInfo.get().length() > Restrictions.MAX_EXTRA_INFO_LENGTH) {
|
||||
return new InputValidator.ValidationResult(false, Res.get("createOffer.extraInfo.invalid.tooLong", Restrictions.MAX_EXTRA_INFO_LENGTH));
|
||||
} else {
|
||||
return new InputValidator.ValidationResult(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateMarketPriceToManual() {
|
||||
final String currencyCode = dataModel.getTradeCurrencyCode().get();
|
||||
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
|
||||
|
@ -20,6 +20,7 @@ package haveno.desktop.main.offer.offerbook;
|
||||
import de.jensd.fx.fontawesome.AwesomeDude;
|
||||
import de.jensd.fx.fontawesome.AwesomeIcon;
|
||||
import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon;
|
||||
import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIconView;
|
||||
import haveno.common.UserThread;
|
||||
import haveno.common.app.DevEnv;
|
||||
import haveno.common.util.Tuple3;
|
||||
@ -1082,7 +1083,7 @@ abstract public class OfferBookView<R extends GridPane, M extends OfferBookViewM
|
||||
button.setPrefWidth(10000);
|
||||
}
|
||||
|
||||
final ImageView iconView2 = new ImageView();
|
||||
MaterialDesignIconView iconView2 = new MaterialDesignIconView(MaterialDesignIcon.PENCIL);
|
||||
final AutoTooltipButton button2 = new AutoTooltipButton();
|
||||
|
||||
{
|
||||
@ -1135,7 +1136,7 @@ abstract public class OfferBookView<R extends GridPane, M extends OfferBookViewM
|
||||
title = Res.get("shared.remove");
|
||||
button.setOnAction(e -> onRemoveOpenOffer(offer));
|
||||
|
||||
iconView2.setId("image-edit");
|
||||
iconView2.setSize("16px");
|
||||
button2.updateText(Res.get("shared.edit"));
|
||||
button2.setOnAction(e -> onEditOpenOffer(offer));
|
||||
button2.setManaged(true);
|
||||
|
@ -711,6 +711,6 @@ abstract class OfferBookViewModel extends ActivatableViewModel {
|
||||
abstract String getCurrencyCodeFromPreferences(OfferDirection direction);
|
||||
|
||||
public OpenOffer getOpenOffer(Offer offer) {
|
||||
return openOfferManager.getOpenOfferById(offer.getId()).orElse(null);
|
||||
return openOfferManager.getOpenOffer(offer.getId()).orElse(null);
|
||||
}
|
||||
}
|
||||
|
@ -284,6 +284,7 @@ class TakeOfferDataModel extends OfferDataModel {
|
||||
// handle error
|
||||
if (errorMsg != null) {
|
||||
new Popup().warning(errorMsg).show();
|
||||
log.warn("Error taking offer " + offer.getId() + ": " + errorMsg);
|
||||
errorMessageHandler.handleErrorMessage(errorMsg);
|
||||
}
|
||||
}
|
||||
|
@ -765,9 +765,7 @@ public abstract class Overlay<T extends Overlay<T>> {
|
||||
FormBuilder.getIconForLabel(AwesomeIcon.COPY, copyIcon, "1.1em");
|
||||
copyIcon.addEventHandler(MOUSE_CLICKED, mouseEvent -> {
|
||||
if (message != null) {
|
||||
String forClipboard = headLineLabel.getText() + System.lineSeparator() + message
|
||||
+ System.lineSeparator() + (messageHyperlinks == null ? "" : messageHyperlinks.toString());
|
||||
Utilities.copyToClipboard(forClipboard);
|
||||
Utilities.copyToClipboard(getClipboardText());
|
||||
Tooltip tp = new Tooltip(Res.get("shared.copiedToClipboard"));
|
||||
Node node = (Node) mouseEvent.getSource();
|
||||
UserThread.runAfter(() -> tp.hide(), 1);
|
||||
@ -1083,6 +1081,11 @@ public abstract class Overlay<T extends Overlay<T>> {
|
||||
return isDisplayed;
|
||||
}
|
||||
|
||||
public String getClipboardText() {
|
||||
return headLineLabel.getText() + System.lineSeparator() + message
|
||||
+ System.lineSeparator() + (messageHyperlinks == null ? "" : messageHyperlinks.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Popup{" +
|
||||
|
@ -342,7 +342,7 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
|
||||
BigInteger reservedAmount = isMyOffer ? offer.getReservedAmount() : null;
|
||||
|
||||
// get offer challenge
|
||||
OpenOffer myOpenOffer = HavenoUtils.openOfferManager.getOpenOfferById(offer.getId()).orElse(null);
|
||||
OpenOffer myOpenOffer = HavenoUtils.openOfferManager.getOpenOffer(offer.getId()).orElse(null);
|
||||
String offerChallenge = myOpenOffer == null ? null : myOpenOffer.getChallenge();
|
||||
|
||||
rows = 3;
|
||||
|
@ -37,10 +37,10 @@ import java.io.ByteArrayInputStream;
|
||||
public class QRCodeWindow extends Overlay<QRCodeWindow> {
|
||||
private static final Logger log = LoggerFactory.getLogger(QRCodeWindow.class);
|
||||
private final ImageView qrCodeImageView;
|
||||
private final String bitcoinURI;
|
||||
private final String moneroUri;
|
||||
|
||||
public QRCodeWindow(String bitcoinURI) {
|
||||
this.bitcoinURI = bitcoinURI;
|
||||
this.moneroUri = bitcoinURI;
|
||||
final byte[] imageBytes = QRCode
|
||||
.from(bitcoinURI)
|
||||
.withSize(300, 300)
|
||||
@ -70,7 +70,7 @@ public class QRCodeWindow extends Overlay<QRCodeWindow> {
|
||||
GridPane.setHalignment(qrCodeImageView, HPos.CENTER);
|
||||
gridPane.getChildren().add(qrCodeImageView);
|
||||
|
||||
String request = bitcoinURI.replace("%20", " ").replace("?", "\n?").replace("&", "\n&");
|
||||
String request = moneroUri.replace("%20", " ").replace("?", "\n?").replace("&", "\n&");
|
||||
Label infoLabel = new AutoTooltipLabel(Res.get("qRCodeWindow.request", request));
|
||||
infoLabel.setMouseTransparent(true);
|
||||
infoLabel.setWrapText(true);
|
||||
@ -87,4 +87,8 @@ public class QRCodeWindow extends Overlay<QRCodeWindow> {
|
||||
applyStyles();
|
||||
display();
|
||||
}
|
||||
|
||||
public String getClipboardText() {
|
||||
return moneroUri;
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user