Merge branch 'haveno-dex:master' into issue1253

This commit is contained in:
preland 2024-10-29 11:26:04 -05:00 committed by GitHub
commit 10285cd37a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
118 changed files with 1329 additions and 431 deletions

View File

@ -11,7 +11,7 @@ jobs:
build: build:
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest, macos-13, windows-latest] os: [ubuntu-22.04, macos-13, windows-latest]
fail-fast: false fail-fast: false
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
@ -37,10 +37,11 @@ jobs:
name: cached-localnet name: cached-localnet
path: .localnet path: .localnet
- name: Install dependencies - name: Install dependencies
if: ${{ matrix.os == 'ubuntu-latest' }} if: ${{ matrix.os == 'ubuntu-22.04' }}
run: | run: |
sudo apt update sudo apt update
sudo apt install -y rpm sudo apt install -y rpm libfuse2 flatpak flatpak-builder appstream
flatpak remote-add --if-not-exists --user flathub https://dl.flathub.org/repo/flathub.flatpakrepo
- name: Install WiX Toolset - name: Install WiX Toolset
if: ${{ matrix.os == 'windows-latest' }} if: ${{ matrix.os == 'windows-latest' }}
run: | run: |
@ -52,26 +53,88 @@ jobs:
./gradlew clean build --refresh-keys --refresh-dependencies ./gradlew clean build --refresh-keys --refresh-dependencies
./gradlew packageInstallers ./gradlew packageInstallers
working-directory: . working-directory: .
- name: Move Release Files on Unix
if: ${{ matrix.os == 'ubuntu-latest' || matrix.os == 'macos-13' }} # get version from jar
- name: Set Version Unix
if: ${{ matrix.os == 'ubuntu-22.04' || matrix.os == 'macos-13' }}
run: | run: |
mkdir ${{ github.workspace }}/release export VERSION=$(ls desktop/build/temp-*/binaries/desktop-*.jar.SHA-256 | grep -Eo 'desktop-[0-9]+\.[0-9]+\.[0-9]+' | sed 's/desktop-//')
if [ "${{ matrix.os }}" == "ubuntu-latest" ]; then echo "VERSION=$VERSION" >> $GITHUB_ENV
mv desktop/build/temp-*/binaries/haveno-*.rpm ${{ github.workspace }}/release - name: Set Version Windows
mv desktop/build/temp-*/binaries/haveno_*.deb ${{ github.workspace }}/release if: ${{ matrix.os == 'windows-latest' }}
run: |
$VERSION = (Get-ChildItem -Path desktop\build\temp-*/binaries\desktop-*.jar.SHA-256).Name -replace 'desktop-', '' -replace '-.*', ''
"VERSION=$VERSION" | Out-File -FilePath $env:GITHUB_ENV -Append
shell: powershell
- name: Move Release Files on Unix
if: ${{ matrix.os == 'ubuntu-22.04' || matrix.os == 'macos-13' }}
run: |
if [ "${{ matrix.os }}" == "ubuntu-22.04" ]; then
mkdir ${{ github.workspace }}/release-linux-rpm
mkdir ${{ github.workspace }}/release-linux-deb
mkdir ${{ github.workspace }}/release-linux-flatpak
mkdir ${{ github.workspace }}/release-linux-appimage
mv desktop/build/temp-*/binaries/haveno-*.rpm ${{ github.workspace }}/release-linux-rpm/haveno-v${{ env.VERSION }}-linux-x86_64-installer.rpm
mv desktop/build/temp-*/binaries/haveno_*.deb ${{ github.workspace }}/release-linux-deb/haveno-v${{ env.VERSION }}-linux-x86_64-installer.deb
mv desktop/build/temp-*/binaries/*.flatpak ${{ github.workspace }}/release-linux-flatpak/haveno-v${{ env.VERSION }}-linux-x86_64.flatpak
mv desktop/build/temp-*/binaries/haveno_*.AppImage ${{ github.workspace }}/release-linux-appimage/haveno-v${{ env.VERSION }}-linux-x86_64.AppImage
cp desktop/build/temp-*/binaries/desktop-*.jar.SHA-256 ${{ github.workspace }}/release-linux-deb
cp desktop/build/temp-*/binaries/desktop-*.jar.SHA-256 ${{ github.workspace }}/release-linux-rpm
cp desktop/build/temp-*/binaries/desktop-*.jar.SHA-256 ${{ github.workspace }}/release-linux-appimage
cp desktop/build/temp-*/binaries/desktop-*.jar.SHA-256 ${{ github.workspace }}/release-linux-flatpak
else else
mv desktop/build/temp-*/binaries/Haveno-*.dmg ${{ github.workspace }}/release mkdir ${{ github.workspace }}/release-macos
mv desktop/build/temp-*/binaries/Haveno-*.dmg ${{ github.workspace }}/release-macos/haveno-v${{ env.VERSION }}-macos-installer.dmg
cp desktop/build/temp-*/binaries/desktop-*.jar.SHA-256 ${{ github.workspace }}/release-macos
fi fi
mv desktop/build/temp-*/binaries/desktop-*.jar.SHA-256 ${{ github.workspace }}/release
shell: bash shell: bash
- name: Move Release Files on Windows - name: Move Release Files on Windows
if: ${{ matrix.os == 'windows-latest' }} if: ${{ matrix.os == 'windows-latest' }}
run: | run: |
mkdir ${{ github.workspace }}/release mkdir ${{ github.workspace }}/release-windows
Move-Item -Path desktop\build\temp-*/binaries\Haveno-*.exe -Destination ${{ github.workspace }}/release Move-Item -Path desktop\build\temp-*/binaries\Haveno-*.exe -Destination ${{ github.workspace }}/release-windows/haveno-v${{ env.VERSION }}-windows-installer.exe
Move-Item -Path desktop\build\temp-*/binaries\desktop-*.jar.SHA-256 -Destination ${{ github.workspace }}/release Move-Item -Path desktop\build\temp-*/binaries\desktop-*.jar.SHA-256 -Destination ${{ github.workspace }}/release-windows
shell: powershell shell: powershell
# win
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
name: "Windows artifacts"
if: ${{ matrix.os == 'windows-latest'}}
with: with:
name: HavenoInstaller-${{ matrix.os }} name: haveno-windows
path: ${{ github.workspace }}/release path: ${{ github.workspace }}/release-windows
# macos
- uses: actions/upload-artifact@v3
name: "macOS artifacts"
if: ${{ matrix.os == 'macos-13' }}
with:
name: haveno-macos
path: ${{ github.workspace }}/release-macos
# linux
- uses: actions/upload-artifact@v3
name: "Linux - deb artifact"
if: ${{ matrix.os == 'ubuntu-22.04' }}
with:
name: haveno-linux-deb
path: ${{ github.workspace }}/release-linux-deb
- uses: actions/upload-artifact@v3
name: "Linux - rpm artifact"
if: ${{ matrix.os == 'ubuntu-22.04' }}
with:
name: haveno-linux-rpm
path: ${{ github.workspace }}/release-linux-rpm
- uses: actions/upload-artifact@v3
name: "Linux - AppImage artifact"
if: ${{ matrix.os == 'ubuntu-22.04' }}
with:
name: haveno-linux-appimage
path: ${{ github.workspace }}/release-linux-appimage
- uses: actions/upload-artifact@v3
name: "Linux - flatpak artifact"
if: ${{ matrix.os == 'ubuntu-22.04' }}
with:
name: haveno-linux-flatpak
path: ${{ github.workspace }}/release-linux-flatpak

2
.gitignore vendored
View File

@ -37,3 +37,5 @@ deploy
.vscode .vscode
.vim/* .vim/*
*/.factorypath */.factorypath
.flatpak-builder
exchange.haveno.Haveno.yaml

View File

@ -1,9 +1,8 @@
<div align="center"> <div align="center">
<img src="https://raw.githubusercontent.com/haveno-dex/haveno-meta/721e52919b28b44d12b6e1e5dac57265f1c05cda/logo/haveno_logo_landscape.svg" alt="Haveno logo"> <img src="https://raw.githubusercontent.com/haveno-dex/haveno-meta/721e52919b28b44d12b6e1e5dac57265f1c05cda/logo/haveno_logo_landscape.svg" alt="Haveno logo">
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/505405b43cb74d5a996f106a3371588e)](https://app.codacy.com/gh/haveno-dex/haveno/dashboard)
![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/haveno-dex/haveno/build.yml?branch=master) ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/haveno-dex/haveno/build.yml?branch=master)
[![GitHub issues with bounty](https://img.shields.io/github/issues-search/haveno-dex/haveno?color=%23fef2c0&label=Issues%20with%20bounties&query=project%3Ahaveno-dex%2F2)](https://github.com/orgs/haveno-dex/projects/2) | [![GitHub issues with bounty](https://img.shields.io/github/issues-search/haveno-dex/haveno?color=%23fef2c0&label=Issues%20with%20bounties&query=is%3Aopen+is%3Aissue+label%3A%F0%9F%92%B0bounty)](https://github.com/haveno-dex/haveno/issues?q=is%3Aopen+is%3Aissue+label%3A%F0%9F%92%B0bounty)
[![Twitter Follow](https://img.shields.io/twitter/follow/HavenoDEX?style=social)](https://twitter.com/havenodex) [![Twitter Follow](https://img.shields.io/twitter/follow/HavenoDEX?style=social)](https://twitter.com/havenodex)
[![Matrix rooms](https://img.shields.io/badge/Matrix%20room-%23haveno-blue)](https://matrix.to/#/#haveno:monero.social) [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](https://github.com/haveno-dex/.github/blob/master/CODE_OF_CONDUCT.md) [![Matrix rooms](https://img.shields.io/badge/Matrix%20room-%23haveno-blue)](https://matrix.to/#/#haveno:monero.social) [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](https://github.com/haveno-dex/.github/blob/master/CODE_OF_CONDUCT.md)
</div> </div>
@ -14,7 +13,7 @@ Haveno (pronounced ha‧ve‧no) is an open source platform to exchange [Monero]
Main features: Main features:
- All communications are routed through **Tor**, to preserve your privacy - Communications are routed through **Tor**, to preserve your privacy.
- Trades are **peer-to-peer**: trades on Haveno happen between people only, there is no central authority. - Trades are **peer-to-peer**: trades on Haveno happen between people only, there is no central authority.
@ -24,24 +23,24 @@ Main features:
See the [FAQ on our website](https://haveno.exchange/faq/) for more information. See the [FAQ on our website](https://haveno.exchange/faq/) for more information.
## Status of the project ## Installing Haveno
Haveno can be used on Monero's main network by using a third party Haveno network. We do not officially endorse any networks at this time. Haveno can be installed on Linux, macOS, and Windows by using a third party installer and network. We do not endorse any networks at this time.
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 network. 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 [start your own 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. Note that Haveno is being actively developed. If you find issues or bugs, please let us know.
Main repositories: ## Main repositories
- **[haveno](https://github.com/haveno-dex/haveno)** - This repository. The core of Haveno. - **[haveno](https://github.com/haveno-dex/haveno)** - This repository. The core of Haveno.
- **[haveno-ui](https://github.com/haveno-dex/haveno-ui)** - The user interface.
- **[haveno-ts](https://github.com/haveno-dex/haveno-ts)** - TypeScript library for using Haveno. - **[haveno-ts](https://github.com/haveno-dex/haveno-ts)** - TypeScript library for using Haveno.
- **[haveno-ui](https://github.com/haveno-dex/haveno-ui)** - A new user interface (WIP).
- **[haveno-meta](https://github.com/haveno-dex/haveno-meta)** - For project-wide discussions and proposals. - **[haveno-meta](https://github.com/haveno-dex/haveno-meta)** - For project-wide discussions and proposals.
If you wish to help, take a look at the repositories above and look for open issues. We run a bounty program to incentivize development. See [Bounties](#bounties) If you wish to help, take a look at the repositories above and look for open issues. We run a bounty program to incentivize development. See [Bounties](#bounties).
The PGP keys of the core team members are in `gpg_keys/`.
## Keep in touch and help out! ## Keep in touch and help out!
@ -63,7 +62,7 @@ If you are not able to contribute code and want to contribute development resour
## Bounties ## Bounties
To incentivize development and reward contributors we adopt a simple bounty system. Contributors may be awarded bounties after completing a task (resolving an issue). Take a look at the issues eligible for a bounty on the [dedicated Kanban board](https://github.com/orgs/haveno-dex/projects/2) or look for [issues labelled '💰bounty'](https://github.com/haveno-dex/haveno/issues?q=is%3Aissue+is%3Aopen+label%3A%F0%9F%92%B0bounty) in the main `haveno` repository. [Details and conditions for receiving a bounty](docs/bounties.md). To incentivize development and reward contributors, we adopt a simple bounty system. Contributors may be awarded bounties after completing a task (resolving an issue). Take a look at the [issues labeled '💰bounty'](https://github.com/haveno-dex/haveno/issues?q=is%3Aopen+is%3Aissue+label%3A%F0%9F%92%B0bounty) in the main `haveno` repository. [Details and conditions for receiving a bounty](docs/bounties.md).
## Support and sponsorships ## Support and sponsorships
@ -72,7 +71,7 @@ To bring Haveno to life, we need resources. If you have the possibility, please
### Monero ### Monero
<p> <p>
<img src="https://raw.githubusercontent.com/haveno-dex/haveno/master/media/donate_monero.png" alt="Donate Monero" width="150" height="150"><br> <img src="https://raw.githubusercontent.com/haveno-dex/haveno/master/media/donate_monero.png" alt="Donate Monero" width="115" height="115"><br>
<code>42sjokkT9FmiWPqVzrWPFE5NCJXwt96bkBozHf4vgLR9hXyJDqKHEHKVscAARuD7in5wV1meEcSTJTanCTDzidTe2cFXS1F</code> <code>42sjokkT9FmiWPqVzrWPFE5NCJXwt96bkBozHf4vgLR9hXyJDqKHEHKVscAARuD7in5wV1meEcSTJTanCTDzidTe2cFXS1F</code>
</p> </p>
@ -81,6 +80,6 @@ If you are using a wallet that supports OpenAlias (like the 'official' CLI and G
### Bitcoin ### Bitcoin
<p> <p>
<img src="https://raw.githubusercontent.com/haveno-dex/haveno/master/media/donate_bitcoin.png" alt="Donate Bitcoin" width="150" height="150"><br> <img src="https://raw.githubusercontent.com/haveno-dex/haveno/master/media/donate_bitcoin.png" alt="Donate Bitcoin" width="115" height="115"><br>
<code>1AKq3CE1yBAnxGmHXbNFfNYStcByNDc5gQ</code> <code>1AKq3CE1yBAnxGmHXbNFfNYStcByNDc5gQ</code>
</p> </p>

View File

@ -78,7 +78,7 @@ public class ApiTestMain {
} catch (Throwable ex) { } catch (Throwable ex) {
err.println("Fault: An unexpected error occurred. " + err.println("Fault: An unexpected error occurred. " +
"Please file a report at https://haveno.exchange/issues"); "Please file a report at https://github.com/haveno-dex/haveno/issues");
ex.printStackTrace(err); ex.printStackTrace(err);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }

View File

@ -49,7 +49,7 @@ configure(subprojects) {
gsonVersion = '2.8.5' gsonVersion = '2.8.5'
guavaVersion = '32.1.1-jre' guavaVersion = '32.1.1-jre'
guiceVersion = '7.0.0' guiceVersion = '7.0.0'
moneroJavaVersion = '0.8.31' moneroJavaVersion = '0.8.33'
httpclient5Version = '5.0' httpclient5Version = '5.0'
hamcrestVersion = '2.2' hamcrestVersion = '2.2'
httpclientVersion = '4.5.12' httpclientVersion = '4.5.12'
@ -334,6 +334,7 @@ configure(project(':p2p')) {
implementation "com.google.protobuf:protobuf-java:$protobufVersion" implementation "com.google.protobuf:protobuf-java:$protobufVersion"
implementation "org.fxmisc.easybind:easybind:$easybindVersion" implementation "org.fxmisc.easybind:easybind:$easybindVersion"
implementation "org.slf4j:slf4j-api:$slf4jVersion" implementation "org.slf4j:slf4j-api:$slf4jVersion"
implementation "org.apache.commons:commons-lang3:$langVersion"
implementation("com.github.haveno-dex.netlayer:tor.external:$netlayerVersion") { implementation("com.github.haveno-dex.netlayer:tor.external:$netlayerVersion") {
exclude(module: 'slf4j-api') exclude(module: 'slf4j-api')
} }
@ -456,14 +457,14 @@ configure(project(':core')) {
doLast { doLast {
// get monero binaries download url // get monero binaries download url
Map moneroBinaries = [ Map moneroBinaries = [
'linux-x86_64' : 'https://github.com/haveno-dex/monero/releases/download/release3/monero-bins-haveno-linux-x86_64.tar.gz', 'linux-x86_64' : 'https://github.com/haveno-dex/monero/releases/download/release4/monero-bins-haveno-linux-x86_64.tar.gz',
'linux-x86_64-sha256' : '591e63c1e3249e0cfbba74f0302022160f64f049d06abff95417ad3ecb588581', 'linux-x86_64-sha256' : '0810808292fd5ad595a46a7fcc8ecb28d251d80f8d75c0e7a7d51afbeb413b68',
'linux-aarch64' : 'https://github.com/haveno-dex/monero/releases/download/release3/monero-bins-haveno-linux-aarch64.tar.gz', 'linux-aarch64' : 'https://github.com/haveno-dex/monero/releases/download/release4/monero-bins-haveno-linux-aarch64.tar.gz',
'linux-aarch64-sha256' : 'fb0a91d07dbbc30646af8007205dbd11c59fb1d124a3b2d703511d8ee2739acc', 'linux-aarch64-sha256' : '61222ee8e2021aaf59ab8813543afc5548f484190ee9360bc9cfa8fdf21cc1de',
'mac' : 'https://github.com/haveno-dex/monero/releases/download/release3/monero-bins-haveno-mac.tar.gz', 'mac' : 'https://github.com/haveno-dex/monero/releases/download/release4/monero-bins-haveno-mac.tar.gz',
'mac-sha256' : '9eb01951976767372a3d10180c092af937afe6494928ea73e311476be5c0eba3', 'mac-sha256' : '5debb8d8d8dd63809e8351368a11aa85c47987f1a8a8f2dcca343e60bcff3287',
'windows' : 'https://github.com/haveno-dex/monero/releases/download/release3/monero-bins-haveno-windows.zip', 'windows' : 'https://github.com/haveno-dex/monero/releases/download/release4/monero-bins-haveno-windows.zip',
'windows-sha256' : '49b84fab3a1f69564068fecff105b6079b843d99792409dffca4a66eb279288f' 'windows-sha256' : 'd7c14f029db37ae2a8bc6b74c35f572283257df5fbcc8cc97b704d1a97be9888'
] ]
String osKey String osKey
@ -609,7 +610,7 @@ configure(project(':desktop')) {
apply plugin: 'com.github.johnrengelman.shadow' apply plugin: 'com.github.johnrengelman.shadow'
apply from: 'package/package.gradle' apply from: 'package/package.gradle'
version = '1.0.11-SNAPSHOT' version = '1.0.12-SNAPSHOT'
jar.manifest.attributes( jar.manifest.attributes(
"Implementation-Title": project.name, "Implementation-Title": project.name,

View File

@ -21,6 +21,7 @@ import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder; import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.filter.ThresholdFilter;
import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.rolling.FixedWindowRollingPolicy; import ch.qos.logback.core.rolling.FixedWindowRollingPolicy;
import ch.qos.logback.core.rolling.RollingFileAppender; import ch.qos.logback.core.rolling.RollingFileAppender;
@ -52,11 +53,12 @@ public class Log {
SizeBasedTriggeringPolicy<ILoggingEvent> triggeringPolicy = new SizeBasedTriggeringPolicy<>(); SizeBasedTriggeringPolicy<ILoggingEvent> triggeringPolicy = new SizeBasedTriggeringPolicy<>();
triggeringPolicy.setMaxFileSize(FileSize.valueOf("10MB")); triggeringPolicy.setMaxFileSize(FileSize.valueOf("10MB"));
triggeringPolicy.setContext(loggerContext);
triggeringPolicy.start(); triggeringPolicy.start();
PatternLayoutEncoder encoder = new PatternLayoutEncoder(); PatternLayoutEncoder encoder = new PatternLayoutEncoder();
encoder.setContext(loggerContext); encoder.setContext(loggerContext);
encoder.setPattern("%d{MMM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{15}: %msg %xEx%n"); encoder.setPattern("%d{MMM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{15}: %msg%n");
encoder.start(); encoder.start();
appender.setEncoder(encoder); appender.setEncoder(encoder);
@ -64,25 +66,43 @@ public class Log {
appender.setTriggeringPolicy(triggeringPolicy); appender.setTriggeringPolicy(triggeringPolicy);
appender.start(); appender.start();
logbackLogger = loggerContext.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
logbackLogger.addAppender(appender);
logbackLogger.setLevel(Level.INFO);
// log errors in separate file // log errors in separate file
// not working as expected still.... damn logback... PatternLayoutEncoder errorEncoder = new PatternLayoutEncoder();
/* FileAppender errorAppender = new FileAppender(); errorEncoder.setContext(loggerContext);
errorAppender.setEncoder(encoder); errorEncoder.setPattern("%d{MMM-dd HH:mm:ss.SSS} [%thread] %-5level %logger: %msg%n%ex");
errorEncoder.start();
RollingFileAppender<ILoggingEvent> errorAppender = new RollingFileAppender<>();
errorAppender.setEncoder(errorEncoder);
errorAppender.setName("Error"); errorAppender.setName("Error");
errorAppender.setContext(loggerContext); errorAppender.setContext(loggerContext);
errorAppender.setFile(fileName + "_error.log"); errorAppender.setFile(fileName + "_error.log");
LevelFilter levelFilter = new LevelFilter();
levelFilter.setLevel(Level.ERROR); FixedWindowRollingPolicy errorRollingPolicy = new FixedWindowRollingPolicy();
levelFilter.setOnMatch(FilterReply.ACCEPT); errorRollingPolicy.setContext(loggerContext);
levelFilter.setOnMismatch(FilterReply.DENY); errorRollingPolicy.setParent(errorAppender);
levelFilter.start(); errorRollingPolicy.setFileNamePattern(fileName + "_error_%i.log");
errorAppender.addFilter(levelFilter); errorRollingPolicy.setMinIndex(1);
errorRollingPolicy.setMaxIndex(20);
errorRollingPolicy.start();
SizeBasedTriggeringPolicy<ILoggingEvent> errorTriggeringPolicy = new SizeBasedTriggeringPolicy<>();
errorTriggeringPolicy.setMaxFileSize(FileSize.valueOf("10MB"));
errorTriggeringPolicy.start();
ThresholdFilter thresholdFilter = new ThresholdFilter();
thresholdFilter.setLevel("WARN");
thresholdFilter.start();
errorAppender.setRollingPolicy(errorRollingPolicy);
errorAppender.setTriggeringPolicy(errorTriggeringPolicy);
errorAppender.addFilter(thresholdFilter);
errorAppender.start(); errorAppender.start();
logbackLogger.addAppender(errorAppender);*/
logbackLogger = loggerContext.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
logbackLogger.addAppender(errorAppender);
logbackLogger.addAppender(appender);
logbackLogger.setLevel(Level.INFO);
} }
public static void setCustomLogLevel(String pattern, Level logLevel) { public static void setCustomLogLevel(String pattern, Level logLevel) {

View File

@ -28,7 +28,7 @@ import static com.google.common.base.Preconditions.checkArgument;
public class Version { public class Version {
// The application versions // The application versions
// We use semantic versioning with major, minor and patch // We use semantic versioning with major, minor and patch
public static final String VERSION = "1.0.11"; public static final String VERSION = "1.0.12";
/** /**
* Holds a list of the tagged resource files for optimizing the getData requests. * Holds a list of the tagged resource files for optimizing the getData requests.

View File

@ -68,8 +68,7 @@ public class FileUtil {
pruneBackup(backupFileDir, numMaxBackupFiles); pruneBackup(backupFileDir, numMaxBackupFiles);
} catch (IOException e) { } catch (IOException e) {
log.error("Backup key failed: " + e.getMessage()); log.error("Backup key failed: {}\n", e.getMessage(), e);
e.printStackTrace();
} }
} }
} }
@ -97,7 +96,7 @@ public class FileUtil {
try { try {
FileUtils.deleteDirectory(backupFileDir); FileUtils.deleteDirectory(backupFileDir);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); log.error("Delete backup key failed: {}\n", e.getMessage(), e);
} }
} }
@ -173,8 +172,7 @@ public class FileUtil {
} }
} }
} catch (Throwable t) { } catch (Throwable t) {
log.error(t.toString()); log.error("Could not delete file, error={}\n", t.getMessage(), t);
t.printStackTrace();
throw new IOException(t); throw new IOException(t);
} }
} }

View File

@ -69,11 +69,7 @@ public class CommonSetup {
"The system tray is not supported on the current platform.".equals(throwable.getMessage())) { "The system tray is not supported on the current platform.".equals(throwable.getMessage())) {
log.warn(throwable.getMessage()); log.warn(throwable.getMessage());
} else { } else {
log.error("Uncaught Exception from thread " + Thread.currentThread().getName()); log.error("Uncaught Exception from thread {}, error={}\n", Thread.currentThread().getName(), throwable.getMessage(), throwable);
log.error("throwableMessage= " + throwable.getMessage());
log.error("throwableClass= " + throwable.getClass());
log.error("Stack trace:\n" + ExceptionUtils.getStackTrace(throwable));
throwable.printStackTrace();
UserThread.execute(() -> uncaughtExceptionHandler.handleUncaughtException(throwable, false)); UserThread.execute(() -> uncaughtExceptionHandler.handleUncaughtException(throwable, false));
} }
}; };
@ -113,8 +109,7 @@ public class CommonSetup {
if (!pathOfCodeSource.endsWith("classes")) if (!pathOfCodeSource.endsWith("classes"))
log.info("Path to Haveno jar file: " + pathOfCodeSource); log.info("Path to Haveno jar file: " + pathOfCodeSource);
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
log.error(e.toString()); log.error(ExceptionUtils.getStackTrace(e));
e.printStackTrace();
} }
} }
} }

View File

@ -25,6 +25,8 @@ import java.util.Arrays;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import org.apache.commons.lang3.exception.ExceptionUtils;
@Slf4j @Slf4j
public class TaskRunner<T extends Model> { public class TaskRunner<T extends Model> {
private final Queue<Class<? extends Task<T>>> tasks = new LinkedBlockingQueue<>(); private final Queue<Class<? extends Task<T>>> tasks = new LinkedBlockingQueue<>();
@ -67,8 +69,8 @@ public class TaskRunner<T extends Model> {
log.info("Run task: " + currentTask.getSimpleName()); log.info("Run task: " + currentTask.getSimpleName());
currentTask.getDeclaredConstructor(TaskRunner.class, sharedModelClass).newInstance(this, sharedModel).run(); currentTask.getDeclaredConstructor(TaskRunner.class, sharedModelClass).newInstance(this, sharedModel).run();
} catch (Throwable throwable) { } catch (Throwable throwable) {
throwable.printStackTrace(); log.error(ExceptionUtils.getStackTrace(throwable));
handleErrorMessage("Error at taskRunner: " + throwable.getMessage()); handleErrorMessage("Error at taskRunner, error=" + throwable.getMessage());
} }
} else { } else {
resultHandler.handleResult(); resultHandler.handleResult();

View File

@ -331,8 +331,7 @@ public class Utilities {
clipboard.setContent(clipboardContent); clipboard.setContent(clipboardContent);
} }
} catch (Throwable e) { } catch (Throwable e) {
log.error("copyToClipboard failed " + e.getMessage()); log.error("copyToClipboard failed: {}\n", e.getMessage(), e);
e.printStackTrace();
} }
} }

View File

@ -247,6 +247,10 @@ public class CoreApi {
xmrConnectionService.setAutoSwitch(autoSwitch); xmrConnectionService.setAutoSwitch(autoSwitch);
} }
public boolean getXmrConnectionAutoSwitch() {
return xmrConnectionService.getAutoSwitch();
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Monero node // Monero node
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -260,11 +264,11 @@ public class CoreApi {
} }
public void startXmrNode(XmrNodeSettings settings) throws IOException { public void startXmrNode(XmrNodeSettings settings) throws IOException {
xmrLocalNode.startNode(settings); xmrLocalNode.start(settings);
} }
public void stopXmrNode() { public void stopXmrNode() {
xmrLocalNode.stopNode(); xmrLocalNode.stop();
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////

View File

@ -52,6 +52,9 @@ import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import org.apache.commons.lang3.exception.ExceptionUtils;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -204,7 +207,7 @@ public class CoreDisputesService {
throw new IllegalStateException(errMessage, err); throw new IllegalStateException(errMessage, err);
}); });
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); log.error(ExceptionUtils.getStackTrace(e));
throw new IllegalStateException(e.getMessage() == null ? ("Error resolving dispute for trade " + trade.getId()) : e.getMessage()); throw new IllegalStateException(e.getMessage() == null ? ("Error resolving dispute for trade " + trade.getId()) : e.getMessage());
} }
} }

View File

@ -3,7 +3,12 @@ package haveno.core.api;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import haveno.core.api.model.TradeInfo; import haveno.core.api.model.TradeInfo;
import haveno.core.support.messages.ChatMessage; import haveno.core.support.messages.ChatMessage;
import haveno.core.trade.BuyerTrade;
import haveno.core.trade.HavenoUtils;
import haveno.core.trade.MakerTrade;
import haveno.core.trade.SellerTrade;
import haveno.core.trade.Trade; import haveno.core.trade.Trade;
import haveno.core.trade.Trade.Phase;
import haveno.proto.grpc.NotificationMessage; import haveno.proto.grpc.NotificationMessage;
import haveno.proto.grpc.NotificationMessage.NotificationType; import haveno.proto.grpc.NotificationMessage.NotificationType;
import java.util.Iterator; import java.util.Iterator;
@ -46,7 +51,18 @@ public class CoreNotificationService {
.build()); .build());
} }
public void sendTradeNotification(Trade trade, String title, String message) { public void sendTradeNotification(Trade trade, Phase phase, String title, String message) {
// play chime when maker's trade is taken
if (trade instanceof MakerTrade && phase == Trade.Phase.DEPOSITS_PUBLISHED) HavenoUtils.playChimeSound();
// play chime when buyer can confirm payment sent
if (trade instanceof BuyerTrade && phase == Trade.Phase.DEPOSITS_UNLOCKED) HavenoUtils.playChimeSound();
// play chime when seller sees buyer confirm payment sent
if (trade instanceof SellerTrade && phase == Trade.Phase.PAYMENT_SENT) HavenoUtils.playChimeSound();
// send notification
sendNotification(NotificationMessage.newBuilder() sendNotification(NotificationMessage.newBuilder()
.setType(NotificationType.TRADE_UPDATE) .setType(NotificationType.TRADE_UPDATE)
.setTrade(TradeInfo.toTradeInfo(trade).toProtoMessage()) .setTrade(TradeInfo.toTradeInfo(trade).toProtoMessage())
@ -57,6 +73,7 @@ public class CoreNotificationService {
} }
public void sendChatNotification(ChatMessage chatMessage) { public void sendChatNotification(ChatMessage chatMessage) {
HavenoUtils.playChimeSound();
sendNotification(NotificationMessage.newBuilder() sendNotification(NotificationMessage.newBuilder()
.setType(NotificationType.CHAT_MESSAGE) .setType(NotificationType.CHAT_MESSAGE)
.setTimestamp(System.currentTimeMillis()) .setTimestamp(System.currentTimeMillis())

View File

@ -284,6 +284,7 @@ public class CoreOffersService {
useSavingsWallet, useSavingsWallet,
triggerPriceAsLong, triggerPriceAsLong,
reserveExactAmount, reserveExactAmount,
true,
resultHandler::accept, resultHandler::accept,
errorMessageHandler); errorMessageHandler);
} }

View File

@ -66,6 +66,8 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.function.Consumer; import java.util.function.Consumer;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
@Singleton @Singleton
@ -161,7 +163,7 @@ class CoreTradesService {
errorMessageHandler errorMessageHandler
); );
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); log.error(ExceptionUtils.getStackTrace(e));
errorMessageHandler.handleErrorMessage(e.getMessage()); errorMessageHandler.handleErrorMessage(e.getMessage());
} }
} }

View File

@ -41,6 +41,9 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.commons.lang3.exception.ExceptionUtils;
import javafx.beans.property.IntegerProperty; import javafx.beans.property.IntegerProperty;
import javafx.beans.property.LongProperty; import javafx.beans.property.LongProperty;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
@ -346,6 +349,11 @@ public final class XmrConnectionService {
connectionList.setAutoSwitch(autoSwitch); connectionList.setAutoSwitch(autoSwitch);
} }
public boolean getAutoSwitch() {
accountService.checkAccountOpen();
return connectionList.getAutoSwitch();
}
public boolean isConnectionLocalHost() { public boolean isConnectionLocalHost() {
return isConnectionLocalHost(getConnection()); return isConnectionLocalHost(getConnection());
} }
@ -464,7 +472,7 @@ public final class XmrConnectionService {
log.info(getClass() + ".onAccountOpened() called"); log.info(getClass() + ".onAccountOpened() called");
initialize(); initialize();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); log.error("Error initializing connection service after account opened, error={}\n", e.getMessage(), e);
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
@ -491,7 +499,7 @@ public final class XmrConnectionService {
xmrLocalNode.addListener(new XmrLocalNodeListener() { xmrLocalNode.addListener(new XmrLocalNodeListener() {
@Override @Override
public void onNodeStarted(MoneroDaemonRpc daemon) { public void onNodeStarted(MoneroDaemonRpc daemon) {
log.info("Local monero node started"); log.info("Local monero node started, height={}", daemon.getHeight());
} }
@Override @Override
@ -580,7 +588,7 @@ public final class XmrConnectionService {
// restore auto switch // restore auto switch
if (coreContext.isApiUser()) connectionManager.setAutoSwitch(connectionList.getAutoSwitch()); if (coreContext.isApiUser()) connectionManager.setAutoSwitch(connectionList.getAutoSwitch());
else connectionManager.setAutoSwitch(true); else connectionManager.setAutoSwitch(true); // auto switch is always enabled on desktop ui
// start local node if applicable // start local node if applicable
maybeStartLocalNode(); maybeStartLocalNode();
@ -620,10 +628,9 @@ public final class XmrConnectionService {
if (connectionManager.getConnection() != null && xmrLocalNode.equalsUri(connectionManager.getConnection().getUri()) && !xmrLocalNode.isDetected() && !xmrLocalNode.shouldBeIgnored()) { if (connectionManager.getConnection() != null && xmrLocalNode.equalsUri(connectionManager.getConnection().getUri()) && !xmrLocalNode.isDetected() && !xmrLocalNode.shouldBeIgnored()) {
try { try {
log.info("Starting local node"); log.info("Starting local node");
xmrLocalNode.startMoneroNode(); xmrLocalNode.start();
} catch (Exception e) { } catch (Exception e) {
log.warn("Unable to start local monero node: " + e.getMessage()); log.error("Unable to start local monero node, error={}\n", e.getMessage(), e);
e.printStackTrace();
} }
} }
} }
@ -711,18 +718,23 @@ public final class XmrConnectionService {
// skip handling if shutting down // skip handling if shutting down
if (isShutDownStarted) return; if (isShutDownStarted) return;
// fallback to provided nodes if custom connection fails on startup // fallback to provided or public nodes if custom connection fails on startup
if (lastInfo == null && "".equals(config.xmrNode) && preferences.getMoneroNodesOption() == XmrNodes.MoneroNodesOption.CUSTOM) { if (lastInfo == null && "".equals(config.xmrNode) && preferences.getMoneroNodesOption() == XmrNodes.MoneroNodesOption.CUSTOM) {
if (xmrNodes.getProvidedXmrNodes().isEmpty()) {
log.warn("Failed to fetch daemon info from custom node on startup, falling back to public nodes: " + e.getMessage());
preferences.setMoneroNodesOptionOrdinal(XmrNodes.MoneroNodesOption.PUBLIC.ordinal());
} else {
log.warn("Failed to fetch daemon info from custom node on startup, falling back to provided nodes: " + e.getMessage()); log.warn("Failed to fetch daemon info from custom node on startup, falling back to provided nodes: " + e.getMessage());
preferences.setMoneroNodesOptionOrdinal(XmrNodes.MoneroNodesOption.PROVIDED.ordinal()); preferences.setMoneroNodesOptionOrdinal(XmrNodes.MoneroNodesOption.PROVIDED.ordinal());
}
initializeConnections(); initializeConnections();
return; return;
} }
// log error message periodically // log error message periodically
if (lastLogPollErrorTimestamp == null || System.currentTimeMillis() - lastLogPollErrorTimestamp > HavenoUtils.LOG_POLL_ERROR_PERIOD_MS) { if (lastLogPollErrorTimestamp == null || System.currentTimeMillis() - lastLogPollErrorTimestamp > HavenoUtils.LOG_POLL_ERROR_PERIOD_MS) {
log.warn("Failed to fetch daemon info, trying to switch to best connection: " + e.getMessage()); log.warn("Failed to fetch daemon info, trying to switch to best connection, error={}", e.getMessage());
if (DevEnv.isDevMode()) e.printStackTrace(); if (DevEnv.isDevMode()) log.error(ExceptionUtils.getStackTrace(e));
lastLogPollErrorTimestamp = System.currentTimeMillis(); lastLogPollErrorTimestamp = System.currentTimeMillis();
} }
@ -734,6 +746,12 @@ public final class XmrConnectionService {
// connected to daemon // connected to daemon
isConnected = true; isConnected = true;
// determine if blockchain is syncing locally
boolean blockchainSyncing = lastInfo.getHeight().equals(lastInfo.getHeightWithoutBootstrap()) || (lastInfo.getTargetHeight().equals(0l) && lastInfo.getHeightWithoutBootstrap().equals(0l)); // blockchain is syncing if height equals height without bootstrap, or target height and height without bootstrap both equal 0
// write sync status to preferences
preferences.getXmrNodeSettings().setSyncBlockchain(blockchainSyncing);
// throttle warnings if daemon not synced // throttle warnings if daemon not synced
if (!isSyncedWithinTolerance() && System.currentTimeMillis() - lastLogDaemonNotSyncedTimestamp > HavenoUtils.LOG_DAEMON_NOT_SYNCED_WARN_PERIOD_MS) { if (!isSyncedWithinTolerance() && System.currentTimeMillis() - lastLogDaemonNotSyncedTimestamp > HavenoUtils.LOG_DAEMON_NOT_SYNCED_WARN_PERIOD_MS) {
log.warn("Our chain height: {} is out of sync with peer nodes chain height: {}", chainHeight.get(), getTargetHeight()); log.warn("Our chain height: {} is out of sync with peer nodes chain height: {}", chainHeight.get(), getTargetHeight());

View File

@ -150,16 +150,16 @@ public class XmrLocalNode {
/** /**
* Start a local Monero node from settings. * Start a local Monero node from settings.
*/ */
public void startMoneroNode() throws IOException { public void start() throws IOException {
var settings = preferences.getXmrNodeSettings(); var settings = preferences.getXmrNodeSettings();
this.startNode(settings); this.start(settings);
} }
/** /**
* Start local Monero node. Throws MoneroError if the node cannot be started. * Start local Monero node. Throws MoneroError if the node cannot be started.
* Persist the settings to preferences if the node started successfully. * Persist the settings to preferences if the node started successfully.
*/ */
public void startNode(XmrNodeSettings settings) throws IOException { public void start(XmrNodeSettings settings) throws IOException {
if (isDetected()) throw new IllegalStateException("Local Monero node already online"); if (isDetected()) throw new IllegalStateException("Local Monero node already online");
log.info("Starting local Monero node: " + settings); log.info("Starting local Monero node: " + settings);
@ -177,6 +177,11 @@ public class XmrLocalNode {
args.add("--bootstrap-daemon-address=" + bootstrapUrl); args.add("--bootstrap-daemon-address=" + bootstrapUrl);
} }
var syncBlockchain = settings.getSyncBlockchain();
if (syncBlockchain != null && !syncBlockchain) {
args.add("--no-sync");
}
var flags = settings.getStartupFlags(); var flags = settings.getStartupFlags();
if (flags != null) { if (flags != null) {
args.addAll(flags); args.addAll(flags);
@ -191,7 +196,7 @@ public class XmrLocalNode {
* Stop the current local Monero node if we own its process. * Stop the current local Monero node if we own its process.
* Does not remove the last XmrNodeSettings. * Does not remove the last XmrNodeSettings.
*/ */
public void stopNode() { public void stop() {
if (!isDetected()) throw new IllegalStateException("Local Monero node is not running"); if (!isDetected()) throw new IllegalStateException("Local Monero node is not running");
if (daemon.getProcess() == null || !daemon.getProcess().isAlive()) throw new IllegalStateException("Cannot stop local Monero node because we don't own its process"); // TODO (woodser): remove isAlive() check after monero-java 0.5.4 which nullifies internal process if (daemon.getProcess() == null || !daemon.getProcess().isAlive()) throw new IllegalStateException("Cannot stop local Monero node because we don't own its process"); // TODO (woodser): remove isAlive() check after monero-java 0.5.4 which nullifies internal process
daemon.stopProcess(); daemon.stopProcess();

View File

@ -98,7 +98,7 @@ public class XmrBalanceInfo implements Payload {
public String toString() { public String toString() {
return "XmrBalanceInfo{" + return "XmrBalanceInfo{" +
"balance=" + balance + "balance=" + balance +
"unlockedBalance=" + availableBalance + ", unlockedBalance=" + availableBalance +
", lockedBalance=" + pendingBalance + ", lockedBalance=" + pendingBalance +
", reservedOfferBalance=" + reservedOfferBalance + ", reservedOfferBalance=" + reservedOfferBalance +
", reservedTradeBalance=" + reservedTradeBalance + ", reservedTradeBalance=" + reservedTradeBalance +

View File

@ -82,6 +82,8 @@ import java.util.concurrent.atomic.AtomicInteger;
@Slf4j @Slf4j
public abstract class HavenoExecutable implements GracefulShutDownHandler, HavenoSetup.HavenoSetupListener, UncaughtExceptionHandler { public abstract class HavenoExecutable implements GracefulShutDownHandler, HavenoSetup.HavenoSetupListener, UncaughtExceptionHandler {
// TODO: regular expression is used to parse application name for the flatpak manifest, a more stable approach would be nice
// Don't edit the next line unless you're only editing in between the quotes.
public static final String DEFAULT_APP_NAME = "Haveno"; public static final String DEFAULT_APP_NAME = "Haveno";
public static final int EXIT_SUCCESS = 0; public static final int EXIT_SUCCESS = 0;
@ -124,7 +126,7 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
System.exit(EXIT_FAILURE); System.exit(EXIT_FAILURE);
} catch (Throwable ex) { } catch (Throwable ex) {
System.err.println("fault: An unexpected error occurred. " + System.err.println("fault: An unexpected error occurred. " +
"Please file a report at https://haveno.exchange/issues"); "Please file a report at https://github.com/haveno-dex/haveno/issues");
ex.printStackTrace(System.err); ex.printStackTrace(System.err);
System.exit(EXIT_FAILURE); System.exit(EXIT_FAILURE);
} }
@ -201,8 +203,7 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
startApplication(); startApplication();
} }
} catch (InterruptedException | ExecutionException e) { } catch (InterruptedException | ExecutionException e) {
log.error("An error occurred: {}", e.getMessage()); log.error("An error occurred: {}\n", e.getMessage(), e);
e.printStackTrace();
} }
}); });
} }
@ -362,7 +363,7 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
try { try {
ThreadUtils.awaitTasks(tasks, tasks.size(), 90000l); // run in parallel with timeout ThreadUtils.awaitTasks(tasks, tasks.size(), 90000l); // run in parallel with timeout
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); log.error("Failed to notify all services to prepare for shutdown: {}\n", e.getMessage(), e);
} }
injector.getInstance(TradeManager.class).shutDown(); injector.getInstance(TradeManager.class).shutDown();
@ -397,8 +398,7 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
}); });
}); });
} catch (Throwable t) { } catch (Throwable t) {
log.error("App shutdown failed with exception {}", t.toString()); log.error("App shutdown failed with exception: {}\n", t.getMessage(), t);
t.printStackTrace();
completeShutdown(resultHandler, EXIT_FAILURE, systemExit); completeShutdown(resultHandler, EXIT_FAILURE, systemExit);
} }
} }

View File

@ -53,6 +53,7 @@ import haveno.core.alert.Alert;
import haveno.core.alert.AlertManager; import haveno.core.alert.AlertManager;
import haveno.core.alert.PrivateNotificationManager; import haveno.core.alert.PrivateNotificationManager;
import haveno.core.alert.PrivateNotificationPayload; import haveno.core.alert.PrivateNotificationPayload;
import haveno.core.api.CoreContext;
import haveno.core.api.XmrConnectionService; import haveno.core.api.XmrConnectionService;
import haveno.core.api.XmrLocalNode; import haveno.core.api.XmrLocalNode;
import haveno.core.locale.Res; import haveno.core.locale.Res;
@ -131,7 +132,10 @@ public class HavenoSetup {
private final Preferences preferences; private final Preferences preferences;
private final User user; private final User user;
private final AlertManager alertManager; private final AlertManager alertManager;
@Getter
private final Config config; private final Config config;
@Getter
private final CoreContext coreContext;
private final AccountAgeWitnessService accountAgeWitnessService; private final AccountAgeWitnessService accountAgeWitnessService;
private final TorSetup torSetup; private final TorSetup torSetup;
private final CoinFormatter formatter; private final CoinFormatter formatter;
@ -228,6 +232,7 @@ public class HavenoSetup {
User user, User user,
AlertManager alertManager, AlertManager alertManager,
Config config, Config config,
CoreContext coreContext,
AccountAgeWitnessService accountAgeWitnessService, AccountAgeWitnessService accountAgeWitnessService,
TorSetup torSetup, TorSetup torSetup,
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter, @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter,
@ -253,6 +258,7 @@ public class HavenoSetup {
this.user = user; this.user = user;
this.alertManager = alertManager; this.alertManager = alertManager;
this.config = config; this.config = config;
this.coreContext = coreContext;
this.accountAgeWitnessService = accountAgeWitnessService; this.accountAgeWitnessService = accountAgeWitnessService;
this.torSetup = torSetup; this.torSetup = torSetup;
this.formatter = formatter; this.formatter = formatter;
@ -263,6 +269,7 @@ public class HavenoSetup {
this.arbitrationManager = arbitrationManager; this.arbitrationManager = arbitrationManager;
HavenoUtils.havenoSetup = this; HavenoUtils.havenoSetup = this;
HavenoUtils.preferences = preferences;
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -376,8 +383,7 @@ public class HavenoSetup {
moneroWalletRpcFile.setExecutable(true); moneroWalletRpcFile.setExecutable(true);
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); log.warn("Failed to install Monero binaries: {}\n", e.getMessage(), e);
log.warn("Failed to install Monero binaries: " + e.toString());
} }
} }

View File

@ -28,6 +28,9 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Paths; import java.nio.file.Paths;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.apache.commons.lang3.exception.ExceptionUtils;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
@ -48,8 +51,7 @@ public class TorSetup {
if (resultHandler != null) if (resultHandler != null)
resultHandler.run(); resultHandler.run();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); log.error(ExceptionUtils.getStackTrace(e));
log.error(e.toString());
if (errorMessageHandler != null) if (errorMessageHandler != null)
errorMessageHandler.handleErrorMessage(e.toString()); errorMessageHandler.handleErrorMessage(e.toString());
} }

View File

@ -123,7 +123,7 @@ public abstract class ExecutableForAppWithP2p extends HavenoExecutable {
try { try {
ThreadUtils.awaitTasks(tasks, tasks.size(), 120000l); // run in parallel with timeout ThreadUtils.awaitTasks(tasks, tasks.size(), 120000l); // run in parallel with timeout
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); log.error("Error awaiting tasks to complete: {}\n", e.getMessage(), e);
} }
JsonFileManager.shutDownAllInstances(); JsonFileManager.shutDownAllInstances();
@ -177,8 +177,7 @@ public abstract class ExecutableForAppWithP2p extends HavenoExecutable {
}, 1); }, 1);
} }
} catch (Throwable t) { } catch (Throwable t) {
log.debug("App shutdown failed with exception"); log.info("App shutdown failed with exception: {}\n", t.getMessage(), t);
t.printStackTrace();
PersistenceManager.flushAllDataToDiskAtShutdown(() -> { PersistenceManager.flushAllDataToDiskAtShutdown(() -> {
resultHandler.handleResult(); resultHandler.handleResult();
log.info("Graceful shutdown resulted in an error. Exiting now."); log.info("Graceful shutdown resulted in an error. Exiting now.");

View File

@ -541,6 +541,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
boolean useSavingsWallet, boolean useSavingsWallet,
long triggerPrice, long triggerPrice,
boolean reserveExactAmount, boolean reserveExactAmount,
boolean resetAddressEntriesOnError,
TransactionResultHandler resultHandler, TransactionResultHandler resultHandler,
ErrorMessageHandler errorMessageHandler) { ErrorMessageHandler errorMessageHandler) {
@ -559,7 +560,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
}, (errorMessage) -> { }, (errorMessage) -> {
if (!openOffer.isCanceled()) { if (!openOffer.isCanceled()) {
log.warn("Error processing pending offer {}: {}", openOffer.getId(), errorMessage); log.warn("Error processing pending offer {}: {}", openOffer.getId(), errorMessage);
doCancelOffer(openOffer); doCancelOffer(openOffer, resetAddressEntriesOnError);
} }
latch.countDown(); latch.countDown();
errorMessageHandler.handleErrorMessage(errorMessage); errorMessageHandler.handleErrorMessage(errorMessage);
@ -715,14 +716,18 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
} }
} }
private void doCancelOffer(OpenOffer openOffer) {
doCancelOffer(openOffer, true);
}
// remove open offer which thaws its key images // remove open offer which thaws its key images
private void doCancelOffer(@NotNull OpenOffer openOffer) { private void doCancelOffer(@NotNull OpenOffer openOffer, boolean resetAddressEntries) {
Offer offer = openOffer.getOffer(); Offer offer = openOffer.getOffer();
offer.setState(Offer.State.REMOVED); offer.setState(Offer.State.REMOVED);
openOffer.setState(OpenOffer.State.CANCELED); openOffer.setState(OpenOffer.State.CANCELED);
removeOpenOffer(openOffer); removeOpenOffer(openOffer);
closedTradableManager.add(openOffer); // TODO: don't add these to closed tradables? closedTradableManager.add(openOffer); // TODO: don't add these to closed tradables?
xmrWalletService.resetAddressEntriesForOpenOffer(offer.getId()); if (resetAddressEntries) xmrWalletService.resetAddressEntriesForOpenOffer(offer.getId());
requestPersistence(); requestPersistence();
xmrWalletService.thawOutputs(offer.getOfferPayload().getReserveTxKeyImages()); xmrWalletService.thawOutputs(offer.getOfferPayload().getReserveTxKeyImages());
} }
@ -929,6 +934,23 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
return; return;
} }
// cancel offer if scheduled txs unavailable
if (openOffer.getScheduledTxHashes() != null) {
boolean scheduledTxsAvailable = true;
for (MoneroTxWallet tx : xmrWalletService.getTxs(openOffer.getScheduledTxHashes())) {
if (!tx.isLocked() && !isOutputsAvailable(tx)) {
scheduledTxsAvailable = false;
break;
}
}
if (!scheduledTxsAvailable) {
log.warn("Canceling offer {} because scheduled txs are no longer available", openOffer.getId());
doCancelOffer(openOffer);
resultHandler.handleResult(null);
return;
}
}
// get amount needed to reserve offer // get amount needed to reserve offer
BigInteger amountNeeded = openOffer.getOffer().getAmountNeeded(); BigInteger amountNeeded = openOffer.getOffer().getAmountNeeded();
@ -977,7 +999,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
// handle result // handle result
resultHandler.handleResult(null); resultHandler.handleResult(null);
} catch (Exception e) { } catch (Exception e) {
if (!openOffer.isCanceled()) e.printStackTrace(); if (!openOffer.isCanceled()) log.error("Error processing pending offer: {}\n", e.getMessage(), e);
errorMessageHandler.handleErrorMessage(e.getMessage()); errorMessageHandler.handleErrorMessage(e.getMessage());
} }
}).start(); }).start();
@ -1133,25 +1155,31 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
throw new RuntimeException("Not enough money in Haveno wallet"); throw new RuntimeException("Not enough money in Haveno wallet");
} }
// get locked txs // get earliest available or pending txs with sufficient incoming amount
List<MoneroTxWallet> lockedTxs = xmrWalletService.getTxs(new MoneroTxQuery().setIsLocked(true));
// get earliest unscheduled txs with sufficient incoming amount
List<String> scheduledTxHashes = new ArrayList<String>();
BigInteger scheduledAmount = BigInteger.ZERO; BigInteger scheduledAmount = BigInteger.ZERO;
for (MoneroTxWallet lockedTx : lockedTxs) { Set<MoneroTxWallet> scheduledTxs = new HashSet<MoneroTxWallet>();
if (isTxScheduledByOtherOffer(openOffers, openOffer, lockedTx.getHash())) continue; for (MoneroTxWallet tx : xmrWalletService.getTxs()) {
if (lockedTx.getIncomingTransfers() == null || lockedTx.getIncomingTransfers().isEmpty()) continue;
scheduledTxHashes.add(lockedTx.getHash()); // skip if outputs unavailable
for (MoneroIncomingTransfer transfer : lockedTx.getIncomingTransfers()) { if (tx.getIncomingTransfers() == null || tx.getIncomingTransfers().isEmpty()) continue;
if (transfer.getAccountIndex() == 0) scheduledAmount = scheduledAmount.add(transfer.getAmount()); if (!isOutputsAvailable(tx)) continue;
if (isTxScheduledByOtherOffer(openOffers, openOffer, tx.getHash())) continue;
// add scheduled tx
for (MoneroIncomingTransfer transfer : tx.getIncomingTransfers()) {
if (transfer.getAccountIndex() == 0) {
scheduledAmount = scheduledAmount.add(transfer.getAmount());
scheduledTxs.add(tx);
} }
}
// break if sufficient funds
if (scheduledAmount.compareTo(offerReserveAmount) >= 0) break; if (scheduledAmount.compareTo(offerReserveAmount) >= 0) break;
} }
if (scheduledAmount.compareTo(offerReserveAmount) < 0) throw new RuntimeException("Not enough funds to schedule offer"); if (scheduledAmount.compareTo(offerReserveAmount) < 0) throw new RuntimeException("Not enough funds to schedule offer");
// schedule txs // schedule txs
openOffer.setScheduledTxHashes(scheduledTxHashes); openOffer.setScheduledTxHashes(scheduledTxs.stream().map(tx -> tx.getHash()).collect(Collectors.toList()));
openOffer.setScheduledAmount(scheduledAmount.toString()); openOffer.setScheduledAmount(scheduledAmount.toString());
openOffer.setState(OpenOffer.State.PENDING); openOffer.setState(OpenOffer.State.PENDING);
} }
@ -1187,6 +1215,14 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
return false; return false;
} }
private boolean isOutputsAvailable(MoneroTxWallet tx) {
if (tx.getOutputsWallet() == null) return false;
for (MoneroOutputWallet output : tx.getOutputsWallet()) {
if (output.isSpent() || output.isFrozen()) return false;
}
return true;
}
private void signAndPostOffer(OpenOffer openOffer, private void signAndPostOffer(OpenOffer openOffer,
boolean useSavingsWallet, // TODO: remove this? boolean useSavingsWallet, // TODO: remove this?
TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
@ -1365,9 +1401,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
}); });
result = true; result = true;
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace();
errorMessage = "Exception at handleSignOfferRequest " + e.getMessage(); errorMessage = "Exception at handleSignOfferRequest " + e.getMessage();
log.error(errorMessage); log.error(errorMessage + "\n", e);
} finally { } finally {
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), result, errorMessage); sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), result, errorMessage);
} }
@ -1519,8 +1554,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
result = true; result = true;
} catch (Throwable t) { } catch (Throwable t) {
errorMessage = "Exception at handleRequestIsOfferAvailableMessage " + t.getMessage(); errorMessage = "Exception at handleRequestIsOfferAvailableMessage " + t.getMessage();
log.error(errorMessage); log.error(errorMessage + "\n", t);
t.printStackTrace();
} finally { } finally {
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), result, errorMessage); sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), result, errorMessage);
} }

View File

@ -59,8 +59,7 @@ public class FeeProvider extends HttpClientProvider {
map.put(Config.BTC_TX_FEE, btcTxFee); map.put(Config.BTC_TX_FEE, btcTxFee);
map.put(Config.BTC_MIN_TX_FEE, btcMinTxFee); map.put(Config.BTC_MIN_TX_FEE, btcMinTxFee);
} catch (Throwable t) { } catch (Throwable t) {
log.error(t.toString()); log.error("Error getting fees: {}\n", t.getMessage(), t);
t.printStackTrace();
} }
return new Tuple2<>(tsMap, map); return new Tuple2<>(tsMap, map);
} }

View File

@ -68,8 +68,7 @@ public class PriceProvider extends HttpClientProvider {
long timestampSec = MathUtils.doubleToLong((Double) treeMap.get("timestampSec")); long timestampSec = MathUtils.doubleToLong((Double) treeMap.get("timestampSec"));
marketPriceMap.put(currencyCode, new MarketPrice(currencyCode, price, timestampSec, true)); marketPriceMap.put(currencyCode, new MarketPrice(currencyCode, price, timestampSec, true));
} catch (Throwable t) { } catch (Throwable t) {
log.error(t.toString()); log.error("Error getting all prices: {}\n", t.getMessage(), t);
t.printStackTrace();
} }
}); });

View File

@ -199,7 +199,7 @@ public abstract class SupportManager {
if (dispute.isClosed()) dispute.reOpen(); if (dispute.isClosed()) dispute.reOpen();
trade.advanceDisputeState(Trade.DisputeState.DISPUTE_OPENED); trade.advanceDisputeState(Trade.DisputeState.DISPUTE_OPENED);
} else if (dispute.isClosed()) { } else if (dispute.isClosed()) {
trade.pollWalletNormallyForMs(30000); // sync to check for payout trade.pollWalletNormallyForMs(60000); // sync to check for payout
} }
} }
} }

View File

@ -83,6 +83,9 @@ import monero.wallet.model.MoneroTxConfig;
import monero.wallet.model.MoneroTxWallet; import monero.wallet.model.MoneroTxWallet;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.apache.commons.lang3.exception.ExceptionUtils;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.KeyPair; import java.security.KeyPair;
import java.time.Instant; import java.time.Instant;
@ -523,7 +526,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
DisputeValidation.validateSenderNodeAddress(dispute, message.getSenderNodeAddress(), config); DisputeValidation.validateSenderNodeAddress(dispute, message.getSenderNodeAddress(), config);
//DisputeValidation.testIfDisputeTriesReplay(dispute, disputeList.getList()); //DisputeValidation.testIfDisputeTriesReplay(dispute, disputeList.getList());
} catch (DisputeValidation.ValidationException e) { } catch (DisputeValidation.ValidationException e) {
e.printStackTrace(); log.error(ExceptionUtils.getStackTrace(e));
validationExceptions.add(e); validationExceptions.add(e);
throw e; throw e;
} }
@ -532,9 +535,9 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
try { try {
DisputeValidation.validatePaymentAccountPayload(dispute); // TODO: add field to dispute details: valid, invalid, missing DisputeValidation.validatePaymentAccountPayload(dispute); // TODO: add field to dispute details: valid, invalid, missing
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); log.error(ExceptionUtils.getStackTrace(e));
log.warn(e.getMessage());
trade.prependErrorMessage(e.getMessage()); trade.prependErrorMessage(e.getMessage());
throw e;
} }
// get sender // get sender
@ -606,9 +609,8 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
} }
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); log.error(ExceptionUtils.getStackTrace(e));
errorMessage = e.getMessage(); errorMessage = e.getMessage();
log.warn(errorMessage);
if (trade != null) trade.setErrorMessage(errorMessage); if (trade != null) trade.setErrorMessage(errorMessage);
} }
@ -852,7 +854,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
// the state, as that is displayed to the user and we only persist that msg // the state, as that is displayed to the user and we only persist that msg
disputeResult.getChatMessage().setArrived(true); disputeResult.getChatMessage().setArrived(true);
trade.advanceDisputeState(Trade.DisputeState.ARBITRATOR_SAW_ARRIVED_DISPUTE_CLOSED_MSG); trade.advanceDisputeState(Trade.DisputeState.ARBITRATOR_SAW_ARRIVED_DISPUTE_CLOSED_MSG);
trade.pollWalletNormallyForMs(30000); trade.pollWalletNormallyForMs(60000);
requestPersistence(trade); requestPersistence(trade);
resultHandler.handleResult(); resultHandler.handleResult();
} }

View File

@ -71,7 +71,7 @@ public class DisputeSummaryVerification {
disputeAgent = arbitratorManager.getDisputeAgentByNodeAddress(nodeAddress).orElse(null); disputeAgent = arbitratorManager.getDisputeAgentByNodeAddress(nodeAddress).orElse(null);
checkNotNull(disputeAgent, "Dispute agent is null"); checkNotNull(disputeAgent, "Dispute agent is null");
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace(); log.error("Error verifying signature: {}\n", e.getMessage(), e);
throw new IllegalArgumentException(Res.get("support.sigCheck.popup.invalidFormat")); throw new IllegalArgumentException(Res.get("support.sigCheck.popup.invalidFormat"));
} }
@ -93,7 +93,7 @@ public class DisputeSummaryVerification {
throw new IllegalArgumentException(Res.get("support.sigCheck.popup.failed")); throw new IllegalArgumentException(Res.get("support.sigCheck.popup.failed"));
} }
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace(); log.error("Error verifying signature with agent pub key ring: {}\n", e.getMessage(), e);
throw new IllegalArgumentException(Res.get("support.sigCheck.popup.invalidFormat")); throw new IllegalArgumentException(Res.get("support.sigCheck.popup.invalidFormat"));
} }
} }

View File

@ -94,6 +94,8 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import org.apache.commons.lang3.exception.ExceptionUtils;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j @Slf4j
@ -355,11 +357,11 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
requestPersistence(trade); requestPersistence(trade);
} catch (Exception e) { } catch (Exception e) {
log.warn("Error processing dispute closed message: {}", e.getMessage()); log.warn("Error processing dispute closed message: {}", e.getMessage());
e.printStackTrace(); log.warn(ExceptionUtils.getStackTrace(e));
requestPersistence(trade); requestPersistence(trade);
// nack bad message and do not reprocess // nack bad message and do not reprocess
if (e instanceof IllegalArgumentException || e instanceof IllegalStateException) { if (HavenoUtils.isIllegal(e)) {
trade.getArbitrator().setDisputeClosedMessage(null); // message is processed trade.getArbitrator().setDisputeClosedMessage(null); // message is processed
trade.setDisputeState(Trade.DisputeState.DISPUTE_CLOSED); trade.setDisputeState(Trade.DisputeState.DISPUTE_CLOSED);
String warningMsg = "Error processing dispute closed message: " + e.getMessage() + "\n\nOpen another dispute to try again (ctrl+o)."; String warningMsg = "Error processing dispute closed message: " + e.getMessage() + "\n\nOpen another dispute to try again (ctrl+o).";
@ -489,8 +491,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
try { try {
feeEstimateTx = createDisputePayoutTx(trade, dispute.getContract(), disputeResult, false); feeEstimateTx = createDisputePayoutTx(trade, dispute.getContract(), disputeResult, false);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); log.warn("Could not recreate dispute payout tx to verify fee: {}\n", e.getMessage(), e);
log.warn("Could not recreate dispute payout tx to verify fee: " + e.getMessage());
} }
if (feeEstimateTx != null) { if (feeEstimateTx != null) {
BigInteger feeEstimate = feeEstimateTx.getFee(); BigInteger feeEstimate = feeEstimateTx.getFee();

View File

@ -27,7 +27,9 @@ import haveno.common.crypto.Hash;
import haveno.common.crypto.KeyRing; import haveno.common.crypto.KeyRing;
import haveno.common.crypto.PubKeyRing; import haveno.common.crypto.PubKeyRing;
import haveno.common.crypto.Sig; import haveno.common.crypto.Sig;
import haveno.common.file.FileUtil;
import haveno.common.util.Utilities; import haveno.common.util.Utilities;
import haveno.core.api.CoreNotificationService;
import haveno.core.api.XmrConnectionService; import haveno.core.api.XmrConnectionService;
import haveno.core.app.HavenoSetup; import haveno.core.app.HavenoSetup;
import haveno.core.offer.OfferPayload; import haveno.core.offer.OfferPayload;
@ -36,9 +38,12 @@ import haveno.core.support.dispute.arbitration.ArbitrationManager;
import haveno.core.support.dispute.arbitration.arbitrator.Arbitrator; import haveno.core.support.dispute.arbitration.arbitrator.Arbitrator;
import haveno.core.trade.messages.PaymentReceivedMessage; import haveno.core.trade.messages.PaymentReceivedMessage;
import haveno.core.trade.messages.PaymentSentMessage; import haveno.core.trade.messages.PaymentSentMessage;
import haveno.core.user.Preferences;
import haveno.core.util.JsonUtil; import haveno.core.util.JsonUtil;
import haveno.core.xmr.wallet.XmrWalletService; import haveno.core.xmr.wallet.XmrWalletService;
import haveno.network.p2p.NodeAddress; import haveno.network.p2p.NodeAddress;
import java.io.File;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.BigInteger; import java.math.BigInteger;
import java.net.InetAddress; import java.net.InetAddress;
@ -53,6 +58,13 @@ import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.SourceDataLine;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import monero.common.MoneroRpcConnection; import monero.common.MoneroRpcConnection;
import monero.daemon.model.MoneroOutput; import monero.daemon.model.MoneroOutput;
@ -110,11 +122,18 @@ public class HavenoUtils {
public static XmrWalletService xmrWalletService; public static XmrWalletService xmrWalletService;
public static XmrConnectionService xmrConnectionService; public static XmrConnectionService xmrConnectionService;
public static OpenOfferManager openOfferManager; public static OpenOfferManager openOfferManager;
public static CoreNotificationService notificationService;
public static Preferences preferences;
public static boolean isSeedNode() { public static boolean isSeedNode() {
return havenoSetup == null; return havenoSetup == null;
} }
public static boolean isDaemon() {
if (isSeedNode()) return true;
return havenoSetup.getCoreContext().isApiUser();
}
@SuppressWarnings("unused") @SuppressWarnings("unused")
public static Date getReleaseDate() { public static Date getReleaseDate() {
if (RELEASE_DATE == null) return null; if (RELEASE_DATE == null) return null;
@ -510,19 +529,83 @@ public class HavenoUtils {
havenoSetup.getTopErrorMsg().set(msg); havenoSetup.getTopErrorMsg().set(msg);
} }
public static boolean isConnectionRefused(Exception e) { public static boolean isConnectionRefused(Throwable e) {
return e != null && e.getMessage().contains("Connection refused"); return e != null && e.getMessage().contains("Connection refused");
} }
public static boolean isReadTimeout(Exception e) { public static boolean isReadTimeout(Throwable e) {
return e != null && e.getMessage().contains("Read timed out"); return e != null && e.getMessage().contains("Read timed out");
} }
public static boolean isUnresponsive(Exception e) { public static boolean isUnresponsive(Throwable e) {
return isConnectionRefused(e) || isReadTimeout(e); return isConnectionRefused(e) || isReadTimeout(e);
} }
public static boolean isNotEnoughSigners(Exception e) { public static boolean isNotEnoughSigners(Throwable e) {
return e != null && e.getMessage().contains("Not enough signers"); return e != null && e.getMessage().contains("Not enough signers");
} }
public static boolean isTransactionRejected(Throwable e) {
return e != null && e.getMessage().contains("was rejected");
}
public static boolean isIllegal(Throwable e) {
return e instanceof IllegalArgumentException || e instanceof IllegalStateException;
}
public static void playChimeSound() {
playAudioFile("chime.wav");
}
public static void playCashRegisterSound() {
playAudioFile("cash_register.wav");
}
private static void playAudioFile(String fileName) {
if (isDaemon()) return; // ignore if running as daemon
if (!preferences.getUseSoundForNotificationsProperty().get()) return; // ignore if sounds disabled
new Thread(() -> {
try {
// get audio file
File wavFile = new File(havenoSetup.getConfig().appDataDir, fileName);
if (!wavFile.exists()) FileUtil.resourceToFile(fileName, wavFile);
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(wavFile);
// get original format
AudioFormat baseFormat = audioInputStream.getFormat();
// set target format: PCM_SIGNED, 16-bit
AudioFormat targetFormat = new AudioFormat(
AudioFormat.Encoding.PCM_SIGNED,
baseFormat.getSampleRate(),
16, // 16-bit instead of 32-bit float
baseFormat.getChannels(),
baseFormat.getChannels() * 2, // Frame size: 2 bytes per channel (16-bit)
baseFormat.getSampleRate(),
false // Little-endian
);
// convert audio to target format
AudioInputStream convertedStream = AudioSystem.getAudioInputStream(targetFormat, audioInputStream);
// play audio
DataLine.Info info = new DataLine.Info(SourceDataLine.class, targetFormat);
SourceDataLine sourceLine = (SourceDataLine) AudioSystem.getLine(info);
sourceLine.open(targetFormat);
sourceLine.start();
byte[] buffer = new byte[1024];
int bytesRead = 0;
while ((bytesRead = convertedStream.read(buffer, 0, buffer.length)) != -1) {
sourceLine.write(buffer, 0, bytesRead);
}
sourceLine.drain();
sourceLine.close();
convertedStream.close();
audioInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
} }

View File

@ -649,6 +649,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
ThreadUtils.submitToPool(() -> { ThreadUtils.submitToPool(() -> {
if (newValue == Trade.Phase.DEPOSIT_REQUESTED) startPolling(); if (newValue == Trade.Phase.DEPOSIT_REQUESTED) startPolling();
if (newValue == Trade.Phase.DEPOSITS_PUBLISHED) onDepositsPublished(); if (newValue == Trade.Phase.DEPOSITS_PUBLISHED) onDepositsPublished();
if (newValue == Trade.Phase.PAYMENT_SENT) onPaymentSent();
if (isDepositsPublished() && !isPayoutUnlocked()) updatePollPeriod(); if (isDepositsPublished() && !isPayoutUnlocked()) updatePollPeriod();
if (isPaymentReceived()) { if (isPaymentReceived()) {
UserThread.execute(() -> { UserThread.execute(() -> {
@ -999,8 +1000,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
xmrWalletService.deleteWallet(getWalletName()); xmrWalletService.deleteWallet(getWalletName());
xmrWalletService.deleteWalletBackups(getWalletName()); xmrWalletService.deleteWalletBackups(getWalletName());
} catch (Exception e) { } catch (Exception e) {
log.warn(e.getMessage()); log.warn("Error deleting wallet for {} {}: {}\n", getClass().getSimpleName(), getId(), e.getMessage(), e);
e.printStackTrace();
setErrorMessage(e.getMessage()); setErrorMessage(e.getMessage());
processModel.getTradeManager().getNotificationService().sendErrorNotification("Error", e.getMessage()); processModel.getTradeManager().getNotificationService().sendErrorNotification("Error", e.getMessage());
} }
@ -1051,7 +1051,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
synchronized (HavenoUtils.getWalletFunctionLock()) { synchronized (HavenoUtils.getWalletFunctionLock()) {
MoneroTxWallet tx = wallet.createTx(txConfig); MoneroTxWallet tx = wallet.createTx(txConfig);
exportMultisigHex(); exportMultisigHex();
requestSaveWallet(); saveWallet();
return tx; return tx;
} }
} }
@ -1152,14 +1152,14 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
throw e; throw e;
} }
} }
requestSaveWallet(); saveWallet();
} }
log.info("Done importing multisig hexes for {} {} in {} ms, count={}", getClass().getSimpleName(), getShortId(), System.currentTimeMillis() - startTime, multisigHexes.size()); log.info("Done importing multisig hexes for {} {} in {} ms, count={}", getClass().getSimpleName(), getShortId(), System.currentTimeMillis() - startTime, multisigHexes.size());
} }
private void handleWalletError(Exception e, MoneroRpcConnection sourceConnection) { private void handleWalletError(Exception e, MoneroRpcConnection sourceConnection) {
if (HavenoUtils.isUnresponsive(e)) forceCloseWallet(); // wallet can be stuck a while if (HavenoUtils.isUnresponsive(e)) forceCloseWallet(); // wallet can be stuck a while
if (xmrConnectionService.isConnected()) requestSwitchToNextBestConnection(sourceConnection); if (!HavenoUtils.isIllegal(e) && xmrConnectionService.isConnected()) requestSwitchToNextBestConnection(sourceConnection);
getWallet(); // re-open wallet getWallet(); // re-open wallet
} }
@ -1279,7 +1279,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
} catch (IllegalArgumentException | IllegalStateException e) { } catch (IllegalArgumentException | IllegalStateException e) {
throw e; throw e;
} catch (Exception e) { } catch (Exception e) {
log.warn("Failed to process payout tx, tradeId={}, attempt={}/{}, error={}", getShortId(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage()); log.warn("Failed to process payout tx, tradeId={}, attempt={}/{}, error={}", getShortId(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage(), e);
handleWalletError(e, sourceConnection); handleWalletError(e, sourceConnection);
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e; if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
@ -1351,20 +1351,20 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
try { try {
MoneroMultisigSignResult result = wallet.signMultisigTxHex(payoutTxHex); MoneroMultisigSignResult result = wallet.signMultisigTxHex(payoutTxHex);
if (result.getSignedMultisigTxHex() == null) throw new IllegalArgumentException("Error signing payout tx, signed multisig hex is null"); if (result.getSignedMultisigTxHex() == null) throw new IllegalArgumentException("Error signing payout tx, signed multisig hex is null");
payoutTxHex = result.getSignedMultisigTxHex(); setPayoutTxHex(result.getSignedMultisigTxHex());
setPayoutTxHex(payoutTxHex);
} catch (Exception e) { } catch (Exception e) {
throw new IllegalStateException(e); throw new IllegalStateException(e);
} }
// describe result // describe result
describedTxSet = wallet.describeMultisigTxSet(payoutTxHex); describedTxSet = wallet.describeMultisigTxSet(getPayoutTxHex());
payoutTx = describedTxSet.getTxs().get(0); payoutTx = describedTxSet.getTxs().get(0);
updatePayout(payoutTx); updatePayout(payoutTx);
// verify fee is within tolerance by recreating payout tx // 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? // 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()); log.info("Creating fee estimate tx for {} {}", getClass().getSimpleName(), getId());
saveWallet(); // save wallet before creating fee estimate tx
MoneroTxWallet feeEstimateTx = createPayoutTx(); MoneroTxWallet feeEstimateTx = createPayoutTx();
BigInteger feeEstimate = feeEstimateTx.getFee(); BigInteger feeEstimate = feeEstimateTx.getFee();
double feeDiff = payoutTx.getFee().subtract(feeEstimate).abs().doubleValue() / feeEstimate.doubleValue(); // TODO: use BigDecimal? double feeDiff = payoutTx.getFee().subtract(feeEstimate).abs().doubleValue() / feeEstimate.doubleValue(); // TODO: use BigDecimal?
@ -1373,17 +1373,20 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
} }
// save trade state // save trade state
saveWallet();
requestPersistence(); requestPersistence();
// submit payout tx // submit payout tx
if (publish) { boolean doPublish = publish && !isPayoutPublished();
if (doPublish) {
try { try {
wallet.submitMultisigTxHex(payoutTxHex); wallet.submitMultisigTxHex(getPayoutTxHex());
setPayoutStatePublished(); setPayoutStatePublished();
} catch (Exception e) { } catch (Exception e) {
if (isPayoutPublished()) throw new IllegalStateException("Payout tx already published for " + getClass().getSimpleName() + " " + getShortId()); if (!isPayoutPublished()) {
if (HavenoUtils.isNotEnoughSigners(e)) throw new IllegalArgumentException(e); if (HavenoUtils.isTransactionRejected(e) || HavenoUtils.isNotEnoughSigners(e)) throw new IllegalArgumentException(e);
throw new RuntimeException("Failed to submit payout tx for " + getClass().getSimpleName() + " " + getId(), e); throw new RuntimeException("Failed to submit payout tx for " + getClass().getSimpleName() + " " + getId() + ", error=" + e.getMessage(), e);
}
} }
} }
} }
@ -1536,8 +1539,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
try { try {
ThreadUtils.awaitTask(shutDownTask, SHUTDOWN_TIMEOUT_MS); ThreadUtils.awaitTask(shutDownTask, SHUTDOWN_TIMEOUT_MS);
} catch (Exception e) { } catch (Exception e) {
log.warn("Error shutting down {} {}: {}", getClass().getSimpleName(), getId(), e.getMessage()); log.warn("Error shutting down {} {}: {}\n", getClass().getSimpleName(), getId(), e.getMessage(), e);
e.printStackTrace();
// force close wallet // force close wallet
forceCloseWallet(); forceCloseWallet();
@ -2817,8 +2819,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
processing = false; processing = false;
if (!isInitialized || isShutDownStarted) return; if (!isInitialized || isShutDownStarted) return;
if (isWalletConnectedToDaemon()) { if (isWalletConnectedToDaemon()) {
e.printStackTrace(); log.warn("Error polling idle trade for {} {}: {}. Monerod={}\n", getClass().getSimpleName(), getId(), e.getMessage(), getXmrWalletService().getXmrConnectionService().getConnection(), e);
log.warn("Error polling idle trade for {} {}: {}. Monerod={}", getClass().getSimpleName(), getId(), e.getMessage(), getXmrWalletService().getXmrConnectionService().getConnection());
}; };
} }
}, getId()); }, getId());
@ -2833,6 +2834,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
// close open offer or reset address entries // close open offer or reset address entries
if (this instanceof MakerTrade) { if (this instanceof MakerTrade) {
processModel.getOpenOfferManager().closeOpenOffer(getOffer()); processModel.getOpenOfferManager().closeOpenOffer(getOffer());
HavenoUtils.notificationService.sendTradeNotification(this, Phase.DEPOSITS_PUBLISHED, "Offer Taken", "Your offer " + offer.getId() + " has been accepted"); // TODO (woodser): use language translation
} else { } else {
getXmrWalletService().resetAddressEntriesForOpenOffer(getId()); getXmrWalletService().resetAddressEntriesForOpenOffer(getId());
} }
@ -2841,6 +2843,12 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
ThreadUtils.submitToPool(() -> xmrWalletService.freezeOutputs(getSelf().getReserveTxKeyImages())); ThreadUtils.submitToPool(() -> xmrWalletService.freezeOutputs(getSelf().getReserveTxKeyImages()));
} }
private void onPaymentSent() {
if (this instanceof SellerTrade) {
HavenoUtils.notificationService.sendTradeNotification(this, Phase.PAYMENT_SENT, "Payment Sent", "The buyer has sent the payment"); // TODO (woodser): use language translation
}
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER // PROTO BUFFER
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////

View File

@ -68,7 +68,6 @@ import haveno.core.support.dispute.mediation.mediator.MediatorManager;
import haveno.core.support.dispute.messages.DisputeClosedMessage; import haveno.core.support.dispute.messages.DisputeClosedMessage;
import haveno.core.support.dispute.messages.DisputeOpenedMessage; import haveno.core.support.dispute.messages.DisputeOpenedMessage;
import haveno.core.trade.Trade.DisputeState; import haveno.core.trade.Trade.DisputeState;
import haveno.core.trade.Trade.Phase;
import haveno.core.trade.failed.FailedTradesManager; import haveno.core.trade.failed.FailedTradesManager;
import haveno.core.trade.handlers.TradeResultHandler; import haveno.core.trade.handlers.TradeResultHandler;
import haveno.core.trade.messages.DepositRequest; import haveno.core.trade.messages.DepositRequest;
@ -134,7 +133,6 @@ import lombok.Setter;
import monero.daemon.model.MoneroTx; import monero.daemon.model.MoneroTx;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import org.bouncycastle.crypto.params.KeyParameter; import org.bouncycastle.crypto.params.KeyParameter;
import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -258,7 +256,9 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
failedTradesManager.setUnFailTradeCallback(this::unFailTrade); failedTradesManager.setUnFailTradeCallback(this::unFailTrade);
xmrWalletService.setTradeManager(this); // TODO: better way to set references
xmrWalletService.setTradeManager(this); // TODO: set reference in HavenoUtils for consistency
HavenoUtils.notificationService = notificationService;
} }
@ -366,8 +366,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
trade.onShutDownStarted(); trade.onShutDownStarted();
} catch (Exception e) { } catch (Exception e) {
if (e.getMessage() != null && e.getMessage().contains("Connection reset")) return; // expected if shut down with ctrl+c if (e.getMessage() != null && e.getMessage().contains("Connection reset")) return; // expected if shut down with ctrl+c
log.warn("Error notifying {} {} that shut down started {}", trade.getClass().getSimpleName(), trade.getId()); log.warn("Error notifying {} {} that shut down started: {}\n", trade.getClass().getSimpleName(), trade.getId(), e.getMessage(), e);
e.printStackTrace();
} }
}); });
try { try {
@ -396,15 +395,13 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
trade.shutDown(); trade.shutDown();
} catch (Exception e) { } catch (Exception e) {
if (e.getMessage() != null && (e.getMessage().contains("Connection reset") || e.getMessage().contains("Connection refused"))) return; // expected if shut down with ctrl+c if (e.getMessage() != null && (e.getMessage().contains("Connection reset") || e.getMessage().contains("Connection refused"))) return; // expected if shut down with ctrl+c
log.warn("Error closing {} {}", trade.getClass().getSimpleName(), trade.getId()); log.warn("Error closing {} {}: {}", trade.getClass().getSimpleName(), trade.getId(), e.getMessage(), e);
e.printStackTrace();
} }
}); });
try { try {
ThreadUtils.awaitTasks(tasks); ThreadUtils.awaitTasks(tasks);
} catch (Exception e) { } catch (Exception e) {
log.warn("Error shutting down trades: {}", e.getMessage()); log.warn("Error shutting down trades: {}\n", e.getMessage(), e);
e.printStackTrace();
} }
} }
@ -462,8 +459,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
} }
} catch (Exception e) { } catch (Exception e) {
if (!isShutDownStarted) { if (!isShutDownStarted) {
e.printStackTrace(); log.warn("Error initializing {} {}: {}\n", trade.getClass().getSimpleName(), trade.getId(), e.getMessage(), e);
log.warn("Error initializing {} {}: {}", trade.getClass().getSimpleName(), trade.getId(), e.getMessage());
trade.setInitError(e); trade.setInitError(e);
} }
} }
@ -603,14 +599,6 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
initTradeAndProtocol(trade, createTradeProtocol(trade)); initTradeAndProtocol(trade, createTradeProtocol(trade));
addTrade(trade); addTrade(trade);
// notify on phase changes
// TODO (woodser): save subscription, bind on startup
EasyBind.subscribe(trade.statePhaseProperty(), phase -> {
if (phase == Phase.DEPOSITS_PUBLISHED) {
notificationService.sendTradeNotification(trade, "Offer Taken", "Your offer " + offer.getId() + " has been accepted"); // TODO (woodser): use language translation
}
});
// process with protocol // process with protocol
((MakerProtocol) getTradeProtocol(trade)).handleInitTradeRequest(request, sender, errorMessage -> { ((MakerProtocol) getTradeProtocol(trade)).handleInitTradeRequest(request, sender, errorMessage -> {
log.warn("Maker error during trade initialization: " + errorMessage); log.warn("Maker error during trade initialization: " + errorMessage);

View File

@ -68,7 +68,7 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
complete(); complete();
} catch (Throwable t) { } catch (Throwable t) {
this.error = t; this.error = t;
t.printStackTrace(); log.error("Error processing deposit request for trade {}: {}\n", trade.getId(), t.getMessage(), t);
trade.setStateIfValidTransitionTo(Trade.State.PUBLISH_DEPOSIT_TX_REQUEST_FAILED); trade.setStateIfValidTransitionTo(Trade.State.PUBLISH_DEPOSIT_TX_REQUEST_FAILED);
failed(t); failed(t);
} }
@ -155,15 +155,14 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
log.info("Arbitrator published deposit txs for trade " + trade.getId()); log.info("Arbitrator published deposit txs for trade " + trade.getId());
trade.setStateIfValidTransitionTo(Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS); trade.setStateIfValidTransitionTo(Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS);
} catch (Exception e) { } catch (Exception e) {
log.warn("Arbitrator error publishing deposit txs for trade {} {}: {}", trade.getClass().getSimpleName(), trade.getShortId(), e.getMessage()); log.warn("Arbitrator error publishing deposit txs for trade {} {}: {}\n", trade.getClass().getSimpleName(), trade.getShortId(), e.getMessage(), e);
e.printStackTrace();
if (!depositTxsRelayed) { if (!depositTxsRelayed) {
// flush txs from pool // flush txs from pool
try { try {
daemon.flushTxPool(processModel.getMaker().getDepositTxHash(), processModel.getTaker().getDepositTxHash()); daemon.flushTxPool(processModel.getMaker().getDepositTxHash(), processModel.getTaker().getDepositTxHash());
} catch (Exception e2) { } catch (Exception e2) {
e2.printStackTrace(); log.warn("Error flushing deposit txs from pool for trade {}: {}\n", trade.getId(), e2.getMessage(), e2);
} }
} }
throw e; throw e;

View File

@ -29,6 +29,8 @@ import monero.daemon.model.MoneroTx;
import java.math.BigInteger; import java.math.BigInteger;
import org.apache.commons.lang3.exception.ExceptionUtils;
/** /**
* Arbitrator verifies reserve tx from maker or taker. * Arbitrator verifies reserve tx from maker or taker.
* *
@ -73,7 +75,7 @@ public class ArbitratorProcessReserveTx extends TradeTask {
request.getReserveTxKey(), request.getReserveTxKey(),
null); null);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); log.error(ExceptionUtils.getStackTrace(e));
throw new RuntimeException("Error processing reserve tx from " + (isFromMaker ? "maker " : "taker ") + processModel.getTempTradePeerNodeAddress() + ", offerId=" + offer.getId() + ": " + e.getMessage()); throw new RuntimeException("Error processing reserve tx from " + (isFromMaker ? "maker " : "taker ") + processModel.getTempTradePeerNodeAddress() + ", offerId=" + offer.getId() + ": " + e.getMessage());
} }

View File

@ -62,7 +62,7 @@ public class MaybeResendDisputeClosedMessageWithPayout extends TradeTask {
HavenoUtils.arbitrationManager.closeDisputeTicket(dispute.getDisputeResultProperty().get(), dispute, dispute.getDisputeResultProperty().get().summaryNotesProperty().get(), () -> { HavenoUtils.arbitrationManager.closeDisputeTicket(dispute.getDisputeResultProperty().get(), dispute, dispute.getDisputeResultProperty().get().summaryNotesProperty().get(), () -> {
completeAux(); completeAux();
}, (errMessage, err) -> { }, (errMessage, err) -> {
err.printStackTrace(); log.error("Failed to close dispute ticket for trade {}: {}\n", trade.getId(), errMessage, err);
failed(err); failed(err);
}); });
ticketClosed = true; ticketClosed = true;

View File

@ -70,7 +70,7 @@ public class ProcessDepositsConfirmedMessage extends TradeTask {
try { try {
trade.importMultisigHex(); trade.importMultisigHex();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); log.warn("Error importing multisig hex on deposits confirmed for trade " + trade.getId() + ": " + e.getMessage() + "\n", e);
} }
}); });
} }

View File

@ -32,6 +32,7 @@ import haveno.network.p2p.NodeAddress;
import haveno.network.p2p.SendDirectMessageListener; import haveno.network.p2p.SendDirectMessageListener;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import monero.wallet.MoneroWallet; import monero.wallet.MoneroWallet;
import monero.wallet.model.MoneroMultisigInfo;
import monero.wallet.model.MoneroMultisigInitResult; import monero.wallet.model.MoneroMultisigInitResult;
import java.util.Arrays; import java.util.Arrays;
@ -118,8 +119,17 @@ public class ProcessInitMultisigRequest extends TradeTask {
if (processModel.getMultisigAddress() == null && peers[0].getExchangedMultisigHex() != null && peers[1].getExchangedMultisigHex() != null) { if (processModel.getMultisigAddress() == null && peers[0].getExchangedMultisigHex() != null && peers[1].getExchangedMultisigHex() != null) {
log.info("Importing exchanged multisig hex for trade {}", trade.getId()); log.info("Importing exchanged multisig hex for trade {}", trade.getId());
MoneroMultisigInitResult result = multisigWallet.exchangeMultisigKeys(Arrays.asList(peers[0].getExchangedMultisigHex(), peers[1].getExchangedMultisigHex()), xmrWalletService.getWalletPassword()); MoneroMultisigInitResult result = multisigWallet.exchangeMultisigKeys(Arrays.asList(peers[0].getExchangedMultisigHex(), peers[1].getExchangedMultisigHex()), xmrWalletService.getWalletPassword());
// check multisig state
MoneroMultisigInfo multisigInfo = multisigWallet.getMultisigInfo();
if (!multisigInfo.isMultisig()) throw new RuntimeException("Multisig wallet is not multisig on completion");
if (!multisigInfo.isReady()) throw new RuntimeException("Multisig wallet is not ready on completion");
if (multisigInfo.getThreshold() != 2) throw new RuntimeException("Multisig wallet has unexpected threshold: " + multisigInfo.getThreshold());
if (multisigInfo.getNumParticipants() != 3) throw new RuntimeException("Multisig wallet has unexpected number of participants: " + multisigInfo.getNumParticipants());
// set final address and save
processModel.setMultisigAddress(result.getAddress()); processModel.setMultisigAddress(result.getAddress());
new Thread(() -> trade.saveWallet()).start(); // save multisig wallet off thread on completion trade.saveWallet();
trade.setStateIfValidTransitionTo(Trade.State.MULTISIG_COMPLETED); trade.setStateIfValidTransitionTo(Trade.State.MULTISIG_COMPLETED);
} }

View File

@ -120,7 +120,7 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
} catch (Throwable t) { } catch (Throwable t) {
// do not reprocess illegal argument // do not reprocess illegal argument
if (t instanceof IllegalArgumentException) { if (HavenoUtils.isIllegal(t)) {
trade.getSeller().setPaymentReceivedMessage(null); // do not reprocess trade.getSeller().setPaymentReceivedMessage(null); // do not reprocess
trade.requestPersistence(); trade.requestPersistence();
} }
@ -151,6 +151,7 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
boolean deferSignAndPublish = trade instanceof ArbitratorTrade && !isSigned && message.isDeferPublishPayout(); boolean deferSignAndPublish = trade instanceof ArbitratorTrade && !isSigned && message.isDeferPublishPayout();
if (deferSignAndPublish) { if (deferSignAndPublish) {
log.info("Deferring signing and publishing payout tx for {} {}", trade.getClass().getSimpleName(), trade.getId()); log.info("Deferring signing and publishing payout tx for {} {}", trade.getClass().getSimpleName(), trade.getId());
trade.pollWalletNormallyForMs(60000);
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
if (trade.isPayoutPublished()) break; if (trade.isPayoutPublished()) break;
HavenoUtils.waitFor(Trade.DEFER_PUBLISH_MS / 5); HavenoUtils.waitFor(Trade.DEFER_PUBLISH_MS / 5);
@ -160,11 +161,11 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
// verify and publish payout tx // verify and publish payout tx
if (!trade.isPayoutPublished()) { if (!trade.isPayoutPublished()) {
try {
if (isSigned) { if (isSigned) {
log.info("{} {} publishing signed payout tx from seller", trade.getClass().getSimpleName(), trade.getId()); log.info("{} {} publishing signed payout tx from seller", trade.getClass().getSimpleName(), trade.getId());
trade.processPayoutTx(message.getSignedPayoutTxHex(), false, true); trade.processPayoutTx(message.getSignedPayoutTxHex(), false, true);
} else { } else {
try {
PaymentSentMessage paymentSentMessage = (trade.isArbitrator() ? trade.getBuyer() : trade.getArbitrator()).getPaymentSentMessage(); PaymentSentMessage paymentSentMessage = (trade.isArbitrator() ? trade.getBuyer() : trade.getArbitrator()).getPaymentSentMessage();
if (paymentSentMessage == null) throw new RuntimeException("Process model does not have payment sent message for " + trade.getClass().getSimpleName() + " " + trade.getId()); if (paymentSentMessage == null) throw new RuntimeException("Process model does not have payment sent message for " + trade.getClass().getSimpleName() + " " + trade.getId());
if (trade.getPayoutTxHex() == null) { // unsigned if (trade.getPayoutTxHex() == null) { // unsigned
@ -174,6 +175,7 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
log.info("{} {} re-verifying and publishing signed payout tx", trade.getClass().getSimpleName(), trade.getId()); log.info("{} {} re-verifying and publishing signed payout tx", trade.getClass().getSimpleName(), trade.getId());
trade.processPayoutTx(trade.getPayoutTxHex(), false, true); trade.processPayoutTx(trade.getPayoutTxHex(), false, true);
} }
}
} catch (Exception e) { } catch (Exception e) {
HavenoUtils.waitFor(trade.getXmrConnectionService().getRefreshPeriodMs()); // wait to see published tx HavenoUtils.waitFor(trade.getXmrConnectionService().getRefreshPeriodMs()); // wait to see published tx
trade.syncAndPollWallet(); trade.syncAndPollWallet();
@ -181,7 +183,6 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
else throw e; else throw e;
} }
} }
}
} else { } else {
log.info("Payout tx already published for {} {}", trade.getClass().getSimpleName(), trade.getId()); log.info("Payout tx already published for {} {}", trade.getClass().getSimpleName(), trade.getId());
if (message.getSignedPayoutTxHex() != null && !trade.isPayoutConfirmed()) trade.processPayoutTx(message.getSignedPayoutTxHex(), false, true); if (message.getSignedPayoutTxHex() != null && !trade.isPayoutConfirmed()) trade.processPayoutTx(message.getSignedPayoutTxHex(), false, true);

View File

@ -65,10 +65,10 @@ public class SellerPreparePaymentReceivedMessage extends TradeTask {
trade.processPayoutTx(trade.getPayoutTxHex(), false, true); trade.processPayoutTx(trade.getPayoutTxHex(), false, true);
} }
} catch (IllegalArgumentException | IllegalStateException e) { } catch (IllegalArgumentException | IllegalStateException e) {
log.warn("Illegal state or argument verifying, signing, and publishing payout tx for {} {}: {}. Creating new unsigned payout tx", trade.getClass().getSimpleName(), trade.getId(), e.getMessage()); log.warn("Illegal state or argument verifying, signing, and publishing payout tx for {} {}: {}. Creating new unsigned payout tx", trade.getClass().getSimpleName(), trade.getId(), e.getMessage(), e);
createUnsignedPayoutTx(); createUnsignedPayoutTx();
} catch (Exception e) { } catch (Exception e) {
log.warn("Error verifying, signing, and publishing payout tx for trade {}: {}", trade.getId(), e.getMessage()); log.warn("Error verifying, signing, and publishing payout tx for trade {}: {}", trade.getId(), e.getMessage(), e);
throw e; throw e;
} }
} }

View File

@ -61,7 +61,7 @@ public abstract class TradeTask extends Task<Trade> {
@Override @Override
protected void failed(Throwable t) { protected void failed(Throwable t) {
t.printStackTrace(); log.error("Trade task failed, error={}\n", t.getMessage(), t);
appendExceptionToErrorMessage(t); appendExceptionToErrorMessage(t);
trade.setErrorMessage(errorMessage); trade.setErrorMessage(errorMessage);
processModel.getTradeManager().requestPersistence(); processModel.getTradeManager().requestPersistence();

View File

@ -120,22 +120,18 @@ public class TradeStatisticsManager {
// collect duplicated trades // collect duplicated trades
Set<TradeStatistics3> duplicates = new HashSet<TradeStatistics3>(); Set<TradeStatistics3> duplicates = new HashSet<TradeStatistics3>();
Set<TradeStatistics3> deduplicates = new HashSet<TradeStatistics3>(); Set<TradeStatistics3> deduplicates = new HashSet<TradeStatistics3>();
Set<TradeStatistics3> usedAsDuplicate = new HashSet<TradeStatistics3>();
for (TradeStatistics3 tradeStatistic : earlyTrades) { for (TradeStatistics3 tradeStatistic : earlyTrades) {
TradeStatistics3 fuzzyDuplicate = findFuzzyDuplicate(tradeStatistic, deduplicates, usedAsDuplicate); TradeStatistics3 fuzzyDuplicate = findFuzzyDuplicate(tradeStatistic, deduplicates);
if (fuzzyDuplicate == null) deduplicates.add(tradeStatistic); if (fuzzyDuplicate == null) deduplicates.add(tradeStatistic);
else { else duplicates.add(tradeStatistic);
duplicates.add(tradeStatistic);
usedAsDuplicate.add(fuzzyDuplicate);
}
} }
// remove duplicated trades // remove duplicated trades
tradeStats.removeAll(duplicates); tradeStats.removeAll(duplicates);
} }
private TradeStatistics3 findFuzzyDuplicate(TradeStatistics3 tradeStatistics, Set<TradeStatistics3> set, Set<TradeStatistics3> excluded) { private TradeStatistics3 findFuzzyDuplicate(TradeStatistics3 tradeStatistics, Set<TradeStatistics3> set) {
return set.stream().filter(e -> !excluded.contains(e)).filter(e -> isFuzzyDuplicate(tradeStatistics, e)).findFirst().orElse(null); return set.stream().filter(e -> isFuzzyDuplicate(tradeStatistics, e)).findFirst().orElse(null);
} }
private boolean isFuzzyDuplicate(TradeStatistics3 tradeStatistics1, TradeStatistics3 tradeStatistics2) { private boolean isFuzzyDuplicate(TradeStatistics3 tradeStatistics1, TradeStatistics3 tradeStatistics2) {

View File

@ -38,6 +38,7 @@ import haveno.core.payment.PaymentAccountUtil;
import haveno.core.xmr.XmrNodeSettings; import haveno.core.xmr.XmrNodeSettings;
import haveno.core.xmr.nodes.XmrNodes; import haveno.core.xmr.nodes.XmrNodes;
import haveno.core.xmr.nodes.XmrNodes.MoneroNodesOption; import haveno.core.xmr.nodes.XmrNodes.MoneroNodesOption;
import haveno.core.xmr.nodes.XmrNodesSetupPreferences;
import haveno.core.xmr.wallet.Restrictions; import haveno.core.xmr.wallet.Restrictions;
import haveno.network.p2p.network.BridgeAddressProvider; import haveno.network.p2p.network.BridgeAddressProvider;
import java.util.ArrayList; import java.util.ArrayList;
@ -130,8 +131,11 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
private final PersistenceManager<PreferencesPayload> persistenceManager; private final PersistenceManager<PreferencesPayload> persistenceManager;
private final Config config; private final Config config;
private final String xmrNodesFromOptions; private final String xmrNodesFromOptions;
private final XmrNodes xmrNodes;
@Getter @Getter
private final BooleanProperty useStandbyModeProperty = new SimpleBooleanProperty(prefPayload.isUseStandbyMode()); private final BooleanProperty useStandbyModeProperty = new SimpleBooleanProperty(prefPayload.isUseStandbyMode());
@Getter
private final BooleanProperty useSoundForNotificationsProperty = new SimpleBooleanProperty(prefPayload.isUseSoundForNotifications());
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Constructor // Constructor
@ -140,11 +144,13 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
@Inject @Inject
public Preferences(PersistenceManager<PreferencesPayload> persistenceManager, public Preferences(PersistenceManager<PreferencesPayload> persistenceManager,
Config config, Config config,
@Named(Config.XMR_NODES) String xmrNodesFromOptions) { @Named(Config.XMR_NODES) String xmrNodesFromOptions,
XmrNodes xmrNodes) {
this.persistenceManager = persistenceManager; this.persistenceManager = persistenceManager;
this.config = config; this.config = config;
this.xmrNodesFromOptions = xmrNodesFromOptions; this.xmrNodesFromOptions = xmrNodesFromOptions;
this.xmrNodes = xmrNodes;
useAnimationsProperty.addListener((ov) -> { useAnimationsProperty.addListener((ov) -> {
prefPayload.setUseAnimations(useAnimationsProperty.get()); prefPayload.setUseAnimations(useAnimationsProperty.get());
@ -162,6 +168,11 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
requestPersistence(); requestPersistence();
}); });
useSoundForNotificationsProperty.addListener((ov) -> {
prefPayload.setUseSoundForNotifications(useSoundForNotificationsProperty.get());
requestPersistence();
});
traditionalCurrenciesAsObservable.addListener((javafx.beans.Observable ov) -> { traditionalCurrenciesAsObservable.addListener((javafx.beans.Observable ov) -> {
prefPayload.getTraditionalCurrencies().clear(); prefPayload.getTraditionalCurrencies().clear();
prefPayload.getTraditionalCurrencies().addAll(traditionalCurrenciesAsObservable); prefPayload.getTraditionalCurrencies().addAll(traditionalCurrenciesAsObservable);
@ -259,6 +270,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
// set all properties // set all properties
useAnimationsProperty.set(prefPayload.isUseAnimations()); useAnimationsProperty.set(prefPayload.isUseAnimations());
useStandbyModeProperty.set(prefPayload.isUseStandbyMode()); useStandbyModeProperty.set(prefPayload.isUseStandbyMode());
useSoundForNotificationsProperty.set(prefPayload.isUseSoundForNotifications());
cssThemeProperty.set(prefPayload.getCssTheme()); cssThemeProperty.set(prefPayload.getCssTheme());
@ -276,6 +288,12 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
if (config.useTorForXmrOptionSetExplicitly) if (config.useTorForXmrOptionSetExplicitly)
setUseTorForXmr(config.useTorForXmr); setUseTorForXmr(config.useTorForXmr);
// switch to public nodes if no provided nodes available
if (getMoneroNodesOptionOrdinal() == XmrNodes.MoneroNodesOption.PROVIDED.ordinal() && xmrNodes.selectPreferredNodes(new XmrNodesSetupPreferences(this)).isEmpty()) {
log.warn("No provided nodes available, switching to public nodes");
setMoneroNodesOptionOrdinal(XmrNodes.MoneroNodesOption.PUBLIC.ordinal());
}
if (xmrNodesFromOptions != null && !xmrNodesFromOptions.isEmpty()) { if (xmrNodesFromOptions != null && !xmrNodesFromOptions.isEmpty()) {
if (getMoneroNodes() != null && !getMoneroNodes().equals(xmrNodesFromOptions)) { if (getMoneroNodes() != null && !getMoneroNodes().equals(xmrNodesFromOptions)) {
log.warn("The Monero node(s) from the program argument and the one(s) persisted in the UI are different. " + log.warn("The Monero node(s) from the program argument and the one(s) persisted in the UI are different. " +
@ -306,6 +324,12 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
}); });
} }
// enable sounds by default for existing clients (protobuf does not express that new field is unset)
if (!prefPayload.isUseSoundForNotificationsInitialized()) {
prefPayload.setUseSoundForNotificationsInitialized(true);
setUseSoundForNotifications(true);
}
initialReadDone = true; initialReadDone = true;
requestPersistence(); requestPersistence();
} }
@ -697,6 +721,10 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
this.useStandbyModeProperty.set(useStandbyMode); this.useStandbyModeProperty.set(useStandbyMode);
} }
public void setUseSoundForNotifications(boolean useSoundForNotifications) {
this.useSoundForNotificationsProperty.set(useSoundForNotifications);
}
public void setTakeOfferSelectedPaymentAccountId(String value) { public void setTakeOfferSelectedPaymentAccountId(String value) {
prefPayload.setTakeOfferSelectedPaymentAccountId(value); prefPayload.setTakeOfferSelectedPaymentAccountId(value);
requestPersistence(); requestPersistence();
@ -946,6 +974,8 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
void setUseStandbyMode(boolean useStandbyMode); void setUseStandbyMode(boolean useStandbyMode);
void setUseSoundForNotifications(boolean useSoundForNotifications);
void setTakeOfferSelectedPaymentAccountId(String value); void setTakeOfferSelectedPaymentAccountId(String value);
void setIgnoreDustThreshold(int value); void setIgnoreDustThreshold(int value);

View File

@ -108,6 +108,8 @@ public final class PreferencesPayload implements PersistableEnvelope {
private boolean useMarketNotifications = true; private boolean useMarketNotifications = true;
private boolean usePriceNotifications = true; private boolean usePriceNotifications = true;
private boolean useStandbyMode = false; private boolean useStandbyMode = false;
private boolean useSoundForNotifications = true;
private boolean useSoundForNotificationsInitialized = false;
@Nullable @Nullable
private String rpcUser; private String rpcUser;
@Nullable @Nullable
@ -185,6 +187,8 @@ public final class PreferencesPayload implements PersistableEnvelope {
.setUseMarketNotifications(useMarketNotifications) .setUseMarketNotifications(useMarketNotifications)
.setUsePriceNotifications(usePriceNotifications) .setUsePriceNotifications(usePriceNotifications)
.setUseStandbyMode(useStandbyMode) .setUseStandbyMode(useStandbyMode)
.setUseSoundForNotifications(useSoundForNotifications)
.setUseSoundForNotificationsInitialized(useSoundForNotificationsInitialized)
.setBuyerSecurityDepositAsPercent(buyerSecurityDepositAsPercent) .setBuyerSecurityDepositAsPercent(buyerSecurityDepositAsPercent)
.setIgnoreDustThreshold(ignoreDustThreshold) .setIgnoreDustThreshold(ignoreDustThreshold)
.setClearDataAfterDays(clearDataAfterDays) .setClearDataAfterDays(clearDataAfterDays)
@ -280,6 +284,8 @@ public final class PreferencesPayload implements PersistableEnvelope {
proto.getUseMarketNotifications(), proto.getUseMarketNotifications(),
proto.getUsePriceNotifications(), proto.getUsePriceNotifications(),
proto.getUseStandbyMode(), proto.getUseStandbyMode(),
proto.getUseSoundForNotifications(),
proto.getUseSoundForNotificationsInitialized(),
proto.getRpcUser().isEmpty() ? null : proto.getRpcUser(), proto.getRpcUser().isEmpty() ? null : proto.getRpcUser(),
proto.getRpcPw().isEmpty() ? null : proto.getRpcPw(), proto.getRpcPw().isEmpty() ? null : proto.getRpcPw(),
proto.getTakeOfferSelectedPaymentAccountId().isEmpty() ? null : proto.getTakeOfferSelectedPaymentAccountId(), proto.getTakeOfferSelectedPaymentAccountId().isEmpty() ? null : proto.getTakeOfferSelectedPaymentAccountId(),

View File

@ -111,6 +111,7 @@ public class Balances {
public XmrBalanceInfo getBalances() { public XmrBalanceInfo getBalances() {
synchronized (this) { synchronized (this) {
if (availableBalance == null) return null;
return new XmrBalanceInfo(availableBalance.longValue() + pendingBalance.longValue(), return new XmrBalanceInfo(availableBalance.longValue() + pendingBalance.longValue(),
availableBalance.longValue(), availableBalance.longValue(),
pendingBalance.longValue(), pendingBalance.longValue(),
@ -127,6 +128,9 @@ public class Balances {
synchronized (this) { synchronized (this) {
synchronized (HavenoUtils.xmrWalletService.getWalletLock()) { synchronized (HavenoUtils.xmrWalletService.getWalletLock()) {
// get non-trade balance before
BigInteger balanceSumBefore = getNonTradeBalanceSum();
// get wallet balances // get wallet balances
BigInteger balance = xmrWalletService.getWallet() == null ? BigInteger.ZERO : xmrWalletService.getBalance(); BigInteger balance = xmrWalletService.getWallet() == null ? BigInteger.ZERO : xmrWalletService.getBalance();
availableBalance = xmrWalletService.getWallet() == null ? BigInteger.ZERO : xmrWalletService.getAvailableBalance(); availableBalance = xmrWalletService.getWallet() == null ? BigInteger.ZERO : xmrWalletService.getAvailableBalance();
@ -160,8 +164,25 @@ public class Balances {
reservedBalance = reservedOfferBalance.add(reservedTradeBalance); reservedBalance = reservedOfferBalance.add(reservedTradeBalance);
// notify balance update // notify balance update
UserThread.execute(() -> updateCounter.set(updateCounter.get() + 1)); UserThread.execute(() -> {
// check if funds received
boolean fundsReceived = balanceSumBefore != null && getNonTradeBalanceSum().compareTo(balanceSumBefore) > 0;
if (fundsReceived) {
HavenoUtils.playCashRegisterSound();
} }
// increase counter to notify listeners
updateCounter.set(updateCounter.get() + 1);
});
}
}
}
private BigInteger getNonTradeBalanceSum() {
synchronized (this) {
if (availableBalance == null) return null;
return availableBalance.add(pendingBalance).add(reservedOfferBalance);
} }
} }
} }

View File

@ -35,6 +35,8 @@ public class XmrNodeSettings implements PersistableEnvelope {
String bootstrapUrl; String bootstrapUrl;
@Nullable @Nullable
List<String> startupFlags; List<String> startupFlags;
@Nullable
Boolean syncBlockchain;
public XmrNodeSettings() { public XmrNodeSettings() {
} }
@ -43,7 +45,8 @@ public class XmrNodeSettings implements PersistableEnvelope {
return new XmrNodeSettings( return new XmrNodeSettings(
proto.getBlockchainPath(), proto.getBlockchainPath(),
proto.getBootstrapUrl(), proto.getBootstrapUrl(),
proto.getStartupFlagsList()); proto.getStartupFlagsList(),
proto.getSyncBlockchain());
} }
@Override @Override
@ -52,6 +55,7 @@ public class XmrNodeSettings implements PersistableEnvelope {
Optional.ofNullable(blockchainPath).ifPresent(e -> builder.setBlockchainPath(blockchainPath)); Optional.ofNullable(blockchainPath).ifPresent(e -> builder.setBlockchainPath(blockchainPath));
Optional.ofNullable(bootstrapUrl).ifPresent(e -> builder.setBootstrapUrl(bootstrapUrl)); Optional.ofNullable(bootstrapUrl).ifPresent(e -> builder.setBootstrapUrl(bootstrapUrl));
Optional.ofNullable(startupFlags).ifPresent(e -> builder.addAllStartupFlags(startupFlags)); Optional.ofNullable(startupFlags).ifPresent(e -> builder.addAllStartupFlags(startupFlags));
Optional.ofNullable(syncBlockchain).ifPresent(e -> builder.setSyncBlockchain(syncBlockchain));
return builder.build(); return builder.build();
} }
} }

View File

@ -62,7 +62,7 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
private final Map<String, EncryptedConnection> items = new HashMap<>(); private final Map<String, EncryptedConnection> items = new HashMap<>();
private @NonNull String currentConnectionUrl = ""; private @NonNull String currentConnectionUrl = "";
private long refreshPeriod; // -1 means no refresh, 0 means default, >0 means custom private long refreshPeriod; // -1 means no refresh, 0 means default, >0 means custom
private boolean autoSwitch; private boolean autoSwitch = true;
@Inject @Inject
public EncryptedConnectionList(PersistenceManager<EncryptedConnectionList> persistenceManager, public EncryptedConnectionList(PersistenceManager<EncryptedConnectionList> persistenceManager,

View File

@ -83,16 +83,15 @@ public class XmrNodes {
); );
case XMR_MAINNET: case XMR_MAINNET:
return Arrays.asList( return Arrays.asList(
new XmrNode(MoneroNodesOption.PROVIDED, null, null, "127.0.0.1", 18081, 1, "@local"), new XmrNode(MoneroNodesOption.PUBLIC, null, null, "127.0.0.1", 18081, 1, "@local"),
new XmrNode(MoneroNodesOption.PROVIDED, null, null, "xmr-node.cakewallet.com", 18081, 2, "@cakewallet"), new XmrNode(MoneroNodesOption.PUBLIC, null, null, "xmr-node.cakewallet.com", 18081, 2, "@cakewallet"),
new XmrNode(MoneroNodesOption.PROVIDED, null, null, "node.community.rino.io", 18081, 2, "@RINOwallet"), new XmrNode(MoneroNodesOption.PUBLIC, null, null, "node.community.rino.io", 18081, 2, "@RINOwallet"),
new XmrNode(MoneroNodesOption.PROVIDED, null, null, "nodes.hashvault.pro", 18080, 2, "@HashVault"), new XmrNode(MoneroNodesOption.PUBLIC, null, null, "nodes.hashvault.pro", 18080, 2, "@HashVault"),
new XmrNode(MoneroNodesOption.PROVIDED, null, null, "p2pmd.xmrvsbeast.com", 18080, 2, "@xmrvsbeast"), new XmrNode(MoneroNodesOption.PUBLIC, null, null, "p2pmd.xmrvsbeast.com", 18080, 2, "@xmrvsbeast"),
new XmrNode(MoneroNodesOption.PROVIDED, null, null, "node.monerodevs.org", 18089, 2, "@monerodevs.org"), new XmrNode(MoneroNodesOption.PUBLIC, null, null, "node.monerodevs.org", 18089, 2, "@monerodevs.org"),
new XmrNode(MoneroNodesOption.PROVIDED, null, null, "nodex.monerujo.io", 18081, 2, "@monerujo.io"), new XmrNode(MoneroNodesOption.PUBLIC, null, null, "nodex.monerujo.io", 18081, 2, "@monerujo.io"),
new XmrNode(MoneroNodesOption.PUBLIC, null, null, "rucknium.me", 18081, 2, "@Rucknium"), new XmrNode(MoneroNodesOption.PUBLIC, null, null, "rucknium.me", 18081, 2, "@Rucknium"),
new XmrNode(MoneroNodesOption.PUBLIC, null, null, "node.sethforprivacy.com", 18089, 2, "@sethforprivacy"), new XmrNode(MoneroNodesOption.PUBLIC, null, null, "node.sethforprivacy.com", 18089, 2, "@sethforprivacy")
new XmrNode(MoneroNodesOption.PUBLIC, null, null, "node3.monerodevs.org", 18089, 2, "@monerodevs.org")
); );
default: default:
throw new IllegalStateException("Unexpected base currency network: " + Config.baseCurrencyNetwork()); throw new IllegalStateException("Unexpected base currency network: " + Config.baseCurrencyNetwork());

View File

@ -6,6 +6,8 @@ import java.util.Optional;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.exception.ExceptionUtils;
import haveno.common.Timer; import haveno.common.Timer;
import haveno.common.UserThread; import haveno.common.UserThread;
import haveno.core.api.XmrConnectionService; import haveno.core.api.XmrConnectionService;
@ -25,7 +27,7 @@ import monero.wallet.model.MoneroWalletListener;
public class XmrWalletBase { public class XmrWalletBase {
// constants // constants
public static final int SYNC_PROGRESS_TIMEOUT_SECONDS = 60; public static final int SYNC_PROGRESS_TIMEOUT_SECONDS = 120;
public static final int DIRECT_SYNC_WITHIN_BLOCKS = 100; public static final int DIRECT_SYNC_WITHIN_BLOCKS = 100;
// inherited // inherited
@ -106,7 +108,7 @@ public class XmrWalletBase {
height = wallet.getHeight(); // can get read timeout while syncing height = wallet.getHeight(); // can get read timeout while syncing
} catch (Exception e) { } catch (Exception e) {
log.warn("Error getting wallet height while syncing with progress: " + e.getMessage()); log.warn("Error getting wallet height while syncing with progress: " + e.getMessage());
if (wallet != null && !isShutDownStarted) e.printStackTrace(); if (wallet != null && !isShutDownStarted) log.warn(ExceptionUtils.getStackTrace(e));
// stop polling and release latch // stop polling and release latch
syncProgressError = e; syncProgressError = e;

View File

@ -818,7 +818,7 @@ public class XmrWalletService extends XmrWalletBase {
MoneroFeeEstimate feeEstimates = getDaemon().getFeeEstimate(); MoneroFeeEstimate feeEstimates = getDaemon().getFeeEstimate();
BigInteger baseFeeEstimate = feeEstimates.getFees().get(2); // get elevated fee per kB BigInteger baseFeeEstimate = feeEstimates.getFees().get(2); // get elevated fee per kB
BigInteger qmask = feeEstimates.getQuantizationMask(); BigInteger qmask = feeEstimates.getQuantizationMask();
log.info("Monero base fee estimate={}, qmask={}: " + baseFeeEstimate, qmask); log.info("Monero base fee estimate={}, qmask={}", baseFeeEstimate, qmask);
// get tx base fee // get tx base fee
BigInteger baseFee = baseFeeEstimate.multiply(BigInteger.valueOf(txWeight)); BigInteger baseFee = baseFeeEstimate.multiply(BigInteger.valueOf(txWeight));
@ -922,8 +922,7 @@ public class XmrWalletService extends XmrWalletBase {
try { try {
ThreadUtils.awaitTask(shutDownTask, SHUTDOWN_TIMEOUT_MS); ThreadUtils.awaitTask(shutDownTask, SHUTDOWN_TIMEOUT_MS);
} catch (Exception e) { } catch (Exception e) {
log.warn("Error shutting down {}: {}", getClass().getSimpleName(), e.getMessage()); log.warn("Error shutting down {}: {}\n", getClass().getSimpleName(), e.getMessage(), e);
e.printStackTrace();
// force close wallet // force close wallet
forceCloseMainWallet(); forceCloseMainWallet();
@ -945,8 +944,7 @@ public class XmrWalletService extends XmrWalletBase {
List<XmrAddressEntry> unusedAddressEntries = getUnusedAddressEntries(); List<XmrAddressEntry> unusedAddressEntries = getUnusedAddressEntries();
if (!unusedAddressEntries.isEmpty()) return xmrAddressEntryList.swapAvailableToAddressEntryWithOfferId(unusedAddressEntries.get(0), context, offerId); if (!unusedAddressEntries.isEmpty()) return xmrAddressEntryList.swapAvailableToAddressEntryWithOfferId(unusedAddressEntries.get(0), context, offerId);
} catch (Exception e) { } catch (Exception e) {
log.warn("Error getting new address entry based on incoming transactions"); log.warn("Error getting new address entry based on incoming transactions: {}\n", e.getMessage(), e);
e.printStackTrace();
} }
// create new entry // create new entry
@ -1172,8 +1170,7 @@ public class XmrWalletService extends XmrWalletBase {
try { try {
balanceListener.onBalanceChanged(balance); balanceListener.onBalanceChanged(balance);
} catch (Exception e) { } catch (Exception e) {
log.warn("Failed to notify balance listener of change"); log.warn("Failed to notify balance listener of change: {}\n", e.getMessage(), e);
e.printStackTrace();
} }
}); });
} }
@ -1309,8 +1306,7 @@ public class XmrWalletService extends XmrWalletBase {
try { try {
doMaybeInitMainWallet(sync, MAX_SYNC_ATTEMPTS); doMaybeInitMainWallet(sync, MAX_SYNC_ATTEMPTS);
} catch (Exception e) { } catch (Exception e) {
log.warn("Error initializing main wallet: " + e.getMessage()); log.warn("Error initializing main wallet: {}\n", e.getMessage(), e);
e.printStackTrace();
HavenoUtils.setTopError(e.getMessage()); HavenoUtils.setTopError(e.getMessage());
throw e; throw e;
} }
@ -1459,9 +1455,10 @@ public class XmrWalletService extends XmrWalletBase {
log.info("Done creating full wallet " + config.getPath() + " in " + (System.currentTimeMillis() - time) + " ms"); log.info("Done creating full wallet " + config.getPath() + " in " + (System.currentTimeMillis() - time) + " ms");
return walletFull; return walletFull;
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); String errorMsg = "Could not create wallet '" + config.getPath() + "': " + e.getMessage();
log.warn(errorMsg + "\n", e);
if (walletFull != null) forceCloseWallet(walletFull, config.getPath()); if (walletFull != null) forceCloseWallet(walletFull, config.getPath());
throw new IllegalStateException("Could not create wallet '" + config.getPath() + "'"); throw new IllegalStateException(errorMsg);
} }
} }
@ -1503,15 +1500,15 @@ public class XmrWalletService extends XmrWalletBase {
} }
// handle success or failure // handle success or failure
File originalCacheBackup = new File(cachePath + ".backup");
if (retrySuccessful) { if (retrySuccessful) {
originalCacheFile.delete(); // delete original wallet cache backup if (originalCacheBackup.exists()) originalCacheBackup.delete(); // delete original wallet cache backup
} else { } else {
// restore original wallet cache // restore original wallet cache
log.warn("Failed to open full wallet using backup cache, restoring original cache"); log.warn("Failed to open full wallet using backup cache, restoring original cache");
File cacheFile = new File(cachePath); File cacheFile = new File(cachePath);
if (cacheFile.exists()) cacheFile.delete(); if (cacheFile.exists()) cacheFile.delete();
File originalCacheBackup = new File(cachePath + ".backup");
if (originalCacheBackup.exists()) originalCacheBackup.renameTo(new File(cachePath)); if (originalCacheBackup.exists()) originalCacheBackup.renameTo(new File(cachePath));
// throw exception // throw exception
@ -1525,9 +1522,10 @@ public class XmrWalletService extends XmrWalletBase {
log.info("Done opening full wallet " + config.getPath()); log.info("Done opening full wallet " + config.getPath());
return walletFull; return walletFull;
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); String errorMsg = "Could not open full wallet '" + config.getPath() + "': " + e.getMessage();
log.warn(errorMsg + "\n", e);
if (walletFull != null) forceCloseWallet(walletFull, config.getPath()); if (walletFull != null) forceCloseWallet(walletFull, config.getPath());
throw new IllegalStateException("Could not open full wallet '" + config.getPath() + "'"); throw new IllegalStateException(errorMsg);
} }
} }
@ -1557,7 +1555,7 @@ public class XmrWalletService extends XmrWalletBase {
log.info("Done creating RPC wallet " + config.getPath() + " in " + (System.currentTimeMillis() - time) + " ms"); log.info("Done creating RPC wallet " + config.getPath() + " in " + (System.currentTimeMillis() - time) + " ms");
return walletRpc; return walletRpc;
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); log.warn("Could not create wallet '" + config.getPath() + "': " + e.getMessage() + "\n", e);
if (walletRpc != null) forceCloseWallet(walletRpc, config.getPath()); if (walletRpc != null) forceCloseWallet(walletRpc, config.getPath());
throw new IllegalStateException("Could not create wallet '" + config.getPath() + "'. Please close Haveno, stop all monero-wallet-rpc processes in your task manager, and restart Haveno."); throw new IllegalStateException("Could not create wallet '" + config.getPath() + "'. Please close Haveno, stop all monero-wallet-rpc processes in your task manager, and restart Haveno.");
} }
@ -1607,15 +1605,15 @@ public class XmrWalletService extends XmrWalletBase {
} }
// handle success or failure // handle success or failure
File originalCacheBackup = new File(cachePath + ".backup");
if (retrySuccessful) { if (retrySuccessful) {
originalCacheFile.delete(); // delete original wallet cache backup if (originalCacheBackup.exists()) originalCacheBackup.delete(); // delete original wallet cache backup
} else { } else {
// restore original wallet cache // restore original wallet cache
log.warn("Failed to open RPC wallet using backup cache, restoring original cache"); log.warn("Failed to open RPC wallet using backup cache, restoring original cache");
File cacheFile = new File(cachePath); File cacheFile = new File(cachePath);
if (cacheFile.exists()) cacheFile.delete(); if (cacheFile.exists()) cacheFile.delete();
File originalCacheBackup = new File(cachePath + ".backup");
if (originalCacheBackup.exists()) originalCacheBackup.renameTo(new File(cachePath)); if (originalCacheBackup.exists()) originalCacheBackup.renameTo(new File(cachePath));
// throw exception // throw exception
@ -1629,7 +1627,7 @@ public class XmrWalletService extends XmrWalletBase {
log.info("Done opening RPC wallet " + config.getPath()); log.info("Done opening RPC wallet " + config.getPath());
return walletRpc; return walletRpc;
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); log.warn("Could not open wallet '" + config.getPath() + "': " + e.getMessage() + "\n", e);
if (walletRpc != null) forceCloseWallet(walletRpc, config.getPath()); if (walletRpc != null) forceCloseWallet(walletRpc, config.getPath());
throw new IllegalStateException("Could not open wallet '" + config.getPath() + "'. Please close Haveno, stop all monero-wallet-rpc processes in your task manager, and restart Haveno.\n\nError message: " + e.getMessage()); throw new IllegalStateException("Could not open wallet '" + config.getPath() + "'. Please close Haveno, stop all monero-wallet-rpc processes in your task manager, and restart Haveno.\n\nError message: " + e.getMessage());
} }
@ -1733,7 +1731,7 @@ public class XmrWalletService extends XmrWalletBase {
wallet.changePassword(oldPassword, newPassword); wallet.changePassword(oldPassword, newPassword);
saveMainWallet(); saveMainWallet();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); log.warn("Error changing main wallet password: " + e.getMessage() + "\n", e);
throw e; throw e;
} }
}); });
@ -1916,7 +1914,7 @@ public class XmrWalletService extends XmrWalletBase {
cacheWalletInfo(); cacheWalletInfo();
requestSaveMainWallet(); requestSaveMainWallet();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); log.warn("Error caching wallet info: " + e.getMessage() + "\n", e);
} }
} }
} }

Binary file not shown.

Binary file not shown.

View File

@ -1266,6 +1266,7 @@ setting.preferences.general=General preferences
setting.preferences.explorer=Monero Explorer setting.preferences.explorer=Monero Explorer
setting.preferences.deviation=Max. deviation from market price setting.preferences.deviation=Max. deviation from market price
setting.preferences.avoidStandbyMode=Avoid standby mode setting.preferences.avoidStandbyMode=Avoid standby mode
setting.preferences.useSoundForNotifications=Play sounds for notifications
setting.preferences.autoConfirmXMR=XMR auto-confirm setting.preferences.autoConfirmXMR=XMR auto-confirm
setting.preferences.autoConfirmEnabled=Enabled setting.preferences.autoConfirmEnabled=Enabled
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations setting.preferences.autoConfirmRequiredConfirmations=Required confirmations
@ -2020,9 +2021,12 @@ tradeDetailsWindow.agentAddresses=Arbitrator/Mediator
tradeDetailsWindow.detailData=Detail data tradeDetailsWindow.detailData=Detail data
txDetailsWindow.headline=Transaction Details txDetailsWindow.headline=Transaction Details
txDetailsWindow.xmr.note=You have sent XMR. txDetailsWindow.xmr.noteSent=You have sent XMR.
txDetailsWindow.xmr.noteReceived=You have received XMR.
txDetailsWindow.sentTo=Sent to txDetailsWindow.sentTo=Sent to
txDetailsWindow.receivedWith=Received with
txDetailsWindow.txId=TxId txDetailsWindow.txId=TxId
txDetailsWindow.txKey=Transaction Key
closedTradesSummaryWindow.headline=Trade history summary closedTradesSummaryWindow.headline=Trade history summary
closedTradesSummaryWindow.totalAmount.title=Total trade amount closedTradesSummaryWindow.totalAmount.title=Total trade amount
@ -2442,7 +2446,7 @@ password.deriveKey=Derive key from password
password.walletDecrypted=Wallet successfully decrypted and password protection removed. password.walletDecrypted=Wallet successfully decrypted and password protection removed.
password.wrongPw=You entered the wrong password.\n\nPlease try entering your password again, carefully checking for typos or spelling errors. password.wrongPw=You entered the wrong password.\n\nPlease try entering your password again, carefully checking for typos or spelling errors.
password.walletEncrypted=Wallet successfully encrypted and password protection enabled. password.walletEncrypted=Wallet successfully encrypted and password protection enabled.
password.walletEncryptionFailed=Wallet password could not be set. You may have imported seed words which do not match the wallet database. Please contact the developers on Keybase ([HYPERLINK:https://keybase.io/team/haveno]). password.walletEncryptionFailed=Password could not be set.
password.passwordsDoNotMatch=The 2 passwords you entered don't match. password.passwordsDoNotMatch=The 2 passwords you entered don't match.
password.forgotPassword=Forgot password? password.forgotPassword=Forgot password?
password.backupReminder=Please note that when setting a wallet password all automatically created backups from the unencrypted wallet will be deleted.\n\n\ password.backupReminder=Please note that when setting a wallet password all automatically created backups from the unencrypted wallet will be deleted.\n\n\

View File

@ -987,6 +987,7 @@ setting.preferences.general=Základní nastavení
setting.preferences.explorer=Monero Explorer setting.preferences.explorer=Monero Explorer
setting.preferences.deviation=Max. odchylka od tržní ceny setting.preferences.deviation=Max. odchylka od tržní ceny
setting.preferences.avoidStandbyMode=Vyhněte se pohotovostnímu režimu setting.preferences.avoidStandbyMode=Vyhněte se pohotovostnímu režimu
setting.preferences.useSoundForNotifications=Přehrávat zvuky pro upozornění
setting.preferences.autoConfirmXMR=Automatické potvrzení XMR setting.preferences.autoConfirmXMR=Automatické potvrzení XMR
setting.preferences.autoConfirmEnabled=Povoleno setting.preferences.autoConfirmEnabled=Povoleno
setting.preferences.autoConfirmRequiredConfirmations=Požadovaná potvrzení setting.preferences.autoConfirmRequiredConfirmations=Požadovaná potvrzení
@ -1501,8 +1502,10 @@ tradeDetailsWindow.agentAddresses=Rozhodce/Mediátor
tradeDetailsWindow.detailData=Detailní data tradeDetailsWindow.detailData=Detailní data
txDetailsWindow.headline=Detaily transakce txDetailsWindow.headline=Detaily transakce
txDetailsWindow.xmr.note=Poslali jste XMR. txDetailsWindow.xmr.noteSent=Poslali jste XMR.
txDetailsWindow.xmr.noteReceived=Obdrželi jste XMR.
txDetailsWindow.sentTo=Odesláno na txDetailsWindow.sentTo=Odesláno na
txDetailsWindow.receivedWith=Přijato s
txDetailsWindow.txId=TxId txDetailsWindow.txId=TxId
closedTradesSummaryWindow.headline=Souhrn uzavřených obchodů closedTradesSummaryWindow.headline=Souhrn uzavřených obchodů
@ -1844,7 +1847,6 @@ password.deriveKey=Odvozuji klíč z hesla
password.walletDecrypted=Peněženka úspěšně dešifrována a ochrana heslem byla odstraněna. password.walletDecrypted=Peněženka úspěšně dešifrována a ochrana heslem byla odstraněna.
password.wrongPw=Zadali jste nesprávné heslo.\n\nZkuste prosím zadat heslo znovu a pečlivě zkontrolujte překlepy nebo pravopisné chyby. password.wrongPw=Zadali jste nesprávné heslo.\n\nZkuste prosím zadat heslo znovu a pečlivě zkontrolujte překlepy nebo pravopisné chyby.
password.walletEncrypted=Peněženka úspěšně šifrována a ochrana heslem povolena. password.walletEncrypted=Peněženka úspěšně šifrována a ochrana heslem povolena.
password.walletEncryptionFailed=Heslo peněženky nelze nastavit. Možná jste importovali počáteční slova, která neodpovídají databázi peněženky. Kontaktujte vývojáře na Keybase ([HYPERLINK:https://keybase.io/team/haveno]).
password.passwordsDoNotMatch=Zadaná 2 hesla se neshodují. password.passwordsDoNotMatch=Zadaná 2 hesla se neshodují.
password.forgotPassword=Zapomněli jste heslo? password.forgotPassword=Zapomněli jste heslo?
password.backupReminder=Pamatujte, že při nastavování hesla do peněženky budou odstraněny všechny automaticky vytvořené zálohy z nezašifrované peněženky.\n\nPřed nastavením hesla se důrazně doporučuje provést zálohu adresáře aplikace a zapsat si počáteční slova! password.backupReminder=Pamatujte, že při nastavování hesla do peněženky budou odstraněny všechny automaticky vytvořené zálohy z nezašifrované peněženky.\n\nPřed nastavením hesla se důrazně doporučuje provést zálohu adresáře aplikace a zapsat si počáteční slova!

View File

@ -987,6 +987,7 @@ setting.preferences.general=Allgemeine Voreinstellungen
setting.preferences.explorer=Monero Explorer setting.preferences.explorer=Monero Explorer
setting.preferences.deviation=Max. Abweichung vom Marktpreis setting.preferences.deviation=Max. Abweichung vom Marktpreis
setting.preferences.avoidStandbyMode=Standby Modus verhindern setting.preferences.avoidStandbyMode=Standby Modus verhindern
setting.preferences.useSoundForNotifications=Spiele Geräusche für Benachrichtigungen
setting.preferences.autoConfirmXMR=XMR automatische Bestätigung setting.preferences.autoConfirmXMR=XMR automatische Bestätigung
setting.preferences.autoConfirmEnabled=Aktiviert setting.preferences.autoConfirmEnabled=Aktiviert
setting.preferences.autoConfirmRequiredConfirmations=Benötigte Bestätigungen setting.preferences.autoConfirmRequiredConfirmations=Benötigte Bestätigungen
@ -1501,8 +1502,10 @@ tradeDetailsWindow.agentAddresses=Vermittler/Mediator
tradeDetailsWindow.detailData=Detaillierte Daten tradeDetailsWindow.detailData=Detaillierte Daten
txDetailsWindow.headline=Transaktionsdetails txDetailsWindow.headline=Transaktionsdetails
txDetailsWindow.xmr.note=Sie haben XMR gesendet. txDetailsWindow.xmr.noteSent=Sie haben XMR gesendet.
txDetailsWindow.xmr.noteReceived=Sie haben XMR erhalten.
txDetailsWindow.sentTo=Gesendet an txDetailsWindow.sentTo=Gesendet an
txDetailsWindow.receivedWith=Erhalten mit
txDetailsWindow.txId=TxId txDetailsWindow.txId=TxId
closedTradesSummaryWindow.headline=Trade history summary closedTradesSummaryWindow.headline=Trade history summary
@ -1845,7 +1848,6 @@ password.deriveKey=Schlüssel aus Passwort ableiten
password.walletDecrypted=Die Wallet wurde erfolgreich entschlüsselt und der Passwortschutz entfernt. password.walletDecrypted=Die Wallet wurde erfolgreich entschlüsselt und der Passwortschutz entfernt.
password.wrongPw=Sie haben das falsche Passwort eingegeben.\n\nVersuchen Sie bitte Ihr Passwort erneut einzugeben, wobei Sie dies vorsichtig auf Tipp- und Rechtschreibfehler überprüfen sollten. password.wrongPw=Sie haben das falsche Passwort eingegeben.\n\nVersuchen Sie bitte Ihr Passwort erneut einzugeben, wobei Sie dies vorsichtig auf Tipp- und Rechtschreibfehler überprüfen sollten.
password.walletEncrypted=Die Wallet wurde erfolgreich verschlüsselt und der Passwortschutz aktiviert. password.walletEncrypted=Die Wallet wurde erfolgreich verschlüsselt und der Passwortschutz aktiviert.
password.walletEncryptionFailed=Wallet Passwort konnte nicht eingerichtet werden. Sie haben vielleicht Seed-Wörter importiert, die nicht mit der Wallet-Datenbank übereinstimmen. Bitte kontaktieren Sie die Entwickler auf Keybase ([HYPERLINK:https://keybase.io/team/haveno]).
password.passwordsDoNotMatch=Die 2 eingegebenen Passwörter stimmen nicht überein. password.passwordsDoNotMatch=Die 2 eingegebenen Passwörter stimmen nicht überein.
password.forgotPassword=Passwort vergessen? password.forgotPassword=Passwort vergessen?
password.backupReminder=Beachten Sie, dass wenn Sie ein Passwort setzen, alle automatisch erstellten Backups der unverschlüsselten Wallet gelöscht werden.\n\nEs wird dringend empfohlen ein Backup des Anwendungsverzeichnisses zu erstellen und die Seed-Wörter aufzuschreiben, bevor Sie ein Passwort erstellen! password.backupReminder=Beachten Sie, dass wenn Sie ein Passwort setzen, alle automatisch erstellten Backups der unverschlüsselten Wallet gelöscht werden.\n\nEs wird dringend empfohlen ein Backup des Anwendungsverzeichnisses zu erstellen und die Seed-Wörter aufzuschreiben, bevor Sie ein Passwort erstellen!

View File

@ -988,6 +988,7 @@ setting.preferences.general=Preferencias generales
setting.preferences.explorer=Explorador Monero setting.preferences.explorer=Explorador Monero
setting.preferences.deviation=Desviación máxima del precio de mercado setting.preferences.deviation=Desviación máxima del precio de mercado
setting.preferences.setting.preferences.avoidStandbyMode=Evitar modo en espera setting.preferences.setting.preferences.avoidStandbyMode=Evitar modo en espera
setting.preferences.useSoundForNotifications=Reproducir sonidos para notificaciones
setting.preferences.autoConfirmXMR=Autoconfirmación XMR setting.preferences.autoConfirmXMR=Autoconfirmación XMR
setting.preferences.autoConfirmEnabled=Habilitado setting.preferences.autoConfirmEnabled=Habilitado
setting.preferences.autoConfirmRequiredConfirmations=Confirmaciones requeridas setting.preferences.autoConfirmRequiredConfirmations=Confirmaciones requeridas
@ -1502,8 +1503,10 @@ tradeDetailsWindow.agentAddresses=Árbitro/Mediador
tradeDetailsWindow.detailData=Detalle de datos tradeDetailsWindow.detailData=Detalle de datos
txDetailsWindow.headline=Detalles de transacción txDetailsWindow.headline=Detalles de transacción
txDetailsWindow.xmr.note=Ha enviado XMR txDetailsWindow.xmr.noteSent=Ha enviado XMR
txDetailsWindow.xmr.noteReceived=Has recibido XMR.
txDetailsWindow.sentTo=Enviado a txDetailsWindow.sentTo=Enviado a
txDetailsWindow.receivedWith=Recibido con
txDetailsWindow.txId=TxId txDetailsWindow.txId=TxId
closedTradesSummaryWindow.headline=Resume de historia de intercambio closedTradesSummaryWindow.headline=Resume de historia de intercambio
@ -1846,7 +1849,6 @@ password.deriveKey=Derivar clave desde contraseña
password.walletDecrypted=El monedero se desencriptó con éxito y se eliminó la protección por contraseña. password.walletDecrypted=El monedero se desencriptó con éxito y se eliminó la protección por contraseña.
password.wrongPw=Ha introducido la contraseña incorrecta.\n\nPor favor, introduzca nuevamente la contraseña, evitando errores. password.wrongPw=Ha introducido la contraseña incorrecta.\n\nPor favor, introduzca nuevamente la contraseña, evitando errores.
password.walletEncrypted=El monedero se encriptó con éxito y se activó la protección por contraseña. password.walletEncrypted=El monedero se encriptó con éxito y se activó la protección por contraseña.
password.walletEncryptionFailed=No se pudo establecer la contraseña de de la cartera. Puede haber importado palabras semilla que no corresponden a la base de datos del monedero. Por favor contacte con los desarrolladores en Keybase ([HYPERLINK:https://keybase.io/team/haveno]).
password.passwordsDoNotMatch=Las 2 contraseñas introducidas no coinciden. password.passwordsDoNotMatch=Las 2 contraseñas introducidas no coinciden.
password.forgotPassword=¿Ha olvidado la contraseña? password.forgotPassword=¿Ha olvidado la contraseña?
password.backupReminder=Por favor, al establecer una contraseña para la cartera, tenga en cuenta que todas las copias de seguridad creadas de la cartera no encriptada serán borradas automáticamente password.backupReminder=Por favor, al establecer una contraseña para la cartera, tenga en cuenta que todas las copias de seguridad creadas de la cartera no encriptada serán borradas automáticamente

View File

@ -984,6 +984,7 @@ setting.preferences.general=اولویت‌های عمومی
setting.preferences.explorer=Monero Explorer setting.preferences.explorer=Monero Explorer
setting.preferences.deviation=حداکثر تفاوت از قیمت روز بازار setting.preferences.deviation=حداکثر تفاوت از قیمت روز بازار
setting.preferences.avoidStandbyMode=حالت «آماده باش» را نادیده بگیر setting.preferences.avoidStandbyMode=حالت «آماده باش» را نادیده بگیر
setting.preferences.useSoundForNotifications=پخش صداها برای اعلان‌ها
setting.preferences.autoConfirmXMR=XMR auto-confirm setting.preferences.autoConfirmXMR=XMR auto-confirm
setting.preferences.autoConfirmEnabled=Enabled setting.preferences.autoConfirmEnabled=Enabled
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations setting.preferences.autoConfirmRequiredConfirmations=Required confirmations
@ -1497,8 +1498,10 @@ tradeDetailsWindow.agentAddresses=Arbitrator/Mediator
tradeDetailsWindow.detailData=Detail data tradeDetailsWindow.detailData=Detail data
txDetailsWindow.headline=Transaction Details txDetailsWindow.headline=Transaction Details
txDetailsWindow.xmr.note=You have sent XMR. txDetailsWindow.xmr.noteSent=شما XMR ارسال کرده‌اید.
txDetailsWindow.sentTo=Sent to txDetailsWindow.xmr.noteReceived=شما XMR دریافت کرده‌اید.
txDetailsWindow.sentTo=ارسال به
txDetailsWindow.receivedWith=دریافت با
txDetailsWindow.txId=TxId txDetailsWindow.txId=TxId
closedTradesSummaryWindow.headline=Trade history summary closedTradesSummaryWindow.headline=Trade history summary
@ -1838,7 +1841,6 @@ password.deriveKey=کلید را از رمز عبور استنتاج کنید
password.walletDecrypted=کیف پول با موفقیت رمزگشایی شد و حفاظت با رمز عبور حذف شد. password.walletDecrypted=کیف پول با موفقیت رمزگشایی شد و حفاظت با رمز عبور حذف شد.
password.wrongPw=شما رمز عبور را اشتباه وارد کرده اید.\n\n لطفا سعی کنید رمز عبور خود را وارد کنید و با دقت خطاها و اشتباهات املایی را بررسی کنید. password.wrongPw=شما رمز عبور را اشتباه وارد کرده اید.\n\n لطفا سعی کنید رمز عبور خود را وارد کنید و با دقت خطاها و اشتباهات املایی را بررسی کنید.
password.walletEncrypted=کیف پول به طور موفقیت آمیز کدگذاری و حفاظت کیف پول فعال شد. password.walletEncrypted=کیف پول به طور موفقیت آمیز کدگذاری و حفاظت کیف پول فعال شد.
password.walletEncryptionFailed=Wallet password could not be set. You may have imported seed words which do not match the wallet database. Please contact the developers on Keybase ([HYPERLINK:https://keybase.io/team/haveno]).
password.passwordsDoNotMatch=2 رمز عبوری که وارد نموده اید باهم مطابقت ندارند. password.passwordsDoNotMatch=2 رمز عبوری که وارد نموده اید باهم مطابقت ندارند.
password.forgotPassword=رمز عبور را فراموش کرده اید؟ password.forgotPassword=رمز عبور را فراموش کرده اید؟
password.backupReminder=Please note that when setting a wallet password all automatically created backups from the unencrypted wallet will be deleted.\n\nIt is highly recommended that you make a backup of the application directory and write down your seed words before setting a password! password.backupReminder=Please note that when setting a wallet password all automatically created backups from the unencrypted wallet will be deleted.\n\nIt is highly recommended that you make a backup of the application directory and write down your seed words before setting a password!

View File

@ -989,6 +989,7 @@ setting.preferences.general=Préférences générales
setting.preferences.explorer=Exploreur Monero setting.preferences.explorer=Exploreur Monero
setting.preferences.deviation=Ecart maximal par rapport au prix du marché setting.preferences.deviation=Ecart maximal par rapport au prix du marché
setting.preferences.avoidStandbyMode=Éviter le mode veille setting.preferences.avoidStandbyMode=Éviter le mode veille
setting.preferences.useSoundForNotifications=Jouer des sons pour les notifications
setting.preferences.autoConfirmXMR=Auto-confirmation XMR setting.preferences.autoConfirmXMR=Auto-confirmation XMR
setting.preferences.autoConfirmEnabled=Activé setting.preferences.autoConfirmEnabled=Activé
setting.preferences.autoConfirmRequiredConfirmations=Confirmations requises setting.preferences.autoConfirmRequiredConfirmations=Confirmations requises
@ -1503,8 +1504,10 @@ tradeDetailsWindow.agentAddresses=Arbitre/Médiateur
tradeDetailsWindow.detailData=Données détaillées tradeDetailsWindow.detailData=Données détaillées
txDetailsWindow.headline=Détails de la transaction txDetailsWindow.headline=Détails de la transaction
txDetailsWindow.xmr.note=Vous avez envoyé du XMR. txDetailsWindow.xmr.noteSent=Vous avez envoyé du XMR.
txDetailsWindow.xmr.noteReceived=Vous avez reçu XMR.
txDetailsWindow.sentTo=Envoyé à txDetailsWindow.sentTo=Envoyé à
txDetailsWindow.receivedWith=Reçu avec
txDetailsWindow.txId=ID de transaction txDetailsWindow.txId=ID de transaction
closedTradesSummaryWindow.headline=Résumé de l'historique de trade closedTradesSummaryWindow.headline=Résumé de l'historique de trade
@ -1847,7 +1850,6 @@ password.deriveKey=Récupérer la clé à partir du mot de passe
password.walletDecrypted=Portefeuille décrypté avec succès et protection par mot de passe désactivée. password.walletDecrypted=Portefeuille décrypté avec succès et protection par mot de passe désactivée.
password.wrongPw=Vous avez entré un mot de passe incorrect.\n\nVeuillez réessayer d'entrer votre mot de passe, en vérifiant soigneusement qu'il ne contient pas de fautes de frappe ou d'orthographe. password.wrongPw=Vous avez entré un mot de passe incorrect.\n\nVeuillez réessayer d'entrer votre mot de passe, en vérifiant soigneusement qu'il ne contient pas de fautes de frappe ou d'orthographe.
password.walletEncrypted=Portefeuille crypté avec succès et protection par mot de passe activée. password.walletEncrypted=Portefeuille crypté avec succès et protection par mot de passe activée.
password.walletEncryptionFailed=Le mot de passe du portefeuille n'a pas pu être défini. Il est possible que vous ayiez importé des mots sources qui ne correspondent pas à la base de données du portefeuille. Veuillez contacter les développeurs sur Keybase ([LIEN:https://keybase.io/team/haveno]).
password.passwordsDoNotMatch=Les 2 mots de passe entrés ne correspondent pas. password.passwordsDoNotMatch=Les 2 mots de passe entrés ne correspondent pas.
password.forgotPassword=Mot de passe oublié? password.forgotPassword=Mot de passe oublié?
password.backupReminder=Veuillez noter que lors de la définition d'un mot de passe de portefeuille, toutes les sauvegardes créées automatiquement à partir du portefeuille non crypté seront supprimées.\n\nIl est fortement recommandé de faire une sauvegarde du répertoire de l'application et d'écrire les mots source avant de définir un mot de passe! password.backupReminder=Veuillez noter que lors de la définition d'un mot de passe de portefeuille, toutes les sauvegardes créées automatiquement à partir du portefeuille non crypté seront supprimées.\n\nIl est fortement recommandé de faire une sauvegarde du répertoire de l'application et d'écrire les mots source avant de définir un mot de passe!

View File

@ -986,6 +986,7 @@ setting.preferences.general=Preferenze generali
setting.preferences.explorer=Monero Explorer setting.preferences.explorer=Monero Explorer
setting.preferences.deviation=Deviazione massima del prezzo di mercato setting.preferences.deviation=Deviazione massima del prezzo di mercato
setting.preferences.avoidStandbyMode=Evita modalità standby setting.preferences.avoidStandbyMode=Evita modalità standby
setting.preferences.useSoundForNotifications=Riproduci suoni per le notifiche
setting.preferences.autoConfirmXMR=XMR auto-confirm setting.preferences.autoConfirmXMR=XMR auto-confirm
setting.preferences.autoConfirmEnabled=Enabled setting.preferences.autoConfirmEnabled=Enabled
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations setting.preferences.autoConfirmRequiredConfirmations=Required confirmations
@ -1500,8 +1501,10 @@ tradeDetailsWindow.agentAddresses=Arbitro/Mediatore
tradeDetailsWindow.detailData=Detail data tradeDetailsWindow.detailData=Detail data
txDetailsWindow.headline=Transaction Details txDetailsWindow.headline=Transaction Details
txDetailsWindow.xmr.note=You have sent XMR. txDetailsWindow.xmr.noteSent=Hai inviato XMR.
txDetailsWindow.sentTo=Sent to txDetailsWindow.xmr.noteReceived=Hai ricevuto XMR.
txDetailsWindow.sentTo=Inviato a
txDetailsWindow.receivedWith=Ricevuto con
txDetailsWindow.txId=TxId txDetailsWindow.txId=TxId
closedTradesSummaryWindow.headline=Trade history summary closedTradesSummaryWindow.headline=Trade history summary
@ -1841,7 +1844,6 @@ password.deriveKey=Deriva la chiave dalla password
password.walletDecrypted=Portafoglio decodificato correttamente e protezione con password rimossa. password.walletDecrypted=Portafoglio decodificato correttamente e protezione con password rimossa.
password.wrongPw=Hai inserito la password errata.\n\nProva a inserire nuovamente la password, verificando attentamente errori di battitura o errori di ortografia. password.wrongPw=Hai inserito la password errata.\n\nProva a inserire nuovamente la password, verificando attentamente errori di battitura o errori di ortografia.
password.walletEncrypted=Portafoglio crittografato correttamente e protezione con password abilitata. password.walletEncrypted=Portafoglio crittografato correttamente e protezione con password abilitata.
password.walletEncryptionFailed=Wallet password could not be set. You may have imported seed words which do not match the wallet database. Please contact the developers on Keybase ([HYPERLINK:https://keybase.io/team/haveno]).
password.passwordsDoNotMatch=Le 2 password inserite non corrispondono. password.passwordsDoNotMatch=Le 2 password inserite non corrispondono.
password.forgotPassword=Password dimenticata? password.forgotPassword=Password dimenticata?
password.backupReminder=Please note that when setting a wallet password all automatically created backups from the unencrypted wallet will be deleted.\n\nIt is highly recommended that you make a backup of the application directory and write down your seed words before setting a password! password.backupReminder=Please note that when setting a wallet password all automatically created backups from the unencrypted wallet will be deleted.\n\nIt is highly recommended that you make a backup of the application directory and write down your seed words before setting a password!

View File

@ -987,6 +987,7 @@ setting.preferences.general=一般設定
setting.preferences.explorer=ビットコインのエクスプローラ setting.preferences.explorer=ビットコインのエクスプローラ
setting.preferences.deviation=市場価格からの最大偏差 setting.preferences.deviation=市場価格からの最大偏差
setting.preferences.avoidStandbyMode=スタンバイモードを避ける setting.preferences.avoidStandbyMode=スタンバイモードを避ける
setting.preferences.useSoundForNotifications=通知音の再生
setting.preferences.autoConfirmXMR=XMR自動確認 setting.preferences.autoConfirmXMR=XMR自動確認
setting.preferences.autoConfirmEnabled=有効されました setting.preferences.autoConfirmEnabled=有効されました
setting.preferences.autoConfirmRequiredConfirmations=必要承認 setting.preferences.autoConfirmRequiredConfirmations=必要承認
@ -1501,8 +1502,10 @@ tradeDetailsWindow.agentAddresses=仲裁者 / 調停人
tradeDetailsWindow.detailData=詳細データ tradeDetailsWindow.detailData=詳細データ
txDetailsWindow.headline=トランザクション詳細 txDetailsWindow.headline=トランザクション詳細
txDetailsWindow.xmr.note=XMRを送金しました。 txDetailsWindow.xmr.noteSent=XMRを送金しました。
txDetailsWindow.xmr.noteReceived=XMRを受け取りました。
txDetailsWindow.sentTo=送信先 txDetailsWindow.sentTo=送信先
txDetailsWindow.receivedWith=受け取りました。
txDetailsWindow.txId=TxId txDetailsWindow.txId=TxId
closedTradesSummaryWindow.headline=Trade history summary closedTradesSummaryWindow.headline=Trade history summary
@ -1844,7 +1847,6 @@ password.deriveKey=パスワードから鍵を引き出す
password.walletDecrypted=ウォレットは正常に復号化され、パスワード保護が解除されました。 password.walletDecrypted=ウォレットは正常に復号化され、パスワード保護が解除されました。
password.wrongPw=間違ったパスワードを入力しています。\n\nパスワードをもう一度入力してみてください。入力ミスやスペルミスがないか慎重に確認してください。 password.wrongPw=間違ったパスワードを入力しています。\n\nパスワードをもう一度入力してみてください。入力ミスやスペルミスがないか慎重に確認してください。
password.walletEncrypted=ウォレットは正常に暗号化され、パスワード保護が有効になりました。 password.walletEncrypted=ウォレットは正常に暗号化され、パスワード保護が有効になりました。
password.walletEncryptionFailed=ウォレットのパスワードを設定できませんでした。ウォレットデータベースと一致しないシードワードをインポートした可能性があります。Keybase ([HYPERLINK:https://keybase.io/team/haveno]) で開発者と連絡して下さい。
password.passwordsDoNotMatch=入力した2つのパスワードが一致しません。 password.passwordsDoNotMatch=入力した2つのパスワードが一致しません。
password.forgotPassword=パスワードを忘れましたか? password.forgotPassword=パスワードを忘れましたか?
password.backupReminder=ウォレットパスワードを設定すると、暗号化されていないウォレットから自動的に作成されたすべてのバックアップが削除されます。\n\nパスワードを設定する前に、アプリケーションディレクトリのバックアップを作成してシードワードを書き留めておくことを強く推奨します。 password.backupReminder=ウォレットパスワードを設定すると、暗号化されていないウォレットから自動的に作成されたすべてのバックアップが削除されます。\n\nパスワードを設定する前に、アプリケーションディレクトリのバックアップを作成してシードワードを書き留めておくことを強く推奨します。

View File

@ -988,6 +988,7 @@ setting.preferences.general=Preferências gerais
setting.preferences.explorer=Monero Explorer setting.preferences.explorer=Monero Explorer
setting.preferences.deviation=Desvio máx. do preço do mercado setting.preferences.deviation=Desvio máx. do preço do mercado
setting.preferences.avoidStandbyMode=Impedir modo de economia de energia setting.preferences.avoidStandbyMode=Impedir modo de economia de energia
setting.preferences.useSoundForNotifications=Reproduzir sons para notificações
setting.preferences.autoConfirmXMR=XMR auto-confirm setting.preferences.autoConfirmXMR=XMR auto-confirm
setting.preferences.autoConfirmEnabled=Enabled setting.preferences.autoConfirmEnabled=Enabled
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations setting.preferences.autoConfirmRequiredConfirmations=Required confirmations
@ -1504,8 +1505,10 @@ tradeDetailsWindow.agentAddresses=Árbitro/Mediador
tradeDetailsWindow.detailData=Detail data tradeDetailsWindow.detailData=Detail data
txDetailsWindow.headline=Transaction Details txDetailsWindow.headline=Transaction Details
txDetailsWindow.xmr.note=You have sent XMR. txDetailsWindow.xmr.noteSent=Você enviou XMR.
txDetailsWindow.sentTo=Sent to txDetailsWindow.xmr.noteReceived=Você recebeu XMR.
txDetailsWindow.sentTo=Enviado para
txDetailsWindow.receivedWith=Recebido com
txDetailsWindow.txId=TxId txDetailsWindow.txId=TxId
closedTradesSummaryWindow.headline=Trade history summary closedTradesSummaryWindow.headline=Trade history summary
@ -1848,7 +1851,6 @@ password.deriveKey=Derivando chave a partir da senha
password.walletDecrypted=A carteira foi decifrada com sucesso e a proteção por senha removida password.walletDecrypted=A carteira foi decifrada com sucesso e a proteção por senha removida
password.wrongPw=Você digitou a senha incorreta.\n\nFavor tentar novamente, verificando com cuidado erros de digitação ou ortografia. password.wrongPw=Você digitou a senha incorreta.\n\nFavor tentar novamente, verificando com cuidado erros de digitação ou ortografia.
password.walletEncrypted=A carteira foi encriptada e a proteção por senha foi ativada com sucesso. password.walletEncrypted=A carteira foi encriptada e a proteção por senha foi ativada com sucesso.
password.walletEncryptionFailed=Wallet password could not be set. You may have imported seed words which do not match the wallet database. Please contact the developers on Keybase ([HYPERLINK:https://keybase.io/team/haveno]).
password.passwordsDoNotMatch=As 2 senhas inseridas não são iguais. password.passwordsDoNotMatch=As 2 senhas inseridas não são iguais.
password.forgotPassword=Esqueceu a senha? password.forgotPassword=Esqueceu a senha?
password.backupReminder=Please note that when setting a wallet password all automatically created backups from the unencrypted wallet will be deleted.\n\nIt is highly recommended that you make a backup of the application directory and write down your seed words before setting a password! password.backupReminder=Please note that when setting a wallet password all automatically created backups from the unencrypted wallet will be deleted.\n\nIt is highly recommended that you make a backup of the application directory and write down your seed words before setting a password!

View File

@ -985,6 +985,7 @@ setting.preferences.general=Preferências gerais
setting.preferences.explorer=Monero Explorer setting.preferences.explorer=Monero Explorer
setting.preferences.deviation=Máx. desvio do preço de mercado setting.preferences.deviation=Máx. desvio do preço de mercado
setting.preferences.avoidStandbyMode=Evite o modo espera setting.preferences.avoidStandbyMode=Evite o modo espera
setting.preferences.useSoundForNotifications=Reproduzir sons para notificações
setting.preferences.autoConfirmXMR=XMR auto-confirm setting.preferences.autoConfirmXMR=XMR auto-confirm
setting.preferences.autoConfirmEnabled=Enabled setting.preferences.autoConfirmEnabled=Enabled
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations setting.preferences.autoConfirmRequiredConfirmations=Required confirmations
@ -1497,8 +1498,10 @@ tradeDetailsWindow.agentAddresses=Árbitro/Mediador
tradeDetailsWindow.detailData=Detail data tradeDetailsWindow.detailData=Detail data
txDetailsWindow.headline=Transaction Details txDetailsWindow.headline=Transaction Details
txDetailsWindow.xmr.note=You have sent XMR. txDetailsWindow.xmr.noteSent=Você enviou XMR.
txDetailsWindow.sentTo=Sent to txDetailsWindow.xmr.noteReceived=Você recebeu XMR.
txDetailsWindow.sentTo=Enviado para
txDetailsWindow.receivedWith=Recebido com
txDetailsWindow.txId=TxId txDetailsWindow.txId=TxId
closedTradesSummaryWindow.headline=Trade history summary closedTradesSummaryWindow.headline=Trade history summary
@ -1838,7 +1841,6 @@ password.deriveKey=Derivar chave a partir da senha
password.walletDecrypted=A carteira foi descriptografada com sucesso e a proteção por senha removida. password.walletDecrypted=A carteira foi descriptografada com sucesso e a proteção por senha removida.
password.wrongPw=Você digitou a senha errada.\n\nPor favor, tente digitar sua senha novamente, verificando com atenção se há erros de ortografia. password.wrongPw=Você digitou a senha errada.\n\nPor favor, tente digitar sua senha novamente, verificando com atenção se há erros de ortografia.
password.walletEncrypted=Carteira encriptada com sucesso e proteção por senha ativada. password.walletEncrypted=Carteira encriptada com sucesso e proteção por senha ativada.
password.walletEncryptionFailed=Wallet password could not be set. You may have imported seed words which do not match the wallet database. Please contact the developers on Keybase ([HYPERLINK:https://keybase.io/team/haveno]).
password.passwordsDoNotMatch=As 2 senhas inseridas não são iguais. password.passwordsDoNotMatch=As 2 senhas inseridas não são iguais.
password.forgotPassword=Esqueceu a senha? password.forgotPassword=Esqueceu a senha?
password.backupReminder=Please note that when setting a wallet password all automatically created backups from the unencrypted wallet will be deleted.\n\nIt is highly recommended that you make a backup of the application directory and write down your seed words before setting a password! password.backupReminder=Please note that when setting a wallet password all automatically created backups from the unencrypted wallet will be deleted.\n\nIt is highly recommended that you make a backup of the application directory and write down your seed words before setting a password!

View File

@ -984,6 +984,7 @@ setting.preferences.general=Основные настройки
setting.preferences.explorer=Monero Explorer setting.preferences.explorer=Monero Explorer
setting.preferences.deviation=Макс. отклонение от рыночного курса setting.preferences.deviation=Макс. отклонение от рыночного курса
setting.preferences.avoidStandbyMode=Избегать режима ожидания setting.preferences.avoidStandbyMode=Избегать режима ожидания
setting.preferences.useSoundForNotifications=Воспроизводить звуки для уведомлений
setting.preferences.autoConfirmXMR=XMR auto-confirm setting.preferences.autoConfirmXMR=XMR auto-confirm
setting.preferences.autoConfirmEnabled=Enabled setting.preferences.autoConfirmEnabled=Enabled
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations setting.preferences.autoConfirmRequiredConfirmations=Required confirmations
@ -1498,8 +1499,10 @@ tradeDetailsWindow.agentAddresses=Arbitrator/Mediator
tradeDetailsWindow.detailData=Detail data tradeDetailsWindow.detailData=Detail data
txDetailsWindow.headline=Transaction Details txDetailsWindow.headline=Transaction Details
txDetailsWindow.xmr.note=You have sent XMR. txDetailsWindow.xmr.noteSent=Вы отправили XMR.
txDetailsWindow.sentTo=Sent to txDetailsWindow.xmr.noteReceived=Вы получили XMR.
txDetailsWindow.sentTo=Отправлено в
txDetailsWindow.receivedWith=Получено с
txDetailsWindow.txId=TxId txDetailsWindow.txId=TxId
closedTradesSummaryWindow.headline=Trade history summary closedTradesSummaryWindow.headline=Trade history summary
@ -1839,7 +1842,6 @@ password.deriveKey=Извлечь ключ из пароля
password.walletDecrypted=Кошелёк успешно расшифрован, защита паролем удалена. password.walletDecrypted=Кошелёк успешно расшифрован, защита паролем удалена.
password.wrongPw=Вы ввели неверный пароль.\n\nПопробуйте снова, обратив внимание на возможные ошибки ввода. password.wrongPw=Вы ввели неверный пароль.\n\nПопробуйте снова, обратив внимание на возможные ошибки ввода.
password.walletEncrypted=Кошелёк успешно зашифрован, защита паролем включена. password.walletEncrypted=Кошелёк успешно зашифрован, защита паролем включена.
password.walletEncryptionFailed=Wallet password could not be set. You may have imported seed words which do not match the wallet database. Please contact the developers on Keybase ([HYPERLINK:https://keybase.io/team/haveno]).
password.passwordsDoNotMatch=Введённые вами 2 пароля не совпадают. password.passwordsDoNotMatch=Введённые вами 2 пароля не совпадают.
password.forgotPassword=Забыли пароль? password.forgotPassword=Забыли пароль?
password.backupReminder=Please note that when setting a wallet password all automatically created backups from the unencrypted wallet will be deleted.\n\nIt is highly recommended that you make a backup of the application directory and write down your seed words before setting a password! password.backupReminder=Please note that when setting a wallet password all automatically created backups from the unencrypted wallet will be deleted.\n\nIt is highly recommended that you make a backup of the application directory and write down your seed words before setting a password!

View File

@ -984,6 +984,7 @@ setting.preferences.general=การตั้งค่าทั่วไป
setting.preferences.explorer=Monero Explorer setting.preferences.explorer=Monero Explorer
setting.preferences.deviation=สูงสุด ส่วนเบี่ยงเบนจากราคาตลาด setting.preferences.deviation=สูงสุด ส่วนเบี่ยงเบนจากราคาตลาด
setting.preferences.avoidStandbyMode=หลีกเลี่ยงโหมดแสตนบายด์ setting.preferences.avoidStandbyMode=หลีกเลี่ยงโหมดแสตนบายด์
setting.preferences.useSoundForNotifications=เล่นเสียงสำหรับการแจ้งเตือน
setting.preferences.autoConfirmXMR=XMR auto-confirm setting.preferences.autoConfirmXMR=XMR auto-confirm
setting.preferences.autoConfirmEnabled=Enabled setting.preferences.autoConfirmEnabled=Enabled
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations setting.preferences.autoConfirmRequiredConfirmations=Required confirmations
@ -1498,8 +1499,10 @@ tradeDetailsWindow.agentAddresses=Arbitrator/Mediator
tradeDetailsWindow.detailData=Detail data tradeDetailsWindow.detailData=Detail data
txDetailsWindow.headline=Transaction Details txDetailsWindow.headline=Transaction Details
txDetailsWindow.xmr.note=You have sent XMR. txDetailsWindow.xmr.noteSent=คุณได้ส่ง XMR แล้ว
txDetailsWindow.sentTo=Sent to txDetailsWindow.xmr.noteReceived=คุณได้รับ XMR แล้ว
txDetailsWindow.sentTo=ส่งไปยัง
txDetailsWindow.receivedWith=ได้รับด้วย
txDetailsWindow.txId=TxId txDetailsWindow.txId=TxId
closedTradesSummaryWindow.headline=Trade history summary closedTradesSummaryWindow.headline=Trade history summary
@ -1839,7 +1842,6 @@ password.deriveKey=ดึงข้อมูลจากรหัสผ่าน
password.walletDecrypted=กระเป๋าสตางค์ถูกถอดรหัสสำเร็จและการป้องกันรหัสผ่านได้มีการออกแล้ว password.walletDecrypted=กระเป๋าสตางค์ถูกถอดรหัสสำเร็จและการป้องกันรหัสผ่านได้มีการออกแล้ว
password.wrongPw=คุณป้อนรหัสผ่านไม่ถูกต้อง\n\nโปรดลองป้อนรหัสผ่านอีกครั้งโดยละเอียด เพื่อตรวจสอบความผิดพลาดในการพิมพ์หรือสะกด password.wrongPw=คุณป้อนรหัสผ่านไม่ถูกต้อง\n\nโปรดลองป้อนรหัสผ่านอีกครั้งโดยละเอียด เพื่อตรวจสอบความผิดพลาดในการพิมพ์หรือสะกด
password.walletEncrypted=เปิดใช้งานกระเป๋าสตางค์ที่เข้ารหัสแล้วและเปิดใช้งานการป้องกันด้วยรหัสผ่านแล้ว password.walletEncrypted=เปิดใช้งานกระเป๋าสตางค์ที่เข้ารหัสแล้วและเปิดใช้งานการป้องกันด้วยรหัสผ่านแล้ว
password.walletEncryptionFailed=Wallet password could not be set. You may have imported seed words which do not match the wallet database. Please contact the developers on Keybase ([HYPERLINK:https://keybase.io/team/haveno]).
password.passwordsDoNotMatch=รหัสผ่าน 2 รายการที่คุณป้อนไม่ตรงกัน password.passwordsDoNotMatch=รหัสผ่าน 2 รายการที่คุณป้อนไม่ตรงกัน
password.forgotPassword=ลืมรหัสผ่านหรือเปล่า? password.forgotPassword=ลืมรหัสผ่านหรือเปล่า?
password.backupReminder=Please note that when setting a wallet password all automatically created backups from the unencrypted wallet will be deleted.\n\nIt is highly recommended that you make a backup of the application directory and write down your seed words before setting a password! password.backupReminder=Please note that when setting a wallet password all automatically created backups from the unencrypted wallet will be deleted.\n\nIt is highly recommended that you make a backup of the application directory and write down your seed words before setting a password!

View File

@ -1261,6 +1261,7 @@ setting.preferences.general=Genel tercihler
setting.preferences.explorer=Monero Gezgini setting.preferences.explorer=Monero Gezgini
setting.preferences.deviation=Piyasa fiyatından maksimum sapma setting.preferences.deviation=Piyasa fiyatından maksimum sapma
setting.preferences.avoidStandbyMode=Bekleme modundan kaçın setting.preferences.avoidStandbyMode=Bekleme modundan kaçın
setting.preferences.useSoundForNotifications=Bildirimler için sesleri çal
setting.preferences.autoConfirmXMR=XMR otomatik onay setting.preferences.autoConfirmXMR=XMR otomatik onay
setting.preferences.autoConfirmEnabled=Etkin setting.preferences.autoConfirmEnabled=Etkin
setting.preferences.autoConfirmRequiredConfirmations=Gerekli onaylar setting.preferences.autoConfirmRequiredConfirmations=Gerekli onaylar
@ -2015,8 +2016,10 @@ tradeDetailsWindow.agentAddresses=Hakem/Arabulucu
tradeDetailsWindow.detailData=Detay verileri tradeDetailsWindow.detailData=Detay verileri
txDetailsWindow.headline=İşlem Detayları txDetailsWindow.headline=İşlem Detayları
txDetailsWindow.xmr.note=XMR gönderdiniz. txDetailsWindow.xmr.noteSent=XMR gönderdiniz.
txDetailsWindow.xmr.noteReceived=XMR aldınız.
txDetailsWindow.sentTo=Gönderilen adres txDetailsWindow.sentTo=Gönderilen adres
txDetailsWindow.receivedWith=Alındı ile
txDetailsWindow.txId=İşlem Kimliği (TxId) txDetailsWindow.txId=İşlem Kimliği (TxId)
closedTradesSummaryWindow.headline=Ticaret geçmişi özeti closedTradesSummaryWindow.headline=Ticaret geçmişi özeti
@ -2437,7 +2440,6 @@ password.deriveKey=Şifreden anahtar türet
password.walletDecrypted=Cüzdan başarıyla şifresiz hale getirildi ve şifre koruması kaldırıldı. password.walletDecrypted=Cüzdan başarıyla şifresiz hale getirildi ve şifre koruması kaldırıldı.
password.wrongPw=Yanlış şifre girdiniz.\n\nLütfen şifrenizi dikkatlice kontrol ederek tekrar girin, yazım hatalarına dikkat edin. password.wrongPw=Yanlış şifre girdiniz.\n\nLütfen şifrenizi dikkatlice kontrol ederek tekrar girin, yazım hatalarına dikkat edin.
password.walletEncrypted=Cüzdan başarıyla şifrelendi ve şifre koruması etkinleştirildi. password.walletEncrypted=Cüzdan başarıyla şifrelendi ve şifre koruması etkinleştirildi.
password.walletEncryptionFailed=Cüzdan şifresi ayarlanamadı. Seed kelimeleriniz cüzdan veritabanıyla eşleşmeyebilir. Lütfen Keybase'deki geliştiricilerle iletişime geçin ([HYPERLINK:https://keybase.io/team/haveno]).
password.passwordsDoNotMatch=Girdiğiniz iki şifre eşleşmiyor. password.passwordsDoNotMatch=Girdiğiniz iki şifre eşleşmiyor.
password.forgotPassword=Şifrenizi mi unuttunuz? password.forgotPassword=Şifrenizi mi unuttunuz?
password.backupReminder=Şifre ayarlarken tüm otomatik olarak oluşturulan şifresiz cüzdan yedekleri silinecektir.\n\n\ password.backupReminder=Şifre ayarlarken tüm otomatik olarak oluşturulan şifresiz cüzdan yedekleri silinecektir.\n\n\

View File

@ -986,6 +986,7 @@ setting.preferences.general=Tham khảo chung
setting.preferences.explorer=Monero Explorer setting.preferences.explorer=Monero Explorer
setting.preferences.deviation=Sai lệch tối đa so với giá thị trường setting.preferences.deviation=Sai lệch tối đa so với giá thị trường
setting.preferences.avoidStandbyMode=Tránh để chế độ chờ setting.preferences.avoidStandbyMode=Tránh để chế độ chờ
setting.preferences.useSoundForNotifications=Phát âm thanh cho thông báo
setting.preferences.autoConfirmXMR=XMR auto-confirm setting.preferences.autoConfirmXMR=XMR auto-confirm
setting.preferences.autoConfirmEnabled=Enabled setting.preferences.autoConfirmEnabled=Enabled
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations setting.preferences.autoConfirmRequiredConfirmations=Required confirmations
@ -1500,8 +1501,10 @@ tradeDetailsWindow.agentAddresses=Arbitrator/Mediator
tradeDetailsWindow.detailData=Detail data tradeDetailsWindow.detailData=Detail data
txDetailsWindow.headline=Transaction Details txDetailsWindow.headline=Transaction Details
txDetailsWindow.xmr.note=You have sent XMR. txDetailsWindow.xmr.noteSent=Bạn đã gửi XMR.
txDetailsWindow.sentTo=Sent to txDetailsWindow.xmr.noteReceived=Bạn đã nhận được XMR.
txDetailsWindow.sentTo=Gửi đến
txDetailsWindow.receivedWith=Đã nhận với
txDetailsWindow.txId=TxId txDetailsWindow.txId=TxId
closedTradesSummaryWindow.headline=Trade history summary closedTradesSummaryWindow.headline=Trade history summary
@ -1841,7 +1844,6 @@ password.deriveKey=Lấy khóa từ mật khẩu
password.walletDecrypted=Ví đã giải mã thành công và bảo vệ bằng mật khẩu bị gỡ bỏ. password.walletDecrypted=Ví đã giải mã thành công và bảo vệ bằng mật khẩu bị gỡ bỏ.
password.wrongPw=Bạn nhập sai mật khẩu.\n\nVui lòng nhập lại mật khẩu, kiểm tra lỗi do gõ phí hoặc lỗi chính tả cẩn thận. password.wrongPw=Bạn nhập sai mật khẩu.\n\nVui lòng nhập lại mật khẩu, kiểm tra lỗi do gõ phí hoặc lỗi chính tả cẩn thận.
password.walletEncrypted=Ví đã được mã hóa thành công và bảo vệ bằng mật khẩu được kích hoạt. password.walletEncrypted=Ví đã được mã hóa thành công và bảo vệ bằng mật khẩu được kích hoạt.
password.walletEncryptionFailed=Wallet password could not be set. You may have imported seed words which do not match the wallet database. Please contact the developers on Keybase ([HYPERLINK:https://keybase.io/team/haveno]).
password.passwordsDoNotMatch=2 mật khẩu bạn nhập không khớp. password.passwordsDoNotMatch=2 mật khẩu bạn nhập không khớp.
password.forgotPassword=Quên mật khẩu? password.forgotPassword=Quên mật khẩu?
password.backupReminder=Please note that when setting a wallet password all automatically created backups from the unencrypted wallet will be deleted.\n\nIt is highly recommended that you make a backup of the application directory and write down your seed words before setting a password! password.backupReminder=Please note that when setting a wallet password all automatically created backups from the unencrypted wallet will be deleted.\n\nIt is highly recommended that you make a backup of the application directory and write down your seed words before setting a password!

View File

@ -987,6 +987,7 @@ setting.preferences.general=通用偏好
setting.preferences.explorer=比特币区块浏览器 setting.preferences.explorer=比特币区块浏览器
setting.preferences.deviation=与市场价格最大差价 setting.preferences.deviation=与市场价格最大差价
setting.preferences.avoidStandbyMode=避免待机模式 setting.preferences.avoidStandbyMode=避免待机模式
setting.preferences.useSoundForNotifications=播放通知声音
setting.preferences.autoConfirmXMR=XMR 自动确认 setting.preferences.autoConfirmXMR=XMR 自动确认
setting.preferences.autoConfirmEnabled=启用 setting.preferences.autoConfirmEnabled=启用
setting.preferences.autoConfirmRequiredConfirmations=已要求确认 setting.preferences.autoConfirmRequiredConfirmations=已要求确认
@ -1502,8 +1503,10 @@ tradeDetailsWindow.agentAddresses=仲裁员/调解员
tradeDetailsWindow.detailData=详情数据 tradeDetailsWindow.detailData=详情数据
txDetailsWindow.headline=Transaction Details txDetailsWindow.headline=Transaction Details
txDetailsWindow.xmr.note=You have sent XMR. txDetailsWindow.xmr.noteSent=您已发送 XMR。
txDetailsWindow.sentTo=Sent to txDetailsWindow.xmr.noteReceived=你已收到XMR。
txDetailsWindow.sentTo=发送至
txDetailsWindow.receivedWith=已收到,带有
txDetailsWindow.txId=TxId txDetailsWindow.txId=TxId
closedTradesSummaryWindow.headline=Trade history summary closedTradesSummaryWindow.headline=Trade history summary
@ -1848,7 +1851,6 @@ password.deriveKey=从密码中提取密钥
password.walletDecrypted=钱包成功解密并移除密码保护 password.walletDecrypted=钱包成功解密并移除密码保护
password.wrongPw=你输入了错误的密码。\n\n请再次尝试输入密码仔细检查拼写错误。 password.wrongPw=你输入了错误的密码。\n\n请再次尝试输入密码仔细检查拼写错误。
password.walletEncrypted=钱包成功加密并开启密码保护。 password.walletEncrypted=钱包成功加密并开启密码保护。
password.walletEncryptionFailed=无法设置钱包密码。您可能导入了与钱包数据库不匹配的还原密钥。请在 Keybase 上联系开发者https://keybase.io/team/haveno]
password.passwordsDoNotMatch=这2个密码您输入的不相同 password.passwordsDoNotMatch=这2个密码您输入的不相同
password.forgotPassword=忘记密码? password.forgotPassword=忘记密码?
password.backupReminder=请注意,设置钱包密码时,所有未加密的钱包的自动创建的备份将被删除。\n\n强烈建议您备份应用程序的目录并在设置密码之前记下您的还原密钥 password.backupReminder=请注意,设置钱包密码时,所有未加密的钱包的自动创建的备份将被删除。\n\n强烈建议您备份应用程序的目录并在设置密码之前记下您的还原密钥

View File

@ -987,6 +987,7 @@ setting.preferences.general=通用偏好
setting.preferences.explorer=比特幣區塊瀏覽器 setting.preferences.explorer=比特幣區塊瀏覽器
setting.preferences.deviation=與市場價格最大差價 setting.preferences.deviation=與市場價格最大差價
setting.preferences.avoidStandbyMode=避免待機模式 setting.preferences.avoidStandbyMode=避免待機模式
setting.preferences.useSoundForNotifications=播放通知音效
setting.preferences.autoConfirmXMR=XMR 自動確認 setting.preferences.autoConfirmXMR=XMR 自動確認
setting.preferences.autoConfirmEnabled=啟用 setting.preferences.autoConfirmEnabled=啟用
setting.preferences.autoConfirmRequiredConfirmations=已要求確認 setting.preferences.autoConfirmRequiredConfirmations=已要求確認
@ -1502,8 +1503,10 @@ tradeDetailsWindow.agentAddresses=仲裁員/調解員
tradeDetailsWindow.detailData=Detail data tradeDetailsWindow.detailData=Detail data
txDetailsWindow.headline=Transaction Details txDetailsWindow.headline=Transaction Details
txDetailsWindow.xmr.note=You have sent XMR. txDetailsWindow.xmr.noteSent=您已發送XMR。
txDetailsWindow.sentTo=Sent to txDetailsWindow.xmr.noteReceived=您已收到 XMR。
txDetailsWindow.sentTo=發送至
txDetailsWindow.receivedWith=已收到與
txDetailsWindow.txId=TxId txDetailsWindow.txId=TxId
closedTradesSummaryWindow.headline=Trade history summary closedTradesSummaryWindow.headline=Trade history summary
@ -1842,7 +1845,6 @@ password.deriveKey=從密碼中提取密鑰
password.walletDecrypted=錢包成功解密並移除密碼保護 password.walletDecrypted=錢包成功解密並移除密碼保護
password.wrongPw=你輸入了錯誤的密碼。\n\n請再次嘗試輸入密碼仔細檢查拼寫錯誤。 password.wrongPw=你輸入了錯誤的密碼。\n\n請再次嘗試輸入密碼仔細檢查拼寫錯誤。
password.walletEncrypted=錢包成功加密並開啟密碼保護。 password.walletEncrypted=錢包成功加密並開啟密碼保護。
password.walletEncryptionFailed=無法設置錢包密碼。您可能導入了與錢包數據庫不匹配的還原密鑰。請在 Keybase 上聯繫開發者https://keybase.io/team/haveno]
password.passwordsDoNotMatch=這2個密碼您輸入的不相同 password.passwordsDoNotMatch=這2個密碼您輸入的不相同
password.forgotPassword=忘記密碼? password.forgotPassword=忘記密碼?
password.backupReminder=請注意,設置錢包密碼時,所有未加密的錢包的自動創建的備份將被刪除。\n\n強烈建議您備份應用程序的目錄並在設置密碼之前記下您的還原密鑰 password.backupReminder=請注意,設置錢包密碼時,所有未加密的錢包的自動創建的備份將被刪除。\n\n強烈建議您備份應用程序的目錄並在設置密碼之前記下您的還原密鑰

View File

@ -58,7 +58,7 @@ public class PreferencesTest {
Config config = new Config(); Config config = new Config();
XmrLocalNode xmrLocalNode = new XmrLocalNode(config, preferences); XmrLocalNode xmrLocalNode = new XmrLocalNode(config, preferences);
preferences = new Preferences( preferences = new Preferences(
persistenceManager, config, null); persistenceManager, config, null, null);
} }
@Test @Test

View File

@ -45,6 +45,8 @@ import haveno.proto.grpc.CheckConnectionReply;
import haveno.proto.grpc.CheckConnectionRequest; import haveno.proto.grpc.CheckConnectionRequest;
import haveno.proto.grpc.CheckConnectionsReply; import haveno.proto.grpc.CheckConnectionsReply;
import haveno.proto.grpc.CheckConnectionsRequest; import haveno.proto.grpc.CheckConnectionsRequest;
import haveno.proto.grpc.GetAutoSwitchReply;
import haveno.proto.grpc.GetAutoSwitchRequest;
import haveno.proto.grpc.GetBestAvailableConnectionReply; import haveno.proto.grpc.GetBestAvailableConnectionReply;
import haveno.proto.grpc.GetBestAvailableConnectionRequest; import haveno.proto.grpc.GetBestAvailableConnectionRequest;
import haveno.proto.grpc.GetConnectionReply; import haveno.proto.grpc.GetConnectionReply;
@ -221,6 +223,16 @@ class GrpcXmrConnectionService extends XmrConnectionsImplBase {
}); });
} }
@Override
public void getAutoSwitch(GetAutoSwitchRequest request,
StreamObserver<GetAutoSwitchReply> responseObserver) {
handleRequest(responseObserver, () -> {
GetAutoSwitchReply.Builder builder = GetAutoSwitchReply.newBuilder();
builder.setAutoSwitch(coreApi.getXmrConnectionAutoSwitch());
return builder.build();
});
}
private <_Reply> void handleRequest(StreamObserver<_Reply> responseObserver, private <_Reply> void handleRequest(StreamObserver<_Reply> responseObserver,
RpcRequestHandler<_Reply> handler) { RpcRequestHandler<_Reply> handler) {
try { try {

View File

@ -3,18 +3,24 @@ Follow these instructions to create installers for the Haveno Java desktop appli
> **Note** > **Note**
> These steps will delete the previously built Haveno binaries, so they'll need rebuilt after. > These steps will delete the previously built Haveno binaries, so they'll need rebuilt after.
### Linux ## Linux
From x86_64 machine: From x86_64 machine:
1. `./gradlew clean build --refresh-keys --refresh-dependencies` (or `make clean && skip-tests` after refreshed) 1. `sudo apt-get update`
2. `./gradlew packageInstallers` 2. `sudo apt install -y rpm libfuse2 flatpak flatpak-builder appstream`
3. Confirm prompts. 3. `flatpak remote-add --if-not-exists --user flathub https://dl.flathub.org/repo/flathub.flatpakrepo`
4. Path to installer is printed at the end. Execute to install: `sudo dpkg -i <path>.deb` or open `<path>.deb` with Software Install. 4. `./gradlew clean build --refresh-keys --refresh-dependencies` (or `make clean && skip-tests` after refreshed)
5. `./gradlew packageInstallers`
6. Confirm prompts.
7. Path to installer is printed at the end. Execute to install, e.g.: `sudo dpkg -i <path>.deb` or open `<path>.deb` with Software Install.
Note: Please see [flatpak.md](../../docs/flatpak.md) for information on
distributing Haveno via Flatpak.
Haveno data folder on Linux: `/home/<username>/.local/share/Haveno/` Haveno data folder on Linux: `/home/<username>/.local/share/Haveno/`
### macOS ## macOS
From x86_64 machine: From x86_64 machine:
@ -29,7 +35,7 @@ From x86_64 machine:
Haveno data folder on Mac: `/Users/<username>/Library/Application Support/Haveno/` Haveno data folder on Mac: `/Users/<username>/Library/Application Support/Haveno/`
### Windows ## Windows
1. Enable .NET Framework 3.5: 1. Enable .NET Framework 3.5:
1. Open the Control Panel on your Windows system. 1. Open the Control Panel on your Windows system.
@ -40,7 +46,7 @@ Haveno data folder on Mac: `/Users/<username>/Library/Application Support/Haveno
6. Click "OK" to save the changes and exit the dialog box. 6. Click "OK" to save the changes and exit the dialog box.
7. Windows will download and install the required files and components to enable the .NET Framework 3.5. This may take several minutes, depending on your internet connection speed and system configuration. 7. Windows will download and install the required files and components to enable the .NET Framework 3.5. This may take several minutes, depending on your internet connection speed and system configuration.
8. Once the installation is complete, you will need to restart your computer to apply the changes. 8. Once the installation is complete, you will need to restart your computer to apply the changes.
2. Install Wix Toolset 3: https://github.com/wixtoolset/wix3/releases/tag/wix314rtm 2. Install Wix Toolset 3: <https://github.com/wixtoolset/wix3/releases/tag/wix314rtm>
3. Open MSYS2 for the following commands. 3. Open MSYS2 for the following commands.
4. `export PATH=$PATH:$JAVA_HOME/bin:"C:\Program Files (x86)\WiX Toolset v3.14\bin"` 4. `export PATH=$PATH:$JAVA_HOME/bin:"C:\Program Files (x86)\WiX Toolset v3.14\bin"`
5. `./gradlew clean build --refresh-keys --refresh-dependencies` (or `make clean && skip-tests` after refreshed) 5. `./gradlew clean build --refresh-keys --refresh-dependencies` (or `make clean && skip-tests` after refreshed)
@ -50,32 +56,32 @@ Haveno data folder on Mac: `/Users/<username>/Library/Application Support/Haveno
Haveno data folder on Windows: `~\AppData\Roaming\Haveno\` Haveno data folder on Windows: `~\AppData\Roaming\Haveno\`
## Copy installer and rebuild Haveno binaries ## Copying installer and rebuilding Haveno binaries
1. Copy the installer to a safe location because it will be deleted in the next step. 1. Copy the installer to a safe location because it will be deleted in the next step.
2. `make clean && make` (or `make clean && make skip-tests`) to rebuild Haveno apps. 2. `make clean && make` (or `make clean && make skip-tests`) to rebuild Haveno apps.
## Additional Notes ## Additional Notes
### Icons ### Icons
Icons (Haveno.zip) were obtained from https://github.com/haveno-dex/haveno-meta/issues/1#issuecomment-819741689. Icons (Haveno.zip) were obtained from <https://github.com/haveno-dex/haveno-meta/issues/1#issuecomment-819741689>.
#### Linux ### Building for Linux
The linux package requires the correct packaging tools installed. You may run into the following errors: The linux package requires the correct packaging tools installed. You may run into the following errors:
``` ```sh
Error: Invalid or unsupported type: [deb] Error: Invalid or unsupported type: [deb]
``` ```
```
```sh
Error: Invalid or unsupported type: [rpm] Error: Invalid or unsupported type: [rpm]
``` ```
On Ubuntu, resolve by running `sudo apt install rpm`. For deb, ensure dpkg is installed. On Ubuntu, resolve by running `sudo apt install rpm`. For deb, ensure dpkg is installed.
``` ```sh
Exception in thread "main" java.io.IOException: Failed to rename /tmp/Haveno-stripped15820156885694375398.tmp to /storage/src/haveno/desktop/build/libs/fatJar/desktop-1.0.0-SNAPSHOT-all.jar Exception in thread "main" java.io.IOException: Failed to rename /tmp/Haveno-stripped15820156885694375398.tmp to /storage/src/haveno/desktop/build/libs/fatJar/desktop-1.0.0-SNAPSHOT-all.jar
at haveno.tools.Utils.renameFile(Utils.java:36) at haveno.tools.Utils.renameFile(Utils.java:36)
at io.github.zlika.reproducible.StipZipFile.strip(StipZipFile.java:35) at io.github.zlika.reproducible.StipZipFile.strip(StipZipFile.java:35)
@ -85,20 +91,21 @@ Exception in thread "main" java.io.IOException: Failed to rename /tmp/Haveno-str
This may happen if the source folder is on a different hard drive than the system `tmp` folder. The tools-1.0.jar calls renameTo to rename the deterministic jar back to the fat jar location. You can temporarily change your temp directory on linux: This may happen if the source folder is on a different hard drive than the system `tmp` folder. The tools-1.0.jar calls renameTo to rename the deterministic jar back to the fat jar location. You can temporarily change your temp directory on linux:
``` ```sh
export _JAVA_OPTIONS="-Djava.io.tmpdir=/storage/tmp" export _JAVA_OPTIONS="-Djava.io.tmpdir=/storage/tmp"
``` ```
#### MacOs ### Building for macOS
Svg was converted into a 1024x1024 pixel PNG using https://webkul.github.io/myscale/, then converted to icns for macosx Svg was converted into a 1024x1024 pixel PNG using
here https://cloudconvert.com/png-to-icns <https://webkul.github.io/myscale/>, then converted to icns for macosx
here <https://cloudconvert.com/png-to-icns>
##### Known Issues #### Known Issues
Signing is not implemented. Signing is not implemented.
#### Windows ### Building for Windows
Pngs were resized and pasted into the WixUi images using paint. [CloudConvert](https://cloudconvert.com) was used to convert the Haveno png icon to ico. Pngs were resized and pasted into the WixUi images using paint. [CloudConvert](https://cloudconvert.com) was used to convert the Haveno png icon to ico.

View File

@ -0,0 +1 @@
../haveno.png

View File

@ -0,0 +1 @@
../Haveno.desktop

View File

@ -0,0 +1 @@
../haveno.svg

View File

@ -0,0 +1,14 @@
[Desktop Entry]
Comment=A decentralized, Tor-based, P2P Monero exchange network.
Exec=sh -c "PATH=\"\\$HOME/.local/bin:\\$PATH\"; bin/Haveno %u"
GenericName[en_US]=Monero Exchange
GenericName=Monero Exchange
Icon=haveno
Categories=Office;Finance;Java;P2P;
Name[en_US]=Haveno
Name=Haveno
Terminal=false
Type=Application
MimeType=
X-AppImage-Name=Haveno
StartupWMClass=Haveno

View File

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>exchange.haveno.Haveno</id>
<!-- <icon type="stock">exchange.haveno.Haveno</icon> -->
<launchable type="desktop-id">exchange.haveno.Haveno.desktop</launchable>
<name>Haveno</name>
<summary>Decentralized P2P exchange built on Monero and Tor</summary>
<categories>
<category>Office</category>
<category>Finance</category>
<category>P2P</category>
</categories>
<keywords>
<keyword>cryptocurrency</keyword>
<keyword>monero</keyword>
</keywords>
<metadata_license>CC-BY-4.0</metadata_license>
<project_license>AGPL-3.0-only</project_license>
<branding>
<color type="primary" scheme_preference="light">#e5a29f</color>
<color type="primary" scheme_preference="dark">#562c63</color>
</branding>
<supports>
<control>pointing</control>
<control>keyboard</control>
<control>touch</control>
</supports>
<description>
<p>Haveno (pronounced ha‧ve‧no) is a platform for people who want to exchange Monero for fiat currencies like EUR, GBP, and USD or other cryptocurrencies like BTC, ETH, and BCH.</p>
<ul>
<li>All communications are routed through Tor, to preserve your privacy
</li>
<li>Trades are peer-to-peer: trades on Haveno will happen between people only, there is no central authority.
</li>
<li>Trades are non-custodial: Haveno provides arbitration in case something goes wrong during the trade, but we will never have access to your funds.
</li>
<li>There is No token, because we don&#39;t need it. Transactions between traders are secured by non-custodial multisignature transactions on the Monero network.
</li>
</ul>
</description>
<screenshots>
<screenshot type="default">
<image>https://github.com/haveno-dex/haveno/blob/master/desktop/package/linux/preview.png</image>
<caption>Recent Trades page</caption>
</screenshot>
</screenshots>
<developer id="exchange.haveno">
<name>woodser</name>
</developer>
<url type="homepage">https://haveno.exchange</url>
<url type="bugtracker">https://github.com/haveno-dex/haveno/issues</url>
<content_rating type="oars-1.1">
<content_attribute id="social-chat">moderate</content_attribute>
<content_attribute id="social-info">moderate</content_attribute>
<content_attribute id="social-contacts">intense</content_attribute>
<content_attribute id="money-purchasing">intense</content_attribute>
</content_rating>
<launchable type="desktop-id">Haveno.desktop</launchable>
</component>

View File

@ -0,0 +1,52 @@
id: exchange.haveno.Haveno
runtime: org.freedesktop.Platform
runtime-version: "23.08"
sdk: org.freedesktop.Sdk
sdk-extensions:
- org.freedesktop.Sdk.Extension.openjdk21
command: /app/bin/Haveno
modules:
- name: openjdk
buildsystem: simple
build-commands:
- /usr/lib/sdk/openjdk21/install.sh
- name: Haveno
buildsystem: simple
sources:
# - type: git
# url: https://github.com/haveno-dex/haveno
- type: dir
path: build
- type: file
path: package/linux/Haveno.desktop
- type: file
path: package/linux/exchange.haveno.Haveno.metainfo.xml
- type: file
path: package/linux/icon.png
build-commands:
- ls
- pwd
# TODO: consider switching from reading from a deb to reading from jpackage's image
- mv temp-*/binaries/haveno_*.deb haveno.deb
- ar x haveno.deb
- tar xf data.tar.*
- cp -r opt/haveno/lib /app/lib
- install -D opt/haveno/bin/Haveno /app/bin/Haveno
- mkdir -p /app/share/icons/hicolor/128x128/apps/
- mkdir -p /app/share/applications/
- mkdir -p /app/share/metainfo/
- mv icon.png /app/share/icons/hicolor/128x128/apps/haveno.png
- mv Haveno.desktop /app/share/applications/exchange.haveno.Haveno.desktop
- mv exchange.haveno.Haveno.metainfo.xml /app/share/metainfo/
# TODO: xdg-open fails
finish-args:
- --env=PATH=/app/jre/bin:/usr/bin:$PATH
# - --env=JAVA_HOME=/app/jre
- --env=JAVA_HOME=/usr/lib/sdk/openjdk21/
- --device=dri
- --talk-name=org.freedesktop.Notifications
- --talk-name=org.freedesktop.secrets
- --share=network
- --share=ipc
- --socket=x11

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800"><defs><style>.cls-1{fill:#f1482d;}.cls-2,.cls-3{fill:#232075;}.cls-2{opacity:0.22;}.cls-3{opacity:0.44;}.cls-4{fill:#fff;}</style></defs><title>haveno_logo_icon</title><g id="Layer_1_copy" data-name="Layer 1 copy"><polygon class="cls-1" points="469.37 155.08 499.53 162.84 511.62 177.19 557.71 207.42 563.75 212.71 576.6 268.62 585.67 276.94 590.96 270.13 638.25 296.94 654.12 312.05 646.12 375.92 630.25 376.68 630.25 386.5 646.12 432.59 584.91 556.51 581.89 568.6 547.89 591.27 504.06 616.96 468.37 618.08 434.54 647.19 358.23 666.08 349.92 639.63 318.18 632.08 170.08 518.73 188.97 501.35 167.06 486.24 149.37 331.08 232.04 173.41 268.31 162.84 275.11 168.13 290.98 168.88 303.07 158.3 329.37 138.08 379.37 125.08 395.37 160.08 422.37 186.08 469.37 155.08"/><path class="cls-2" d="M510.93,216.49c24.31,7.8,36.09,30,57.63,72,23.3,45.44,35,68.16,26.9,88-9.52,23.41-38.88,31.5-35.86,48,1.51,8.27,9.51,9.62,11.52,20,2.61,13.4-7.81,26.12-11.52,30.66-28.3,34.58-68,16.05-103.74,49.32-12.79,11.91-6.63,13.27-32,44-22.74,27.53-34.11,41.29-48.66,44-28.25,5.24-58.53-24.33-73-49.32-18.74-32.38-4-45.06-21.77-78.65-20.61-38.91-46.78-33.88-61.47-70.64-10.55-26.37-6-51.37-2.57-70.65,3.21-17.82,13.66-75.79,52.51-94.64,37.83-18.37,56.84,22.57,110.14,8,33-9,41.66-29.07,89.65-38.65C486.59,214.25,497.55,212.2,510.93,216.49Z"/><path class="cls-3" d="M413.19,283c-32.8.14-104,.43-140.55,35.6-2.81,2.7-31,30.48-16.53,49.39,14,18.27,53.54,9.53,71.1,28.71,14.09,15.39-6,26.91-1.65,58.57,3.33,24.47,21.26,61.11,56.22,66.61,25.76,4.06,50.3-10.45,57.87-14.93,37.64-22.26,41.7-57.59,43-71.2,4.72-49.9-31.83-68.23-11.57-99.92,12.89-20.17,35.64-25.19,31.41-35.61C495.35,282.64,424.89,282.94,413.19,283Z"/><path class="cls-3" d="M342.76,336.08c6.17-12.18,41.43-22.94,66.07-14,14.5,5.26,22.66,16.39,20.94,26-2,11-4.24,13.62-9.77,24.92-7.46,15.25,13.11,19,15,40,1.67,18.59-23.39,40.09-32.62,40.08-60.38-.08.71-46.44-45.12-92C348.72,352.58,338.47,344.52,342.76,336.08Z"/></g><g id="Layer_4" data-name="Layer 4"><path class="cls-4" d="M354.58,380.91h94.33v-97h65.33V535.23H448.91v-103H354.58v103H289.26V283.92h65.32Z"/><circle class="cls-4" cx="402" cy="229" r="33"/></g></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 KiB

View File

@ -5,10 +5,10 @@
<!-- See: https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html --> <!-- See: https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -->
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.0.11</string> <string>1.0.12</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.0.11</string> <string>1.0.12</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>Haveno</string> <string>Haveno</string>

View File

@ -1,6 +1,7 @@
import org.apache.tools.ant.taskdefs.condition.Os import org.apache.tools.ant.taskdefs.condition.Os
import java.time.LocalDateTime import java.time.LocalDateTime
import java.util.regex.Pattern
task jpackageSanityChecks { task jpackageSanityChecks {
description 'Interactive sanity checks on the version of the code that will be packaged' description 'Interactive sanity checks on the version of the code that will be packaged'
@ -113,6 +114,8 @@ task getJavaBinariesDownloadURLs {
} }
} }
ext.osKey = osKey
ext.jdk21Binary_DownloadURL = jdk21Binaries[osKey] ext.jdk21Binary_DownloadURL = jdk21Binaries[osKey]
ext.jdk21Binary_SHA256Hash = jdk21Binaries[osKey + '-sha256'] ext.jdk21Binary_SHA256Hash = jdk21Binaries[osKey + '-sha256']
} }
@ -321,6 +324,69 @@ task packageInstallers {
" --linux-deb-maintainer noreply@haveno.exchange" + " --linux-deb-maintainer noreply@haveno.exchange" +
" --type deb") " --type deb")
// Clean jpackage temp folder, needs to be empty for the next packaging step (AppImage)
jpackageTempDir.deleteDir()
jpackageTempDir.mkdirs()
executeCmd(jPackageFilePath + commonOpts +
" --dest \"${jpackageTempDir}\"" +
" --type app-image")
// Path to the app-image directory: THIS IS NOT THE ACTUAL .AppImage FILE.
// See JPackage documentation on --type app-image for more.
String appImagePath = new String(
"\"${binariesFolderPath}/${appNameAndVendor}\""
)
// Which version of AppImageTool to use
String AppImageToolVersion = "13";
// Download AppImageTool
Map AppImageToolBinaries = [
'linux' : "https://github.com/AppImage/AppImageKit/releases/download/${AppImageToolVersion}/appimagetool-x86_64.AppImage",
'linux-aarch64' : "https://github.com/AppImage/AppImageKit/releases/download/${AppImageToolVersion}/appimagetool-aarch64.AppImage",
]
String osKey = getJavaBinariesDownloadURLs.property('osKey')
File appDir = new File("${jpackageTempDir}/Haveno")
File templateAppDir = new File("${project(':desktop').projectDir}/package/linux/Haveno.AppDir")
File jpackDir = appDir
appDir.mkdirs()
File AppImageToolBinary = new File("${jpackageTempDir}/appimagetool.AppImage")
// Adding a platform to the AppImageToolBinaries essentially adds it to the "supported" list of platforms able to make AppImages
// However, be warned that any platform that doesn't support unix `ln` and `chmod` will not work with the current method.
if (AppImageToolBinaries.containsKey(osKey)) {
println "Downloading ${AppImageToolBinaries[osKey]}"
ant.get(src: AppImageToolBinaries[osKey], dest: AppImageToolBinary)
println 'Download saved to ' + jpackageTempDir
project.exec {
commandLine('chmod', '+x', AppImageToolBinary)
}
copy {
from templateAppDir
into appDir
boolean includeEmptyDirs = true
}
project.exec {
workingDir appDir
commandLine 'ln', '-s', 'bin/Haveno', 'AppRun'
}
project.exec {
commandLine "${AppImageToolBinary}", appDir, "${binariesFolderPath}/haveno_${appVersion}.AppImage"
}
} else {
println "Your platform does not support AppImageTool ${AppImageToolVersion}"
}
// Clean jpackage temp folder, needs to be empty for the next packaging step (rpm) // Clean jpackage temp folder, needs to be empty for the next packaging step (rpm)
jpackageTempDir.deleteDir() jpackageTempDir.deleteDir()
jpackageTempDir.mkdirs() jpackageTempDir.mkdirs()
@ -329,6 +395,55 @@ task packageInstallers {
executeCmd(jPackageFilePath + commonOpts + linuxOpts + executeCmd(jPackageFilePath + commonOpts + linuxOpts +
" --linux-rpm-license-type AGPLv3" + // https://fedoraproject.org/wiki/Licensing:Main?rd=Licensing#Good_Licenses " --linux-rpm-license-type AGPLv3" + // https://fedoraproject.org/wiki/Licensing:Main?rd=Licensing#Good_Licenses
" --type rpm") " --type rpm")
// Define Flatpak-related properties
String flatpakManifestFile = 'package/linux/exchange.haveno.Haveno.yml'
String linuxDir = 'package/linux'
String flatpakOutputDir = 'package/linux/build'
String flatpakExportDir = "${binariesFolderPath}/fpexport"
String flatpakBundleFile = "${binariesFolderPath}/haveno.flatpak"
// Read the default app name from the HavenoExecutable.java file
def filer = file('../core/src/main/java/haveno/core/app/HavenoExecutable.java')
def content = filer.text
def matcher = Pattern.compile(/public static final String DEFAULT_APP_NAME = "(.*?)";/).matcher(content)
def defaultAppName = "Haveno"
if (matcher.find()) {
defaultAppName = matcher.group(1)
} else {
throw new GradleException("DEFAULT_APP_NAME not found in HavenoExecutable.java")
}
// Copy the manifest to a new tmp one in the same place
// and add a --filesystem=.local/share/${name} to the flatpak manifest
def manifest = file(flatpakManifestFile)
def newManifest = file('exchange.haveno.Haveno.yaml')
newManifest.write(manifest.text.replace("- --share=network", "- --share=network\n - --filesystem=~/.local/share/${defaultAppName}:create"))
flatpakManifestFile = 'exchange.haveno.Haveno.yaml'
// Command to build the Flatpak
exec {
commandLine 'flatpak-builder', '--force-clean', flatpakOutputDir, flatpakManifestFile, '--user', '--install-deps-from=flathub'
}
// Command to export the Flatpak
exec {
commandLine 'flatpak', 'build-export', flatpakExportDir, flatpakOutputDir
}
// Command to create the Flatpak bundle
exec {
commandLine 'flatpak', 'build-bundle', flatpakExportDir, flatpakBundleFile, 'exchange.haveno.Haveno', '--runtime-repo=https://flathub.org/repo/flathub.flatpakrepo'
}
// delete the flatpak build directory
delete(flatpakOutputDir)
delete(flatpakExportDir)
delete(flatpakManifestFile)
println "Flatpak package created at ${flatpakBundleFile}"
} }
// Env variable can be set by calling "export HAVENO_SHARED_FOLDER='Some value'" // Env variable can be set by calling "export HAVENO_SHARED_FOLDER='Some value'"
@ -345,6 +460,7 @@ task packageInstallers {
from binariesFolderPath from binariesFolderPath
into envVariableSharedFolder into envVariableSharedFolder
} }
executeCmd("open " + envVariableSharedFolder) executeCmd("open " + envVariableSharedFolder)
} }
} }

View File

@ -18,7 +18,6 @@
package haveno.desktop.main.account.content.cryptoaccounts; package haveno.desktop.main.account.content.cryptoaccounts;
import com.google.inject.Inject; import com.google.inject.Inject;
import haveno.common.crypto.KeyRing;
import haveno.common.file.CorruptedStorageFileHandler; import haveno.common.file.CorruptedStorageFileHandler;
import haveno.common.proto.persistable.PersistenceProtoResolver; import haveno.common.proto.persistable.PersistenceProtoResolver;
import haveno.core.account.witness.AccountAgeWitnessService; import haveno.core.account.witness.AccountAgeWitnessService;
@ -55,7 +54,6 @@ class CryptoAccountsDataModel extends ActivatableDataModel {
private final String accountsFileName = "CryptoPaymentAccounts"; private final String accountsFileName = "CryptoPaymentAccounts";
private final PersistenceProtoResolver persistenceProtoResolver; private final PersistenceProtoResolver persistenceProtoResolver;
private final CorruptedStorageFileHandler corruptedStorageFileHandler; private final CorruptedStorageFileHandler corruptedStorageFileHandler;
private final KeyRing keyRing;
@Inject @Inject
public CryptoAccountsDataModel(User user, public CryptoAccountsDataModel(User user,
@ -64,8 +62,7 @@ class CryptoAccountsDataModel extends ActivatableDataModel {
TradeManager tradeManager, TradeManager tradeManager,
AccountAgeWitnessService accountAgeWitnessService, AccountAgeWitnessService accountAgeWitnessService,
PersistenceProtoResolver persistenceProtoResolver, PersistenceProtoResolver persistenceProtoResolver,
CorruptedStorageFileHandler corruptedStorageFileHandler, CorruptedStorageFileHandler corruptedStorageFileHandler) {
KeyRing keyRing) {
this.user = user; this.user = user;
this.preferences = preferences; this.preferences = preferences;
this.openOfferManager = openOfferManager; this.openOfferManager = openOfferManager;
@ -73,7 +70,6 @@ class CryptoAccountsDataModel extends ActivatableDataModel {
this.accountAgeWitnessService = accountAgeWitnessService; this.accountAgeWitnessService = accountAgeWitnessService;
this.persistenceProtoResolver = persistenceProtoResolver; this.persistenceProtoResolver = persistenceProtoResolver;
this.corruptedStorageFileHandler = corruptedStorageFileHandler; this.corruptedStorageFileHandler = corruptedStorageFileHandler;
this.keyRing = keyRing;
setChangeListener = change -> fillAndSortPaymentAccounts(); setChangeListener = change -> fillAndSortPaymentAccounts();
} }
@ -157,12 +153,12 @@ class CryptoAccountsDataModel extends ActivatableDataModel {
ArrayList<PaymentAccount> accounts = new ArrayList<>(user.getPaymentAccounts().stream() ArrayList<PaymentAccount> accounts = new ArrayList<>(user.getPaymentAccounts().stream()
.filter(paymentAccount -> paymentAccount instanceof AssetAccount) .filter(paymentAccount -> paymentAccount instanceof AssetAccount)
.collect(Collectors.toList())); .collect(Collectors.toList()));
GUIUtil.exportAccounts(accounts, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler, keyRing); GUIUtil.exportAccounts(accounts, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler);
} }
} }
public void importAccounts(Stage stage) { public void importAccounts(Stage stage) {
GUIUtil.importAccounts(user, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler, keyRing); GUIUtil.importAccounts(user, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler);
} }
public int getNumPaymentAccounts() { public int getNumPaymentAccounts() {

View File

@ -39,6 +39,7 @@ import static haveno.desktop.util.FormBuilder.addButtonBusyAnimationLabel;
import static haveno.desktop.util.FormBuilder.addMultilineLabel; import static haveno.desktop.util.FormBuilder.addMultilineLabel;
import static haveno.desktop.util.FormBuilder.addPasswordTextField; import static haveno.desktop.util.FormBuilder.addPasswordTextField;
import static haveno.desktop.util.FormBuilder.addTitledGroupBg; import static haveno.desktop.util.FormBuilder.addTitledGroupBg;
import org.apache.commons.lang3.exception.ExceptionUtils;
import haveno.desktop.util.Layout; import haveno.desktop.util.Layout;
import haveno.desktop.util.validation.PasswordValidator; import haveno.desktop.util.validation.PasswordValidator;
import javafx.beans.value.ChangeListener; import javafx.beans.value.ChangeListener;
@ -157,8 +158,9 @@ public class PasswordView extends ActivatableView<GridPane, Void> {
backupWalletAndResetFields(); backupWalletAndResetFields();
walletsManager.clearBackup(); walletsManager.clearBackup();
} catch (Throwable t) { } catch (Throwable t) {
log.error("Error applying password: {}\n", t.getMessage(), t);
new Popup() new Popup()
.warning(Res.get("password.walletEncryptionFailed")) .warning(Res.get("password.walletEncryptionFailed") + "\n\n" + ExceptionUtils.getStackTrace(t))
.show(); .show();
} }
} }

View File

@ -18,7 +18,6 @@
package haveno.desktop.main.account.content.traditionalaccounts; package haveno.desktop.main.account.content.traditionalaccounts;
import com.google.inject.Inject; import com.google.inject.Inject;
import haveno.common.crypto.KeyRing;
import haveno.common.file.CorruptedStorageFileHandler; import haveno.common.file.CorruptedStorageFileHandler;
import haveno.common.proto.persistable.PersistenceProtoResolver; import haveno.common.proto.persistable.PersistenceProtoResolver;
import haveno.core.account.witness.AccountAgeWitnessService; import haveno.core.account.witness.AccountAgeWitnessService;
@ -56,7 +55,6 @@ class TraditionalAccountsDataModel extends ActivatableDataModel {
private final String accountsFileName = "FiatPaymentAccounts"; private final String accountsFileName = "FiatPaymentAccounts";
private final PersistenceProtoResolver persistenceProtoResolver; private final PersistenceProtoResolver persistenceProtoResolver;
private final CorruptedStorageFileHandler corruptedStorageFileHandler; private final CorruptedStorageFileHandler corruptedStorageFileHandler;
private final KeyRing keyRing;
@Inject @Inject
public TraditionalAccountsDataModel(User user, public TraditionalAccountsDataModel(User user,
@ -65,8 +63,7 @@ class TraditionalAccountsDataModel extends ActivatableDataModel {
TradeManager tradeManager, TradeManager tradeManager,
AccountAgeWitnessService accountAgeWitnessService, AccountAgeWitnessService accountAgeWitnessService,
PersistenceProtoResolver persistenceProtoResolver, PersistenceProtoResolver persistenceProtoResolver,
CorruptedStorageFileHandler corruptedStorageFileHandler, CorruptedStorageFileHandler corruptedStorageFileHandler) {
KeyRing keyRing) {
this.user = user; this.user = user;
this.preferences = preferences; this.preferences = preferences;
this.openOfferManager = openOfferManager; this.openOfferManager = openOfferManager;
@ -74,7 +71,6 @@ class TraditionalAccountsDataModel extends ActivatableDataModel {
this.accountAgeWitnessService = accountAgeWitnessService; this.accountAgeWitnessService = accountAgeWitnessService;
this.persistenceProtoResolver = persistenceProtoResolver; this.persistenceProtoResolver = persistenceProtoResolver;
this.corruptedStorageFileHandler = corruptedStorageFileHandler; this.corruptedStorageFileHandler = corruptedStorageFileHandler;
this.keyRing = keyRing;
setChangeListener = change -> fillAndSortPaymentAccounts(); setChangeListener = change -> fillAndSortPaymentAccounts();
} }
@ -159,12 +155,12 @@ class TraditionalAccountsDataModel extends ActivatableDataModel {
ArrayList<PaymentAccount> accounts = new ArrayList<>(user.getPaymentAccounts().stream() ArrayList<PaymentAccount> accounts = new ArrayList<>(user.getPaymentAccounts().stream()
.filter(paymentAccount -> !(paymentAccount instanceof AssetAccount)) .filter(paymentAccount -> !(paymentAccount instanceof AssetAccount))
.collect(Collectors.toList())); .collect(Collectors.toList()));
GUIUtil.exportAccounts(accounts, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler, keyRing); GUIUtil.exportAccounts(accounts, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler);
} }
} }
public void importAccounts(Stage stage) { public void importAccounts(Stage stage) {
GUIUtil.importAccounts(user, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler, keyRing); GUIUtil.importAccounts(user, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler);
} }
public int getNumPaymentAccounts() { public int getNumPaymentAccounts() {

View File

@ -42,7 +42,7 @@ import java.util.Date;
import java.util.Optional; import java.util.Optional;
@Slf4j @Slf4j
class TransactionsListItem { public class TransactionsListItem {
private String dateString; private String dateString;
private final Date date; private final Date date;
private final String txId; private final String txId;
@ -54,12 +54,15 @@ class TransactionsListItem {
private boolean received; private boolean received;
private boolean detailsAvailable; private boolean detailsAvailable;
private BigInteger amount = BigInteger.ZERO; private BigInteger amount = BigInteger.ZERO;
private BigInteger txFee = BigInteger.ZERO;
private String memo = ""; private String memo = "";
private long confirmations = 0; private long confirmations = 0;
@Getter @Getter
private boolean initialTxConfidenceVisibility = true; private boolean initialTxConfidenceVisibility = true;
private final Supplier<LazyFields> lazyFieldsSupplier; private final Supplier<LazyFields> lazyFieldsSupplier;
private XmrWalletService xmrWalletService; private XmrWalletService xmrWalletService;
@Getter
private MoneroTxWallet tx;
private static class LazyFields { private static class LazyFields {
TxConfidenceIndicator txConfidenceIndicator; TxConfidenceIndicator txConfidenceIndicator;
@ -80,6 +83,7 @@ class TransactionsListItem {
TransactionsListItem(MoneroTxWallet tx, TransactionsListItem(MoneroTxWallet tx,
XmrWalletService xmrWalletService, XmrWalletService xmrWalletService,
TransactionAwareTradable transactionAwareTradable) { TransactionAwareTradable transactionAwareTradable) {
this.tx = tx;
this.memo = tx.getNote(); this.memo = tx.getNote();
this.txId = tx.getHash(); this.txId = tx.getHash();
this.xmrWalletService = xmrWalletService; this.xmrWalletService = xmrWalletService;
@ -107,6 +111,7 @@ class TransactionsListItem {
amount = valueSentFromMe.multiply(BigInteger.valueOf(-1)); amount = valueSentFromMe.multiply(BigInteger.valueOf(-1));
received = false; received = false;
direction = Res.get("funds.tx.direction.sentTo"); direction = Res.get("funds.tx.direction.sentTo");
txFee = tx.getFee().multiply(BigInteger.valueOf(-1));
} }
if (optionalTradable.isPresent()) { if (optionalTradable.isPresent()) {
@ -201,6 +206,14 @@ class TransactionsListItem {
return amount; return amount;
} }
public BigInteger getTxFee() {
return txFee;
}
public String getTxFeeStr() {
return txFee.equals(BigInteger.ZERO) ? "" : HavenoUtils.formatXmr(txFee);
}
public String getAddressString() { public String getAddressString() {
return addressString; return addressString;
} }

View File

@ -36,7 +36,8 @@
<TableColumn fx:id="detailsColumn" minWidth="220" maxWidth="220"/> <TableColumn fx:id="detailsColumn" minWidth="220" maxWidth="220"/>
<TableColumn fx:id="addressColumn" minWidth="260"/> <TableColumn fx:id="addressColumn" minWidth="260"/>
<TableColumn fx:id="transactionColumn" minWidth="180"/> <TableColumn fx:id="transactionColumn" minWidth="180"/>
<TableColumn fx:id="amountColumn" minWidth="130" maxWidth="130"/> <TableColumn fx:id="amountColumn" minWidth="110" maxWidth="110"/>
<TableColumn fx:id="txFeeColumn" minWidth="110" maxWidth="110"/>
<TableColumn fx:id="memoColumn" minWidth="40"/> <TableColumn fx:id="memoColumn" minWidth="40"/>
<TableColumn fx:id="confidenceColumn" minWidth="120" maxWidth="130"/> <TableColumn fx:id="confidenceColumn" minWidth="120" maxWidth="130"/>
<TableColumn fx:id="revertTxColumn" sortable="false" minWidth="110" maxWidth="110" visible="false"/> <TableColumn fx:id="revertTxColumn" sortable="false" minWidth="110" maxWidth="110" visible="false"/>

View File

@ -32,10 +32,10 @@ import haveno.desktop.common.view.FxmlView;
import haveno.desktop.components.AddressWithIconAndDirection; import haveno.desktop.components.AddressWithIconAndDirection;
import haveno.desktop.components.AutoTooltipButton; import haveno.desktop.components.AutoTooltipButton;
import haveno.desktop.components.AutoTooltipLabel; import haveno.desktop.components.AutoTooltipLabel;
import haveno.desktop.components.ExternalHyperlink;
import haveno.desktop.components.HyperlinkWithIcon; import haveno.desktop.components.HyperlinkWithIcon;
import haveno.desktop.main.overlays.windows.OfferDetailsWindow; import haveno.desktop.main.overlays.windows.OfferDetailsWindow;
import haveno.desktop.main.overlays.windows.TradeDetailsWindow; import haveno.desktop.main.overlays.windows.TradeDetailsWindow;
import haveno.desktop.main.overlays.windows.TxDetailsWindow;
import haveno.desktop.util.GUIUtil; import haveno.desktop.util.GUIUtil;
import haveno.network.p2p.P2PService; import haveno.network.p2p.P2PService;
import java.math.BigInteger; import java.math.BigInteger;
@ -70,7 +70,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
@FXML @FXML
TableView<TransactionsListItem> tableView; TableView<TransactionsListItem> tableView;
@FXML @FXML
TableColumn<TransactionsListItem, TransactionsListItem> dateColumn, detailsColumn, addressColumn, transactionColumn, amountColumn, memoColumn, confidenceColumn, revertTxColumn; TableColumn<TransactionsListItem, TransactionsListItem> dateColumn, detailsColumn, addressColumn, transactionColumn, amountColumn, txFeeColumn, memoColumn, confidenceColumn, revertTxColumn;
@FXML @FXML
Label numItems; Label numItems;
@FXML @FXML
@ -85,11 +85,12 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
private final Preferences preferences; private final Preferences preferences;
private final TradeDetailsWindow tradeDetailsWindow; private final TradeDetailsWindow tradeDetailsWindow;
private final OfferDetailsWindow offerDetailsWindow; private final OfferDetailsWindow offerDetailsWindow;
private final TxDetailsWindow txDetailsWindow;
private EventHandler<KeyEvent> keyEventEventHandler; private EventHandler<KeyEvent> keyEventEventHandler;
private Scene scene; private Scene scene;
private TransactionsUpdater transactionsUpdater = new TransactionsUpdater(); private final TransactionsUpdater transactionsUpdater = new TransactionsUpdater();
private class TransactionsUpdater extends MoneroWalletListener { private class TransactionsUpdater extends MoneroWalletListener {
@Override @Override
@ -113,11 +114,13 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
Preferences preferences, Preferences preferences,
TradeDetailsWindow tradeDetailsWindow, TradeDetailsWindow tradeDetailsWindow,
OfferDetailsWindow offerDetailsWindow, OfferDetailsWindow offerDetailsWindow,
TxDetailsWindow txDetailsWindow,
DisplayedTransactionsFactory displayedTransactionsFactory) { DisplayedTransactionsFactory displayedTransactionsFactory) {
this.xmrWalletService = xmrWalletService; this.xmrWalletService = xmrWalletService;
this.preferences = preferences; this.preferences = preferences;
this.tradeDetailsWindow = tradeDetailsWindow; this.tradeDetailsWindow = tradeDetailsWindow;
this.offerDetailsWindow = offerDetailsWindow; this.offerDetailsWindow = offerDetailsWindow;
this.txDetailsWindow = txDetailsWindow;
this.displayedTransactions = displayedTransactionsFactory.create(); this.displayedTransactions = displayedTransactionsFactory.create();
this.sortedDisplayedTransactions = displayedTransactions.asSortedList(); this.sortedDisplayedTransactions = displayedTransactions.asSortedList();
} }
@ -129,11 +132,12 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
addressColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.address"))); addressColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.address")));
transactionColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.txId", Res.getBaseCurrencyCode()))); transactionColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.txId", Res.getBaseCurrencyCode())));
amountColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.amountWithCur", Res.getBaseCurrencyCode()))); amountColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.amountWithCur", Res.getBaseCurrencyCode())));
txFeeColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.txFee", Res.getBaseCurrencyCode())));
memoColumn.setGraphic(new AutoTooltipLabel(Res.get("funds.tx.memo"))); memoColumn.setGraphic(new AutoTooltipLabel(Res.get("funds.tx.memo")));
confidenceColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.confirmations", Res.getBaseCurrencyCode()))); confidenceColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.confirmations", Res.getBaseCurrencyCode())));
revertTxColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.revert", Res.getBaseCurrencyCode()))); revertTxColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.revert", Res.getBaseCurrencyCode())));
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN);
tableView.setPlaceholder(new AutoTooltipLabel(Res.get("funds.tx.noTxAvailable"))); tableView.setPlaceholder(new AutoTooltipLabel(Res.get("funds.tx.noTxAvailable")));
setDateColumnCellFactory(); setDateColumnCellFactory();
@ -141,6 +145,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
setAddressColumnCellFactory(); setAddressColumnCellFactory();
setTransactionColumnCellFactory(); setTransactionColumnCellFactory();
setAmountColumnCellFactory(); setAmountColumnCellFactory();
setTxFeeColumnCellFactory();
setMemoColumnCellFactory(); setMemoColumnCellFactory();
setConfidenceColumnCellFactory(); setConfidenceColumnCellFactory();
setRevertTxColumnCellFactory(); setRevertTxColumnCellFactory();
@ -156,7 +161,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
addressColumn.setComparator(Comparator.comparing(item -> item.getDirection() + item.getAddressString())); addressColumn.setComparator(Comparator.comparing(item -> item.getDirection() + item.getAddressString()));
transactionColumn.setComparator(Comparator.comparing(TransactionsListItem::getTxId)); transactionColumn.setComparator(Comparator.comparing(TransactionsListItem::getTxId));
amountColumn.setComparator(Comparator.comparing(TransactionsListItem::getAmount)); amountColumn.setComparator(Comparator.comparing(TransactionsListItem::getAmount));
confidenceColumn.setComparator(Comparator.comparingLong(item -> item.getNumConfirmations())); confidenceColumn.setComparator(Comparator.comparingLong(TransactionsListItem::getNumConfirmations));
memoColumn.setComparator(Comparator.comparing(TransactionsListItem::getMemo)); memoColumn.setComparator(Comparator.comparing(TransactionsListItem::getMemo));
dateColumn.setSortType(TableColumn.SortType.DESCENDING); dateColumn.setSortType(TableColumn.SortType.DESCENDING);
@ -216,8 +221,9 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
columns[2] = item.getDirection() + " " + item.getAddressString(); columns[2] = item.getDirection() + " " + item.getAddressString();
columns[3] = item.getTxId(); columns[3] = item.getTxId();
columns[4] = item.getAmountStr(); columns[4] = item.getAmountStr();
columns[5] = item.getMemo() == null ? "" : item.getMemo(); columns[5] = item.getTxFeeStr();
columns[6] = String.valueOf(item.getNumConfirmations()); columns[6] = item.getMemo() == null ? "" : item.getMemo();
columns[7] = String.valueOf(item.getNumConfirmations());
return columns; return columns;
}; };
@ -250,6 +256,10 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
tradeDetailsWindow.show((Trade) item.getTradable()); tradeDetailsWindow.show((Trade) item.getTradable());
} }
private void openTxDetailPopup(TransactionsListItem item) {
txDetailsWindow.show(item);
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// ColumnCellFactories // ColumnCellFactories
@ -373,9 +383,9 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
//noinspection Duplicates //noinspection Duplicates
if (item != null && !empty) { if (item != null && !empty) {
String transactionId = item.getTxId(); String transactionId = item.getTxId();
hyperlinkWithIcon = new ExternalHyperlink(transactionId); hyperlinkWithIcon = new HyperlinkWithIcon(transactionId, AwesomeIcon.INFO_SIGN);
hyperlinkWithIcon.setOnAction(event -> openTxInBlockExplorer(item)); hyperlinkWithIcon.setOnAction(event -> openTxDetailPopup(item));
hyperlinkWithIcon.setTooltip(new Tooltip(Res.get("tooltip.openBlockchainForTx", transactionId))); hyperlinkWithIcon.setTooltip(new Tooltip(Res.get("txDetailsWindow.headline")));
setGraphic(hyperlinkWithIcon); setGraphic(hyperlinkWithIcon);
} else { } else {
setGraphic(null); setGraphic(null);
@ -414,6 +424,33 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
}); });
} }
private void setTxFeeColumnCellFactory() {
txFeeColumn.setCellValueFactory((addressListItem) ->
new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
txFeeColumn.setCellFactory(
new Callback<>() {
@Override
public TableCell<TransactionsListItem, TransactionsListItem> call(TableColumn<TransactionsListItem,
TransactionsListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final TransactionsListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setGraphic(new AutoTooltipLabel(item.getTxFeeStr()));
} else {
setGraphic(null);
}
}
};
}
});
}
private void setMemoColumnCellFactory() { private void setMemoColumnCellFactory() {
memoColumn.setCellValueFactory((addressListItem) -> memoColumn.setCellValueFactory((addressListItem) ->
new ReadOnlyObjectWrapper<>(addressListItem.getValue())); new ReadOnlyObjectWrapper<>(addressListItem.getValue()));

View File

@ -51,7 +51,7 @@ import haveno.desktop.components.BusyAnimation;
import haveno.desktop.components.HyperlinkWithIcon; import haveno.desktop.components.HyperlinkWithIcon;
import haveno.desktop.components.TitledGroupBg; import haveno.desktop.components.TitledGroupBg;
import haveno.desktop.main.overlays.popups.Popup; import haveno.desktop.main.overlays.popups.Popup;
import haveno.desktop.main.overlays.windows.TxDetails; import haveno.desktop.main.overlays.windows.TxWithdrawWindow;
import haveno.desktop.main.overlays.windows.WalletPasswordWindow; import haveno.desktop.main.overlays.windows.WalletPasswordWindow;
import haveno.desktop.util.FormBuilder; import haveno.desktop.util.FormBuilder;
import haveno.desktop.util.GUIUtil; import haveno.desktop.util.GUIUtil;
@ -333,7 +333,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
xmrWalletService.getWallet().setTxNote(tx.getHash(), withdrawMemoTextField.getText()); // TODO (monero-java): tx note does not persist when tx created then relayed xmrWalletService.getWallet().setTxNote(tx.getHash(), withdrawMemoTextField.getText()); // TODO (monero-java): tx note does not persist when tx created then relayed
String key = "showTransactionSent"; String key = "showTransactionSent";
if (DontShowAgainLookup.showAgain(key)) { if (DontShowAgainLookup.showAgain(key)) {
new TxDetails(tx.getHash(), withdrawToAddress, HavenoUtils.formatXmr(receiverAmount, true), HavenoUtils.formatXmr(fee, true), xmrWalletService.getWallet().getTxNote(tx.getHash())) new TxWithdrawWindow(tx.getHash(), withdrawToAddress, HavenoUtils.formatXmr(receiverAmount, true), HavenoUtils.formatXmr(fee, true), xmrWalletService.getWallet().getTxNote(tx.getHash()))
.dontShowAgainId(key) .dontShowAgainId(key)
.show(); .show();
} }

View File

@ -296,6 +296,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
useSavingsWallet, useSavingsWallet,
triggerPrice, triggerPrice,
reserveExactAmount, reserveExactAmount,
false, // desktop ui resets address entries on cancel
resultHandler, resultHandler,
errorMessageHandler); errorMessageHandler);
} }

View File

@ -65,7 +65,6 @@ import haveno.desktop.main.overlays.popups.Popup;
import haveno.desktop.main.overlays.windows.OfferDetailsWindow; import haveno.desktop.main.overlays.windows.OfferDetailsWindow;
import haveno.desktop.main.portfolio.PortfolioView; import haveno.desktop.main.portfolio.PortfolioView;
import haveno.desktop.main.portfolio.editoffer.EditOfferView; import haveno.desktop.main.portfolio.editoffer.EditOfferView;
import haveno.desktop.util.CssTheme;
import haveno.desktop.util.FormBuilder; import haveno.desktop.util.FormBuilder;
import haveno.desktop.util.GUIUtil; import haveno.desktop.util.GUIUtil;
import haveno.desktop.util.Layout; import haveno.desktop.util.Layout;
@ -1130,14 +1129,10 @@ abstract public class OfferBookView<R extends GridPane, M extends OfferBookViewM
if (myOffer) { if (myOffer) {
iconView.setId("image-remove"); iconView.setId("image-remove");
title = Res.get("shared.remove"); title = Res.get("shared.remove");
button.setId(null);
button.setStyle(CssTheme.isDarkTheme() ? "-fx-text-fill: white" : "-fx-text-fill: #444444");
button.setOnAction(e -> onRemoveOpenOffer(offer)); button.setOnAction(e -> onRemoveOpenOffer(offer));
iconView2.setId("image-edit"); iconView2.setId("image-edit");
button2.updateText(Res.get("shared.edit")); button2.updateText(Res.get("shared.edit"));
button2.setId(null);
button2.setStyle(CssTheme.isDarkTheme() ? "-fx-text-fill: white" : "-fx-text-fill: #444444");
button2.setOnAction(e -> onEditOpenOffer(offer)); button2.setOnAction(e -> onEditOpenOffer(offer));
button2.setManaged(true); button2.setManaged(true);
button2.setVisible(true); button2.setVisible(true);

View File

@ -678,8 +678,7 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
closeTicketButton.disableProperty().unbind(); closeTicketButton.disableProperty().unbind();
hide(); hide();
}, (errMessage, err) -> { }, (errMessage, err) -> {
log.error("Error closing dispute ticket: " + errMessage); log.error("Error closing dispute ticket: " + errMessage + "\n", err);
err.printStackTrace();
new Popup().error(err.toString()).show(); new Popup().error(err.toString()).show();
}); });
} }

View File

@ -0,0 +1,108 @@
/*
* 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.main.overlays.windows;
import haveno.core.locale.Res;
import haveno.core.trade.HavenoUtils;
import haveno.core.xmr.wallet.XmrWalletService;
import haveno.desktop.main.funds.transactions.TransactionsListItem;
import haveno.desktop.main.overlays.Overlay;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Region;
import monero.wallet.model.MoneroTxWallet;
import static haveno.desktop.util.FormBuilder.addConfirmationLabelLabel;
import static haveno.desktop.util.FormBuilder.addConfirmationLabelTextFieldWithCopyIcon;
import static haveno.desktop.util.FormBuilder.addLabelTxIdTextField;
import static haveno.desktop.util.FormBuilder.addMultilineLabel;
import static haveno.desktop.util.FormBuilder.addTitledGroupBg;
import java.math.BigInteger;
import com.google.inject.Inject;
public class TxDetailsWindow extends Overlay<TxDetailsWindow> {
private XmrWalletService xmrWalletService;
private TransactionsListItem item;
@Inject
public TxDetailsWindow(XmrWalletService xmrWalletService) {
this.xmrWalletService = xmrWalletService;
}
public void show(TransactionsListItem item) {
this.item = item;
rowIndex = -1;
width = 918;
createGridPane();
gridPane.setHgap(15);
addHeadLine();
addContent();
addButtons();
addDontShowAgainCheckBox();
applyStyles();
display();
}
protected void addContent() {
int rows = 10;
MoneroTxWallet tx = item.getTx();
String memo = tx.getNote();
if (memo != null && !"".equals(memo)) rows++;
String txKey = null;
boolean isOutgoing = tx.getOutgoingTransfer() != null;
if (isOutgoing) {
try {
txKey = xmrWalletService.getWallet().getTxKey(tx.getHash());
} catch (Exception e) {
// TODO (monero-java): wallet.getTxKey() should return null if key does not exist instead of throwing exception
}
}
if (txKey != null && !"".equals(txKey)) rows++;
// add title
addTitledGroupBg(gridPane, ++rowIndex, rows, Res.get("txDetailsWindow.headline"));
Region spacer = new Region();
spacer.setMinHeight(15);
gridPane.add(spacer, 0, ++rowIndex);
// add sent or received note
String resKey = isOutgoing ? "txDetailsWindow.xmr.noteSent" : "txDetailsWindow.xmr.noteReceived";
GridPane.setColumnSpan(addMultilineLabel(gridPane, ++rowIndex, Res.get(resKey), 0), 2);
spacer = new Region();
spacer.setMinHeight(15);
gridPane.add(spacer, 0, ++rowIndex);
// add tx fields
addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.dateTime"), item.getDateString());
BigInteger amount;
if (isOutgoing) {
addConfirmationLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, Res.get("txDetailsWindow.sentTo"), item.getAddressString());
amount = tx.getOutgoingAmount();
} else {
addConfirmationLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, Res.get("txDetailsWindow.receivedWith"), item.getAddressString());
amount = tx.getIncomingAmount();
}
addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.amount"), HavenoUtils.formatXmr(amount));
addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.txFee"), HavenoUtils.formatXmr(tx.getFee()));
if (memo != null && !"".equals(memo)) addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("funds.withdrawal.memoLabel"), memo);
if (txKey != null && !"".equals(txKey)) addConfirmationLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, Res.get("txDetailsWindow.txKey"), txKey);
addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("txDetailsWindow.txId"), tx.getHash());
}
}

View File

@ -28,12 +28,12 @@ import static haveno.desktop.util.FormBuilder.addConfirmationLabelTextFieldWithC
import static haveno.desktop.util.FormBuilder.addLabelTxIdTextField; import static haveno.desktop.util.FormBuilder.addLabelTxIdTextField;
import static haveno.desktop.util.FormBuilder.addMultilineLabel; import static haveno.desktop.util.FormBuilder.addMultilineLabel;
public class TxDetails extends Overlay<TxDetails> { public class TxWithdrawWindow extends Overlay<TxWithdrawWindow> {
protected String txId, address, amount, fee, memo; protected String txId, address, amount, fee, memo;
protected TxIdTextField txIdTextField; protected TxIdTextField txIdTextField;
public TxDetails(String txId, String address, String amount, String fee, String memo) { public TxWithdrawWindow(String txId, String address, String amount, String fee, String memo) {
type = Type.Attention; type = Type.Attention;
this.txId = txId; this.txId = txId;
this.address = address; this.address = address;
@ -59,7 +59,7 @@ public class TxDetails extends Overlay<TxDetails> {
protected void addContent() { protected void addContent() {
GridPane.setColumnSpan( GridPane.setColumnSpan(
addMultilineLabel(gridPane, ++rowIndex, Res.get("txDetailsWindow.xmr.note"), 0), 2); addMultilineLabel(gridPane, ++rowIndex, Res.get("txDetailsWindow.xmr.noteSent"), 0), 2);
Region spacer = new Region(); Region spacer = new Region();
spacer.setMinHeight(20); spacer.setMinHeight(20);
gridPane.add(spacer, 0, ++rowIndex); gridPane.add(spacer, 0, ++rowIndex);

View File

@ -18,6 +18,7 @@
package haveno.desktop.main.portfolio; package haveno.desktop.main.portfolio;
import com.google.inject.Inject; import com.google.inject.Inject;
import haveno.common.UserThread;
import haveno.core.locale.Res; import haveno.core.locale.Res;
import haveno.core.offer.OfferPayload; import haveno.core.offer.OfferPayload;
import haveno.core.offer.OpenOffer; import haveno.core.offer.OpenOffer;
@ -200,7 +201,7 @@ public class PortfolioView extends ActivatableView<TabPane, Void> {
editOfferView.applyOpenOffer(openOffer); editOfferView.applyOpenOffer(openOffer);
editOpenOfferTab = new Tab(Res.get("portfolio.tab.editOpenOffer").toUpperCase()); editOpenOfferTab = new Tab(Res.get("portfolio.tab.editOpenOffer").toUpperCase());
editOfferView.setCloseHandler(() -> { editOfferView.setCloseHandler(() -> {
root.getTabs().remove(editOpenOfferTab); UserThread.execute(() -> root.getTabs().remove(editOpenOfferTab));
}); });
root.getTabs().add(editOpenOfferTab); root.getTabs().add(editOpenOfferTab);
} }
@ -220,7 +221,7 @@ public class PortfolioView extends ActivatableView<TabPane, Void> {
duplicateOfferView.initWithData((OfferPayload) data); duplicateOfferView.initWithData((OfferPayload) data);
duplicateOfferTab = new Tab(Res.get("portfolio.tab.duplicateOffer").toUpperCase()); duplicateOfferTab = new Tab(Res.get("portfolio.tab.duplicateOffer").toUpperCase());
duplicateOfferView.setCloseHandler(() -> { duplicateOfferView.setCloseHandler(() -> {
root.getTabs().remove(duplicateOfferTab); UserThread.execute(() -> root.getTabs().remove(duplicateOfferTab));
}); });
root.getTabs().add(duplicateOfferTab); root.getTabs().add(duplicateOfferTab);
} }

View File

@ -153,8 +153,7 @@ public abstract class TradeSubView extends HBox {
tradeStepView.setChatCallback(chatCallback); tradeStepView.setChatCallback(chatCallback);
tradeStepView.activate(); tradeStepView.activate();
} catch (Exception e) { } catch (Exception e) {
log.error("Creating viewClass {} caused an error {}", viewClass, e.getMessage()); log.error("Creating viewClass {} caused an error {}\n", viewClass, e.getMessage(), e);
e.printStackTrace();
} }
} }

View File

@ -282,6 +282,12 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
}; };
filterPropertyListener = (observable, oldValue, newValue) -> applyPreventPublicXmrNetwork(); filterPropertyListener = (observable, oldValue, newValue) -> applyPreventPublicXmrNetwork();
// disable radio buttons if no nodes available
if (xmrNodes.getProvidedXmrNodes().isEmpty()) {
useProvidedNodesRadio.setDisable(true);
}
usePublicNodesRadio.setDisable(isPublicNodesDisabled());
//TODO sorting needs other NetworkStatisticListItem as columns type //TODO sorting needs other NetworkStatisticListItem as columns type
/* creationDateColumn.setComparator((o1, o2) -> /* creationDateColumn.setComparator((o1, o2) ->
o1.statistic.getCreationDate().compareTo(o2.statistic.getCreationDate())); o1.statistic.getCreationDate().compareTo(o2.statistic.getCreationDate()));
@ -433,7 +439,7 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
} }
private void onMoneroPeersToggleSelected(boolean calledFromUser) { private void onMoneroPeersToggleSelected(boolean calledFromUser) {
usePublicNodesRadio.setDisable(isPreventPublicXmrNetwork()); usePublicNodesRadio.setDisable(isPublicNodesDisabled());
XmrNodes.MoneroNodesOption currentMoneroNodesOption = XmrNodes.MoneroNodesOption.values()[preferences.getMoneroNodesOptionOrdinal()]; XmrNodes.MoneroNodesOption currentMoneroNodesOption = XmrNodes.MoneroNodesOption.values()[preferences.getMoneroNodesOptionOrdinal()];
@ -493,7 +499,7 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
private void applyPreventPublicXmrNetwork() { private void applyPreventPublicXmrNetwork() {
final boolean preventPublicXmrNetwork = isPreventPublicXmrNetwork(); final boolean preventPublicXmrNetwork = isPreventPublicXmrNetwork();
usePublicNodesRadio.setDisable(xmrLocalNode.shouldBeUsed() || preventPublicXmrNetwork); usePublicNodesRadio.setDisable(isPublicNodesDisabled());
if (preventPublicXmrNetwork && selectedMoneroNodesOption == XmrNodes.MoneroNodesOption.PUBLIC) { if (preventPublicXmrNetwork && selectedMoneroNodesOption == XmrNodes.MoneroNodesOption.PUBLIC) {
selectedMoneroNodesOption = XmrNodes.MoneroNodesOption.PROVIDED; selectedMoneroNodesOption = XmrNodes.MoneroNodesOption.PROVIDED;
preferences.setMoneroNodesOptionOrdinal(selectedMoneroNodesOption.ordinal()); preferences.setMoneroNodesOptionOrdinal(selectedMoneroNodesOption.ordinal());
@ -502,6 +508,10 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
} }
} }
private boolean isPublicNodesDisabled() {
return xmrNodes.getPublicXmrNodes().isEmpty() || isPreventPublicXmrNetwork();
}
private void updateP2PTable() { private void updateP2PTable() {
if (connectionService.isShutDownStarted()) return; // ignore if shutting down if (connectionService.isShutDownStarted()) return; // ignore if shutting down
p2pPeersTableView.getItems().forEach(P2pNetworkListItem::cleanup); p2pPeersTableView.getItems().forEach(P2pNetworkListItem::cleanup);

View File

@ -108,7 +108,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
private ComboBox<TradeCurrency> preferredTradeCurrencyComboBox; private ComboBox<TradeCurrency> preferredTradeCurrencyComboBox;
private ToggleButton showOwnOffersInOfferBook, useAnimations, useDarkMode, sortMarketCurrenciesNumerically, private ToggleButton showOwnOffersInOfferBook, useAnimations, useDarkMode, sortMarketCurrenciesNumerically,
avoidStandbyMode, useCustomFee, autoConfirmXmrToggle, hideNonAccountPaymentMethodsToggle, denyApiTakerToggle, avoidStandbyMode, useSoundForNotifications, useCustomFee, autoConfirmXmrToggle, hideNonAccountPaymentMethodsToggle, denyApiTakerToggle,
notifyOnPreReleaseToggle; notifyOnPreReleaseToggle;
private int gridRow = 0; private int gridRow = 0;
private int displayCurrenciesGridRowIndex = 0; private int displayCurrenciesGridRowIndex = 0;
@ -209,7 +209,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private void initializeGeneralOptions() { private void initializeGeneralOptions() {
int titledGroupBgRowSpan = displayStandbyModeFeature ? 7 : 6; int titledGroupBgRowSpan = displayStandbyModeFeature ? 8 : 7;
TitledGroupBg titledGroupBg = addTitledGroupBg(root, gridRow, titledGroupBgRowSpan, Res.get("setting.preferences.general")); TitledGroupBg titledGroupBg = addTitledGroupBg(root, gridRow, titledGroupBgRowSpan, Res.get("setting.preferences.general"));
GridPane.setColumnSpan(titledGroupBg, 1); GridPane.setColumnSpan(titledGroupBg, 1);
@ -285,6 +285,9 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
avoidStandbyMode = addSlideToggleButton(root, ++gridRow, avoidStandbyMode = addSlideToggleButton(root, ++gridRow,
Res.get("setting.preferences.avoidStandbyMode")); Res.get("setting.preferences.avoidStandbyMode"));
} }
useSoundForNotifications = addSlideToggleButton(root, ++gridRow,
Res.get("setting.preferences.useSoundForNotifications"), Layout.GROUP_DISTANCE * -1); // TODO: why must negative value be used to place toggle consistently?
} }
private void initializeSeparator() { private void initializeSeparator() {
@ -518,6 +521,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
GridPane.setHgrow(resetDontShowAgainButton, Priority.ALWAYS); GridPane.setHgrow(resetDontShowAgainButton, Priority.ALWAYS);
GridPane.setColumnIndex(resetDontShowAgainButton, 0); GridPane.setColumnIndex(resetDontShowAgainButton, 0);
} }
private void initializeAutoConfirmOptions() { private void initializeAutoConfirmOptions() {
GridPane autoConfirmGridPane = new GridPane(); GridPane autoConfirmGridPane = new GridPane();
GridPane.setHgrow(autoConfirmGridPane, Priority.ALWAYS); GridPane.setHgrow(autoConfirmGridPane, Priority.ALWAYS);
@ -790,6 +794,9 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
} else { } else {
preferences.setUseStandbyMode(false); preferences.setUseStandbyMode(false);
} }
useSoundForNotifications.setSelected(preferences.isUseSoundForNotifications());
useSoundForNotifications.setOnAction(e -> preferences.setUseSoundForNotifications(useSoundForNotifications.isSelected()));
} }
private void activateAutoConfirmPreferences() { private void activateAutoConfirmPreferences() {

View File

@ -65,6 +65,7 @@ import javafx.scene.text.TextAlignment;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.fxmisc.easybind.EasyBind; import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription; import org.fxmisc.easybind.Subscription;
@ -565,12 +566,10 @@ public class ChatView extends AnchorPane {
inputTextArea.setText(inputTextArea.getText() + "\n[" + Res.get("support.attachment") + " " + result.getName() + "]"); inputTextArea.setText(inputTextArea.getText() + "\n[" + Res.get("support.attachment") + " " + result.getName() + "]");
} }
} catch (java.io.IOException e) { } catch (java.io.IOException e) {
e.printStackTrace(); log.error(ExceptionUtils.getStackTrace(e));
log.error(e.getMessage());
} }
} catch (MalformedURLException e2) { } catch (MalformedURLException e2) {
e2.printStackTrace(); log.error(ExceptionUtils.getStackTrace(e2));
log.error(e2.getMessage());
} }
} }
} else { } else {
@ -593,8 +592,7 @@ public class ChatView extends AnchorPane {
inputTextArea.setText(inputTextArea.getText() + "\n[" + Res.get("support.attachment") + " " + name + "]"); inputTextArea.setText(inputTextArea.getText() + "\n[" + Res.get("support.attachment") + " " + name + "]");
} }
} catch (Exception e) { } catch (Exception e) {
log.error(e.toString()); log.error(ExceptionUtils.getStackTrace(e));
e.printStackTrace();
} }
} }
@ -629,8 +627,7 @@ public class ChatView extends AnchorPane {
try (FileOutputStream fileOutputStream = new FileOutputStream(file.getAbsolutePath())) { try (FileOutputStream fileOutputStream = new FileOutputStream(file.getAbsolutePath())) {
fileOutputStream.write(attachment.getBytes()); fileOutputStream.write(attachment.getBytes());
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); log.error("Error opening attachment: {}\n", e.getMessage(), e);
System.out.println(e.getMessage());
} }
} }
} }

View File

@ -28,7 +28,6 @@ import com.googlecode.jcsv.writer.internal.CSVWriterBuilder;
import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon; import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon;
import haveno.common.UserThread; import haveno.common.UserThread;
import haveno.common.config.Config; import haveno.common.config.Config;
import haveno.common.crypto.KeyRing;
import haveno.common.file.CorruptedStorageFileHandler; import haveno.common.file.CorruptedStorageFileHandler;
import haveno.common.persistence.PersistenceManager; import haveno.common.persistence.PersistenceManager;
import haveno.common.proto.persistable.PersistableEnvelope; import haveno.common.proto.persistable.PersistableEnvelope;
@ -168,12 +167,11 @@ public class GUIUtil {
Preferences preferences, Preferences preferences,
Stage stage, Stage stage,
PersistenceProtoResolver persistenceProtoResolver, PersistenceProtoResolver persistenceProtoResolver,
CorruptedStorageFileHandler corruptedStorageFileHandler, CorruptedStorageFileHandler corruptedStorageFileHandler) {
KeyRing keyRing) {
if (!accounts.isEmpty()) { if (!accounts.isEmpty()) {
String directory = getDirectoryFromChooser(preferences, stage); String directory = getDirectoryFromChooser(preferences, stage);
if (!directory.isEmpty()) { if (!directory.isEmpty()) {
PersistenceManager<PersistableEnvelope> persistenceManager = new PersistenceManager<>(new File(directory), persistenceProtoResolver, corruptedStorageFileHandler, keyRing); PersistenceManager<PersistableEnvelope> persistenceManager = new PersistenceManager<>(new File(directory), persistenceProtoResolver, corruptedStorageFileHandler, null);
PaymentAccountList paymentAccounts = new PaymentAccountList(accounts); PaymentAccountList paymentAccounts = new PaymentAccountList(accounts);
persistenceManager.initialize(paymentAccounts, fileName, PersistenceManager.Source.PRIVATE_LOW_PRIO); persistenceManager.initialize(paymentAccounts, fileName, PersistenceManager.Source.PRIVATE_LOW_PRIO);
persistenceManager.persistNow(() -> { persistenceManager.persistNow(() -> {
@ -193,8 +191,7 @@ public class GUIUtil {
Preferences preferences, Preferences preferences,
Stage stage, Stage stage,
PersistenceProtoResolver persistenceProtoResolver, PersistenceProtoResolver persistenceProtoResolver,
CorruptedStorageFileHandler corruptedStorageFileHandler, CorruptedStorageFileHandler corruptedStorageFileHandler) {
KeyRing keyRing) {
FileChooser fileChooser = new FileChooser(); FileChooser fileChooser = new FileChooser();
File initDir = new File(preferences.getDirectoryChooserPath()); File initDir = new File(preferences.getDirectoryChooserPath());
if (initDir.isDirectory()) { if (initDir.isDirectory()) {
@ -207,7 +204,7 @@ public class GUIUtil {
if (Paths.get(path).getFileName().toString().equals(fileName)) { if (Paths.get(path).getFileName().toString().equals(fileName)) {
String directory = Paths.get(path).getParent().toString(); String directory = Paths.get(path).getParent().toString();
preferences.setDirectoryChooserPath(directory); preferences.setDirectoryChooserPath(directory);
PersistenceManager<PaymentAccountList> persistenceManager = new PersistenceManager<>(new File(directory), persistenceProtoResolver, corruptedStorageFileHandler, keyRing); PersistenceManager<PaymentAccountList> persistenceManager = new PersistenceManager<>(new File(directory), persistenceProtoResolver, corruptedStorageFileHandler, null);
persistenceManager.readPersisted(fileName, persisted -> { persistenceManager.readPersisted(fileName, persisted -> {
StringBuilder msg = new StringBuilder(); StringBuilder msg = new StringBuilder();
HashSet<PaymentAccount> paymentAccounts = new HashSet<>(); HashSet<PaymentAccount> paymentAccounts = new HashSet<>();

Some files were not shown because too many files have changed in this diff Show More