mirror of
https://github.com/haveno-dex/haveno.git
synced 2024-12-25 23:39:26 -05:00
Merge branch 'haveno-dex:master' into issue1253
This commit is contained in:
commit
10285cd37a
95
.github/workflows/build.yml
vendored
95
.github/workflows/build.yml
vendored
@ -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
2
.gitignore
vendored
@ -37,3 +37,5 @@ deploy
|
|||||||
.vscode
|
.vscode
|
||||||
.vim/*
|
.vim/*
|
||||||
*/.factorypath
|
*/.factorypath
|
||||||
|
.flatpak-builder
|
||||||
|
exchange.haveno.Haveno.yaml
|
||||||
|
27
README.md
27
README.md
@ -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>
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
21
build.gradle
21
build.gradle
@ -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,
|
||||||
|
@ -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) {
|
||||||
|
@ -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.
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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())
|
||||||
|
@ -284,6 +284,7 @@ public class CoreOffersService {
|
|||||||
useSavingsWallet,
|
useSavingsWallet,
|
||||||
triggerPriceAsLong,
|
triggerPriceAsLong,
|
||||||
reserveExactAmount,
|
reserveExactAmount,
|
||||||
|
true,
|
||||||
resultHandler::accept,
|
resultHandler::accept,
|
||||||
errorMessageHandler);
|
errorMessageHandler);
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
log.warn("Failed to fetch daemon info from custom node on startup, falling back to provided nodes: " + e.getMessage());
|
if (xmrNodes.getProvidedXmrNodes().isEmpty()) {
|
||||||
preferences.setMoneroNodesOptionOrdinal(XmrNodes.MoneroNodesOption.PROVIDED.ordinal());
|
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());
|
||||||
|
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());
|
||||||
|
@ -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();
|
||||||
|
@ -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 +
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
|
@ -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.");
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()) {
|
||||||
if (isSigned) {
|
try {
|
||||||
log.info("{} {} publishing signed payout tx from seller", trade.getClass().getSimpleName(), trade.getId());
|
if (isSigned) {
|
||||||
trade.processPayoutTx(message.getSignedPayoutTxHex(), false, true);
|
log.info("{} {} publishing signed payout tx from seller", trade.getClass().getSimpleName(), trade.getId());
|
||||||
} else {
|
trade.processPayoutTx(message.getSignedPayoutTxHex(), false, true);
|
||||||
try {
|
} else {
|
||||||
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,12 +175,12 @@ 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) {
|
|
||||||
HavenoUtils.waitFor(trade.getXmrConnectionService().getRefreshPeriodMs()); // wait to see published tx
|
|
||||||
trade.syncAndPollWallet();
|
|
||||||
if (trade.isPayoutPublished()) log.info("Payout tx already published for {} {}", trade.getClass().getName(), trade.getId());
|
|
||||||
else throw e;
|
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
HavenoUtils.waitFor(trade.getXmrConnectionService().getRefreshPeriodMs()); // wait to see published tx
|
||||||
|
trade.syncAndPollWallet();
|
||||||
|
if (trade.isPayoutPublished()) log.info("Payout tx already published for {} {}", trade.getClass().getName(), trade.getId());
|
||||||
|
else throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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) {
|
||||||
|
@ -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);
|
||||||
|
@ -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(),
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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());
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BIN
core/src/main/resources/cash_register.wav
Normal file
BIN
core/src/main/resources/cash_register.wav
Normal file
Binary file not shown.
BIN
core/src/main/resources/chime.wav
Normal file
BIN
core/src/main/resources/chime.wav
Normal file
Binary file not shown.
@ -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\
|
||||||
|
@ -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!
|
||||||
|
@ -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!
|
||||||
|
@ -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
|
||||||
|
@ -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!
|
||||||
|
@ -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!
|
||||||
|
@ -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!
|
||||||
|
@ -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パスワードを設定する前に、アプリケーションディレクトリのバックアップを作成してシードワードを書き留めておくことを強く推奨します。
|
||||||
|
@ -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!
|
||||||
|
@ -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!
|
||||||
|
@ -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!
|
||||||
|
@ -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!
|
||||||
|
@ -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\
|
||||||
|
@ -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!
|
||||||
|
@ -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强烈建议您备份应用程序的目录,并在设置密码之前记下您的还原密钥!
|
||||||
|
@ -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強烈建議您備份應用程序的目錄,並在設置密碼之前記下您的還原密鑰!
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
1
desktop/package/linux/Haveno.AppDir/.DirIcon
Symbolic link
1
desktop/package/linux/Haveno.AppDir/.DirIcon
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../haveno.png
|
@ -0,0 +1 @@
|
|||||||
|
../Haveno.desktop
|
1
desktop/package/linux/Haveno.AppDir/haveno.svg
Symbolic link
1
desktop/package/linux/Haveno.AppDir/haveno.svg
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../haveno.svg
|
14
desktop/package/linux/Haveno.desktop
Normal file
14
desktop/package/linux/Haveno.desktop
Normal 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
|
66
desktop/package/linux/exchange.haveno.Haveno.metainfo.xml
Normal file
66
desktop/package/linux/exchange.haveno.Haveno.metainfo.xml
Normal 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'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>
|
52
desktop/package/linux/exchange.haveno.Haveno.yml
Normal file
52
desktop/package/linux/exchange.haveno.Haveno.yml
Normal 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
|
BIN
desktop/package/linux/haveno.png
Normal file
BIN
desktop/package/linux/haveno.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
1
desktop/package/linux/haveno.svg
Normal file
1
desktop/package/linux/haveno.svg
Normal 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 |
BIN
desktop/package/linux/preview.png
Normal file
BIN
desktop/package/linux/preview.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 630 KiB |
@ -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>
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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"/>
|
||||||
|
@ -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()));
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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() {
|
||||||
|
@ -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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
Loading…
Reference in New Issue
Block a user