Compare commits

...

160 Commits

Author SHA1 Message Date
tobtoht 72f33da508 Merge pull request 'Improve row background color contrast for QDarkStyle' (#179) from tobtoht/feather:darkstyle_readability into master
4 years ago
tobtoht 480ecfd047 Improve row background color contrast for QDarkStyle
4 years ago
tobtoht 1a6024c697 Merge pull request 'MorphToken: Add Qr code for deposit address' (#178) from tobtoht/feather:morphtoken_qr into master
4 years ago
tobtoht a11dd576e4 MorphToken: Add Qr code for deposit address
4 years ago
tobtoht 9fed3eb171 Merge pull request 'update PKGBUILD' (#177) from wowario/feather:master into master
4 years ago
wowario 20156a7440
update PKGBUILD
4 years ago
tobtoht ca5b1df7df Merge pull request 'XMRig: don't disable mining on Tails' (#176) from tobtoht/feather:xmrig_mining_tails into master
4 years ago
tobtoht 474ee2100c XMRig: don't disable mining on Tails
4 years ago
tobtoht 1ccedea1dd Merge pull request 'Settings: temporarily remove warn on update checkbox' (#175) from tobtoht/feather:settings_update into master
4 years ago
tobtoht 70dd25c477 Settings: temporarily remove warn on update checkbox
4 years ago
tobtoht bce983a6a5 Merge pull request 'Menu: add shortcuts' (#174) from tobtoht/feather:shortcuts into master
4 years ago
tobtoht eb8d150a9d Menu: add shortcuts
4 years ago
tobtoht 841739cee9 Merge pull request 'SeedDialog: show restore height for 25 word seeds' (#173) from tobtoht/feather:seed_restore_height into master
4 years ago
tobtoht d4dbc748c8 SeedDialog: show restore height for 25 word seeds
4 years ago
tobtoht 5ce6c8933d Merge pull request 'MorphToken: get rates' (#171) from tobtoht/feather:morphtoken_rates into master
4 years ago
tobtoht f61355c409 MorphToken: get rates
4 years ago
tobtoht edad1928ab Merge pull request 'TickerWidget: Format fiat currency using locale' (#170) from tobtoht/feather:ticker_locale into master
4 years ago
tobtoht 4201167477 TickerWidget: Format fiat currency using locale
4 years ago
tobtoht 16414ba2e9 Merge pull request 'Send: Always include pref. currency in combobox' (#169) from tobtoht/feather:send_pref_cur into master
4 years ago
tobtoht 1d001322cc Send: Always include pref. currency in combobox
4 years ago
tobtoht 9d781d1b57 Merge pull request 'VerifyProofDialog: minor fixes' (#166) from tobtoht/feather:tx_proof_width into master
4 years ago
tobtoht 3453e5ef34 Merge pull request 'WalletInfoDialog: fix open wallet directory on macOS' (#168) from tobtoht/feather:open_url into master
4 years ago
tobtoht 76da957b81 Merge pull request 'Settings: raise window' (#167) from tobtoht/feather:settings_bring_to_front into master
4 years ago
tobtoht 35438792a0 Settings: raise window
4 years ago
tobtoht 01966df866 WalletInfoDialog: fix open wallet directory on macOS
4 years ago
tobtoht e2eb32ce78 VerifyProofDialog: minor fixes
4 years ago
tobtoht 03f484c2ae Merge pull request 'PasswordDialog: show warning if password incorrect' (#165) from tobtoht/feather:incorrect_password into master
4 years ago
tobtoht c44ee73ed0 PasswordDialog: show warning if password incorrect
4 years ago
tobtoht e6e669845c Merge pull request 'Misc networking fixes' (#160) from tobtoht/feather:nodes into master
4 years ago
tobtoht 5a08bc353e Misc networking fixes
4 years ago
tobtoht c8bc66a287 Merge pull request 'DebugInfoDialog: add target height' (#159) from tobtoht/feather:target-height into master
4 years ago
tobtoht 3beb470bbd DebugInfoDialog: add target height
4 years ago
tobtoht b875ee362e Merge pull request 'Menu: remove About QT' (#157) from tobtoht/feather:about_qt into master
4 years ago
tobtoht 4e3a977995 Menu: remove About QT
4 years ago
tobtoht eb801ed9d4 Merge pull request 'Mitigate target_height denial of service attack in wsMode' (#156) from tobtoht/feather:target_height into master
4 years ago
tobtoht d9dfef021f Mitigate target_height attack
4 years ago
tobtoht d2d3978de6 Change websocket .onion
4 years ago
tobtoht 02f7249e6e Merge pull request 'Revert "Syncing hotfix"' (#154) from tobtoht/feather:sync_hotfix into master
4 years ago
tobtoht a3309cea91 Revert "Syncing hotfix"
4 years ago
tobtoht 137ef6da72 Merge pull request 'Syncing hotfix' (#153) from tobtoht/feather:sync_hotfix into master
4 years ago
tobtoht 4f56147319 Syncing hotfix
4 years ago
tobtoht d58259cefe Merge pull request 'Receive: Fix qr code segfault' (#152) from tobtoht/feather:qr_code_crash into master
4 years ago
tobtoht 9be79bba48 Receive: Fix qr code segfault
4 years ago
tobtoht 64db32dca9 Wallet: do not emit updated on refresh
4 years ago
tobtoht 774b4ed15f Merge pull request 'DebugInfoDialog: make some text selectable' (#151) from tobtoht/feather:text_select into master
4 years ago
tobtoht d337653e53 DebugInfoDialog: make some text selectable
4 years ago
tobtoht b06b7fca2a Merge pull request 'Add missing license headers' (#150) from tobtoht/feather:missing_licence into master
4 years ago
tobtoht 169720002c Add missing license headers
4 years ago
tobtoht d7135393cd Merge pull request 'PasswordDialog: misc improvements' (#149) from tobtoht/feather:password_dialog into master
4 years ago
tobtoht 1e4a442b16 PasswordDialog: misc improvements
4 years ago
tobtoht 519df3f5a1 Merge pull request 'Nodes: don't connect to out of sync nodes in wsmode' (#148) from tobtoht/feather:node_height_mode into master
4 years ago
tobtoht 2a03ca9eda Nodes: don't connect to out of sync nodes in wsmode
4 years ago
tobtoht ff5dff26bb Merge pull request 'SeedDialog: add 25 word seed toggle' (#147) from tobtoht/feather:seed_25 into master
4 years ago
tobtoht cbd29e6290 Merge pull request 'MorphToken: allow disabling module' (#146) from tobtoht/feather:morphtoken into master
4 years ago
tobtoht 0f3c005b60 SeedDialog: add 25 word seed toggle
4 years ago
tobtoht 39c1d3bbf7 MorphToken: allow disabling module
4 years ago
tobtoht b66aceccc8 Merge pull request 'Initial MorphToken support' (#145) from tobtoht/feather:morphtoken into master
4 years ago
tobtoht e9a4a828d6 Initial MorphToken support
4 years ago
tobtoht bd60e30c3f Merge pull request 'Import transaction' (#143) from tobtoht/feather:import_tx into master
4 years ago
tobtoht 046b2cfc4c Import transaction
4 years ago
tobtoht 24133ac390 Merge pull request 'History: don't show copy spend proof on incoming transactions' (#115) from mrdeveloper/feather:hide-spend-proof-incoming-tx into master
4 years ago
tobtoht 8aecf79647 Merge pull request 'TransactionInfoDialog: add copy spendproof, in/outproof' (#142) from tobtoht/feather:txproof into master
4 years ago
tobtoht 780a35fabe TransactionInfoDialog: add copy spendproof, in/outproof
4 years ago
mrdeveloper 0e7c773bfd Remove unused declared method
4 years ago
mrdeveloper 11f8406be3 History: don't show copy spend proof on incoming transactions
4 years ago
tobtoht 2c99454565 Merge pull request 'FutureScheduler: update with upstream v0.17.1.3' (#141) from tobtoht/feather:scheduler into master
4 years ago
tobtoht cd96abe545 FutureScheduler: update with upstream v0.17.1.3
4 years ago
tobtoht d76b688ec4 Merge pull request 'Libwalletqt: update with upstream' (#140) from tobtoht/feather:libwalletqt_syncing into master
4 years ago
tobtoht ed47e8e05c Merge pull request 'Added cryptonote-social mining pool to XMRig' (#136) from mrdeveloper/feather:cryptonote-social into master
4 years ago
tobtoht 268b87a803 Libwalletqt: update with upstream
4 years ago
tobtoht 318690681b Merge pull request 'Update for v0.17.1.3' (#139) from tobtoht/feather:v0.17.1.3 into master
4 years ago
tobtoht b1e37e714d Update for v0.17.1.3
4 years ago
mrdeveloper 18b514351d Added cryptonote-social mining pool to XMRig
4 years ago
tobtoht 63c120bdb0 Merge pull request 'Stylesheets: Breeze: misc fixes' (#135) from tobtoht/feather:stylesheet_breeze_fixes into master
4 years ago
tobtoht 96409ecc17 Stylesheets: Breeze: misc fixes
4 years ago
tobtoht c1cba44cba Merge pull request 'AppContext: updateBalance: don't throw runtime error' (#134) from tobtoht/feather:no_runtime_error into master
4 years ago
tobtoht 7a1ac42b1e AppContext: updateBalance: don't throw runtime error
4 years ago
tobtoht 87fd142ffd Merge pull request 'Settings: add hide balance feature' (#133) from tobtoht/feather:hide_balance into master
4 years ago
tobtoht 62ad0d9527 Settings: add hide balance feature
4 years ago
tobtoht 4f9e4ac93d Merge pull request 'Utils: add blockchair.com block explorer' (#132) from tobtoht/feather:blockchair into master
4 years ago
tobtoht cb6f2873a9 Utils: add blockchair.com block explorer
4 years ago
tobtoht 20b741d30d Merge pull request 'Coins: numerical sort for amount column' (#105) from tobtoht/feather:coins_amount_sort into master
4 years ago
tobtoht 7ed2ff15d5 Merge pull request 'CCSWidget: fix misspelling' (#131) from tobtoht/feather:fix_ccs into master
4 years ago
tobtoht da4b2d16ef CCSWidget: fix misspelling
4 years ago
tobtoht 4521223413 Merge pull request 'Simplify and remove white space on recipient (Pay to)' (#124) from mrdeveloper/feather:simplified-recipient-addr into master
4 years ago
mrdeveloper 0dd81c4873 Simplify and remove white space on recipient (Pay to)
4 years ago
dsc df2ff26c0f Merge pull request 'MacOS: add touchbar support' (#121) from dsc/feather:macos-touchbar-submodule into master
4 years ago
mrdeveloper e657e2a6b0 MacOS: add touchbar support
4 years ago
dsc 34a63897d6 Merge pull request 'BugFix: show message box if no wallet is selected' (#118) from mrdeveloper/feather:bug-wallet-select into master
4 years ago
dsc 6279591d1b Merge pull request 'Check if given node URL has https so we can append https for get_info call' (#120) from mrdeveloper/feather:node-check-for-https into master
4 years ago
mrdeveloper 403b8140b3 Check if given node URL has https so we can append https for get_info call
4 years ago
mrdeveloper 71ba9f6d4a BugFix: show message box if no wallet is selected
4 years ago
dsc 3d5dae1f42 Merge pull request 'Coins: disable sweep instead of hide when output unconfirmed' (#111) from tobtoht/feather:coins_hide_sweep into master
4 years ago
tobtoht 78eabc5ae7 Coins: disable sweep instead of hide when output unconfirmed
4 years ago
dsc 485fb89941 Merge pull request 'Include QtMultimedia for buildbots' (#108) from dsc/feather:include-qtmultimedia into master
4 years ago
dsc df280cdd91 Merge pull request 'Write proper .exe on Windows' (#109) from dsc/feather:write-exe-windows into master
4 years ago
dsc 9b96ece2f9 Merge pull request 'XMRig: Donate level 1' (#110) from dsc/feather:xmrig-donate-level into master
4 years ago
dsc a44087b750
XMRig: Donate level 1
4 years ago
dsc 70cbf59ce8
Write proper .exe on Windows
4 years ago
dsc 4aeaf2660f
Include QtMultimedia for buildbots
4 years ago
tobtoht 9b7849c5be Coins: numerical sort for amount column
4 years ago
tobtoht 51a0f86652 Move cdiv to globals.h
4 years ago
dsc fa6ee35fb0 Merge pull request 'Mainwindow: fix tab order and stylization' (#104) from tobtoht/feather:tabs_order into master
4 years ago
tobtoht b1160280fe Mainwindow: fix tab order and stylization
4 years ago
tobtoht 7441d6cb4a Merge pull request 'XMRig: fix stop on windows' (#102) from tobtoht/feather:xmrrig_stop_windows into master
4 years ago
mrdeveloper 23000c6e60 Merge pull request 'PasswordDialog: Show wallet name' (#103) from mrdeveloper/feather:password-dialog-show-wallet-name into master
4 years ago
mrdeveloper 75edc6e5bb Merge branch 'master' into password-dialog-show-wallet-name
4 years ago
mrdeveloper 06f649cbb7 Merge pull request 'Contacts: import via csv file' (#100) from mrdeveloper/feather:contacts-import-csv into master
4 years ago
tobtoht ff78b5a6c2 XMRig: fix stop on windows
4 years ago
mrdeveloper 47d965c6cd Make format csv import as same as export
4 years ago
mrdeveloper d6c26d4159 PasswordDialog: Show wallet name
4 years ago
mrdeveloper 78516f4546 Contacts: import via csv file
4 years ago
dsc 49f072eea4 Merge pull request 'TransactionInfoDialog: add tx key' (#96) from tobtoht/feather:tx_key into master
4 years ago
tobtoht 302c258e6b TransactionInfoDialog: add tx key
4 years ago
dsc 70f1846de6 Merge pull request 'Update for v0.17.1.1' (#92) from tobtoht/feather:v0.17.1.1 into master
4 years ago
tobtoht ae2815c1c6 Update for v0.17.1.1
4 years ago
dsc 24993bc0b2 Merge pull request 'Offline transaction signing' (#88) from tobtoht/feather:offline_tx_signing into master
4 years ago
tobtoht 216b2e0c5e Offline transaction signing
4 years ago
dsc 44a2fc30dc Merge pull request 'Cmake: Refactor executable embeds, embed XMRig, allow solo mining' (#89) from dsc/feather:refactor-feather-execs into master
4 years ago
dsc aee60f33b7
- Embed XMRig executable (baked into buildbot image)
4 years ago
dsc 5d8700370b Merge pull request 'Xmrig: add support for TLS. Tor default off. Save username/password in wallet cache.' (#85) from dsc/feather:xmrig-tab-2 into master
4 years ago
dsc 1ef29207d5
Xmrig: add support for TLS. Tor default off. Save username/password in wallet cache.
4 years ago
tobtoht 497567e61f Merge pull request 'Bugfix: fix wallet closing issues by partially reverting #63' (#84) from tobtoht/feather:duplicate_wizard into master
4 years ago
tobtoht 15c51e2e2b Bugfix: fix wallet closing issues by partially reverting #63
4 years ago
tobtoht c5a055eb70 Merge pull request 'Prices: add support for NZD' (#82) from tobtoht/feather:nzd into master
4 years ago
tobtoht 1b9cc7c679 Prices: add support for NZD
4 years ago
tobtoht 0f0b033fe4 Merge pull request 'Fix indefinite hang when closing wallet via File -> Quit' (#80) from tobtoht/feather:close_without_indefinite_hang into master
4 years ago
tobtoht 3feda6d6c7 Fix indefinite hang when closing wallet via File -> Quit
4 years ago
dsc 103965ef40 Merge pull request 'Drone: fix AppImage builds' (#77) from dsc/feather:drone_appimage-fix into master
4 years ago
tobtoht 3ee5e003a7 Drone: fix AppImage builds
4 years ago
dsc 96ac717ede Merge pull request 'XMRig - Simple mining GUI to download/run XMRig' (#76) from dsc/feather:xmrig-tab into master
4 years ago
dsc d0260ce018 XMRig tab - Simple mining GUI to download and run XMRig releases.
4 years ago
tobtoht 61a135e1c2 Merge pull request 'Send: show fiat conversion when XMR is selected' (#75) from tobtoht/feather:send_fiat into master
4 years ago
tobtoht 98db512164 Send: show fiat conversion when XMR is selected
4 years ago
tobtoht a325883b62 Merge pull request 'Tails: fix slow wallet open' (#72) from tobtoht/feather:tails_fix_slow_open into master
4 years ago
tobtoht 5c7c13d76a Tails: fix slow wallet open
4 years ago
tobtoht 06cee3f03e Merge pull request 'DebugInfoDialog: Add Tails version' (#69) from tobtoht/feather:tails_version into master
4 years ago
tobtoht ec0d88cf1b DebugInfoDialog: Add Tails version
4 years ago
tobtoht eca37f934c Merge pull request 'Fix wallet update regression' (#68) from tobtoht/feather:no_update into master
4 years ago
tobtoht 18cf749ad9 Fix wallet update regression
4 years ago
tobtoht 1febbc869f Merge pull request 'Export/Import KeyImages/Outputs' (#66) from tobtoht/feather:keyimages into master
4 years ago
tobtoht ea49097ab3 Merge pull request 'Close wallet on exit' (#67) from tobtoht/feather:close_on_exit into master
4 years ago
tobtoht 1dcc69db39 Close wallet on exit
4 years ago
tobtoht fc3ac2eb33 Export/Import KeyImages/Outputs
4 years ago
dsc 0289aba113 Merge pull request 'AboutDialog: minor layout fix' (#62) from tobtoht/feather:about_fix into master
4 years ago
dsc 15f076f7d6 Merge pull request 'Coins: add key image icons for view only wallets' (#59) from tobtoht/feather:coins_no_keyimage into master
4 years ago
dsc 63e2ba799d Merge pull request 'Libwalletqt: integrate changes from upstream' (#63) from tobtoht/feather:syncing into master
4 years ago
tobtoht 1ff8a2aaf4 Libwalletqt: integrate changes from upstream
4 years ago
tobtoht 980340546a AboutDialog: minor layout fix
4 years ago
tobtoht ebde5a1cb7 Coins: add key image icons for view only wallets
4 years ago
tobtoht ffa8c3e5bf Merge pull request 'Rebase to Monero Core v0.17.1.0' (#58) from tobtoht/feather:v_17_1_0 into master
4 years ago
tobtoht 5a8641660c Rebase to Monero Core v0.17.1.0
4 years ago
dsc 36641d73a6 Merge pull request 'Open/create view-only wallets' (#53) from dsc/feather:view-only into master
4 years ago
dsc eac29cd0b6
Import wallet from keys (view-only)
4 years ago
dsc e8a5f59b4c Merge pull request 'Fix segfault on close' (#55) from tobtoht/feather:wizard_segv into master
4 years ago
tobtoht 7526714b4a Fix segfault on close
4 years ago
dsc 2ab78022f5 Merge pull request 'Fix misc image scaling hangs' (#54) from tobtoht/feather:img_hang into master
4 years ago
tobtoht 363bf0ea38 Fix misc image scaling hangs
4 years ago
dsc d465639d2a Merge pull request 'Fix debug build' (#52) from dsc/feather:fix-rpath-debug into master
4 years ago
dsc 0790b30b5d Commit cd1cd5cb75
4 years ago

@ -14,11 +14,9 @@ steps:
commands:
- git config --global url."http://gitea:3000/tor/".insteadOf https://git.torproject.org/
- git config --global url."http://gitea:3000/".insteadOf https://github.com/
- git submodule update --init --depth 50 contrib/tor
- git submodule update --init --depth 50 contrib/torsocks
- git submodule update --init --depth 120 monero
- git submodule update --init --depth 120 --recursive monero
- make -j8 release-static
- TOR="/usr/local/tor/bin/tor" XMRIG="/xmrig/xmrig" make -j6 release-static
environment:
OPENSSL_ROOT_DIR: /usr/local/openssl/
CMAKEFLAGS_EXTRA: -DFETCH_DEPS=Off
@ -30,7 +28,7 @@ steps:
- name: files_linux_release
path: /files
commands:
- export FN="feather-`git rev-parse --short HEAD`.zip"
- export FN="feather-`echo $DRONE_COMMIT_AFTER | cut -c 1-7`.zip"
- export TARGET_DIR="/files/$DRONE_SOURCE_BRANCH"
- mkdir -p "$TARGET_DIR"
- echo "writing to $TARGET_DIR/$FN"
@ -44,7 +42,7 @@ volumes:
path: /var/drone/ccache_linux_release/
- name: files_linux_release
host:
path: /mnt/storage1/feather_files/files/linux-release/
path: /build/feather_files/files/linux-release/
---
@ -56,7 +54,7 @@ steps:
- name: build
image: feather:appimage
commands:
- export FN="feather-`git rev-parse --short HEAD`.zip"
- export FN="feather-`echo $DRONE_COMMIT_AFTER | cut -c 1-7`.zip"
- export BRANCH="$DRONE_SOURCE_BRANCH"
- bash ./contrib/build-appimage.sh
- name: deploy
@ -75,7 +73,7 @@ steps:
volumes:
- name: files_linux_appimage
host:
path: /mnt/storage1/feather_files/files/linux-release-appimage/
path: /build/feather_files/files/linux-release-appimage/
---
@ -94,11 +92,9 @@ steps:
commands:
- git config --global url."http://gitea:3000/tor/".insteadOf https://git.torproject.org/
- git config --global url."http://gitea:3000/".insteadOf https://github.com/
- git submodule update --init --depth 50 contrib/tor
- git submodule update --init --depth 50 contrib/torsocks
- git submodule update --init --depth 120 monero
- git submodule update --init --depth 120 --recursive monero
- PATH=/mxe/usr/bin/:$PATH make -j8 windows-mxe-release
- PATH="/mxe/usr/bin/:$PATH" TOR="/mxe/usr/x86_64-w64-mingw32.static/bin/tor.exe" XMRIG="/xmrig/xmrig.exe" make -j6 windows-mxe-release
environment:
CMAKEFLAGS_EXTRA: -DFETCH_DEPS=Off
- name: deploy
@ -109,7 +105,7 @@ steps:
- name: files_win_release
path: /files
commands:
- export FN="feather-`git rev-parse --short HEAD`.zip"
- export FN="feather-`echo $DRONE_COMMIT_AFTER | cut -c 1-7`.zip"
- export TARGET_DIR="/files/$DRONE_SOURCE_BRANCH"
- mkdir -p "$TARGET_DIR"
- echo "writing to $TARGET_DIR/$FN"
@ -122,7 +118,7 @@ volumes:
path: /var/drone/ccache_win_release/
- name: files_win_release
host:
path: /mnt/storage1/feather_files/files/windows-mxe-release/
path: /build/feather_files/files/windows-mxe-release/
---
@ -147,7 +143,7 @@ steps:
- name: files_mac_release
path: /files
commands:
- export FN="feather-`git rev-parse --short HEAD`.zip"
- export FN="feather-`echo $DRONE_COMMIT_AFTER | cut -c 1-7`.zip"
- export TARGET_DIR="/files/$DRONE_SOURCE_BRANCH"
- mkdir -p "$TARGET_DIR"
- echo "writing to $TARGET_DIR/$FN"
@ -157,9 +153,9 @@ steps:
volumes:
- name: files_mac_release
host:
path: /mnt/storage1/feather_files/files/mac-release/
path: /build/feather_files/files/mac-release/
---
kind: signature
hmac: 91e773a27d27f29ea62f5df500664b733df641b7c31202c8e5558174fd046fba
hmac: 527d334190a8a824b3b781a05ae4c7d87f4fa2bc37ebc53a96db91f925fa4a52
...

3
.gitmodules vendored

@ -7,3 +7,6 @@
[submodule "contrib/tor"]
path = contrib/tor
url = https://git.torproject.org/tor.git
[submodule "contrib/KDMacTouchBar"]
path = contrib/KDMacTouchBar
url = https://github.com/KDAB/KDMacTouchBar.git

@ -35,7 +35,7 @@ Note: You only need to build the base image once.
#### 3. Build
```bash
docker run --rm -it -v /tmp/ccache:/root/.ccache -v /root/feather:/feather -w /feather feather:win /bin/bash -c 'PATH=/mxe/usr/bin/:$PATH make windows-mxe-release -j8'
docker run --rm -it -v /tmp/ccache:/root/.ccache -v /root/feather:/feather -w /feather feather:win /bin/bash -c 'PATH="/mxe/usr/bin/:$PATH" TOR="/mxe/usr/x86_64-w64-mingw32.static/bin/tor.exe" XMRIG="/xmrig/xmrig.exe" make windows-mxe-release -j8'
```
Replace `PATH_TO_FEATHER` with the absolute path to Feather locally.
@ -68,7 +68,7 @@ Note: You only need to build the base image once.
#### 3. Build
```bash
docker run --env OPENSSL_ROOT_DIR=/usr/local/openssl/ --rm -it -v /tmp/ccache:/root/.ccache -v PATH_TO_FEATHER:/feather -w /feather feather:linux sh -c 'make release-static -j8'
docker run --env OPENSSL_ROOT_DIR=/usr/local/openssl/ --rm -it -v /tmp/ccache:/root/.ccache -v PATH_TO_FEATHER:/feather -w /feather feather:linux sh -c 'TOR="/usr/local/tor/bin/tor" XMRIG="/xmrig/xmrig" make release-static -j8'
```
Replace `PATH_TO_FEATHER` with the absolute path to Feather locally.
@ -98,4 +98,4 @@ Build Feather.
CMAKE_PREFIX_PATH=~/Qt5.15.1/5.15.1/clang_64 make mac-release
```
The resulting Mac OS application can be found `build/bin/feather.app`.
The resulting Mac OS application can be found `build/bin/feather.app` and will **not** have Tor embedded.

@ -11,7 +11,10 @@ set(VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_REVISION}")
option(FETCH_DEPS "Download dependencies if they are not found" ON)
option(XMRTO "Include Xmr.To module" ON)
option(BUILD_TOR "Build Tor" OFF)
option(MORPHTOKEN "Include MorphToken module" ON)
option(XMRIG "Path to XMRig binary to embed inside Feather" OFF)
option(TOR "Path to Tor binary to embed inside Feather" OFF)
option(TOR_VERSION "Optional git hash or tag of embedded Tor version" "tor-0.4.3.5")
option(STATIC "Link libraries statically, requires static Qt")
option(USE_DEVICE_TREZOR "Trezor support compilation" OFF)
option(DONATE_BEG "Prompt donation window every once in a while" ON)
@ -28,7 +31,7 @@ if(DEBUG)
set(CMAKE_VERBOSE_MAKEFILE ON)
endif()
set(MONERO_HEAD "a1404e92cb439ba0f120e7c4a579ed0b9a0372a4")
set(MONERO_HEAD "2fc0c6355d7f3756f9cc01f1165aeec42bc52201")
set(BUILD_GUI_DEPS ON)
set(ARCH "x86-64")
set(BUILD_64 ON)
@ -157,7 +160,11 @@ find_package(Boost 1.58 REQUIRED COMPONENTS
locale)
if(UNIX AND NOT APPLE)
set(CMAKE_SKIP_RPATH ON)
if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
# https://github.com/monero-project/monero-gui/issues/3142#issuecomment-705940446
set(CMAKE_SKIP_RPATH ON)
endif()
find_package(X11 REQUIRED)
message(STATUS "X11_FOUND = ${X11_FOUND}")
message(STATUS "X11_INCLUDE_DIR = ${X11_INCLUDE_DIR}")
@ -170,47 +177,48 @@ if(UNIX AND NOT APPLE)
endif()
endif()
# Tor/torsocks
set(TOR_TAG "tor-0.4.3.5")
set(TOR_DIR "${CMAKE_SOURCE_DIR}/contrib/tor")
if("$ENV{DRONE}" STREQUAL "true")
message(STATUS "We are inside a static compile with Drone CI")
endif()
# To build Feather with embedded (and static) Tor, pass CMake -DTOR=/path/to/tor
if(TOR)
if(APPLE)
execute_process(COMMAND bash -c "touch ${CMAKE_CURRENT_SOURCE_DIR}/src/tor/libevent-2.1.7.dylib")
endif()
if(BUILD_TOR AND APPLE)
execute_process(COMMAND bash -c "touch ${CMAKE_SOURCE_DIR}/src/tor/libevent-2.1.7.dylib")
ENDIF()
# on the buildbot Tor is baked into the image
# - linux: See `Dockerfile`
# - windows: https://github.com/mxe/mxe/blob/1024dc7d2db5eb7d5d3c64a2c12b5f592572f1ce/plugins/apps/tor.mk
# - macos: taken from Tor Browser official release
set(TOR_COPY_CMD "cp ${TOR} ${CMAKE_CURRENT_SOURCE_DIR}/src/assets/exec/tor")
message(STATUS "${TOR_COPY_CMD}")
execute_process(COMMAND bash -c "${TOR_COPY_CMD}" RESULT_VARIABLE ret)
if(ret EQUAL "1")
message(FATAL_ERROR "Tor copy failure: ${TOR_COPY_CMD}")
endif()
if(UNIX AND NOT APPLE)
set(TOR_LIB "libtorsocks.so")
elseif(APPLE)
set(TOR_LIB "libtorsocks.dylib")
message(STATUS "Embedding Tor binary at ${TOR}")
else()
message(STATUS "Skipping Tor inclusion because -DTOR=Off")
endif()
if("$ENV{DRONE}" STREQUAL "true" AND APPLE)
message(STATUS "We are inside a static compile with Drone CI")
# @TODO: taken from Tor Browser official release for now
execute_process(COMMAND bash -c "cp ~/tor/libevent-2.1.7.dylib ${CMAKE_SOURCE_DIR}/src/tor/libevent-2.1.7.dylib")
execute_process(COMMAND bash -c "cp ~/tor/tor ${CMAKE_SOURCE_DIR}/src/tor/tor")
elseif("$ENV{DRONE}" STREQUAL "true" AND BUILD_TOR)
message(STATUS "We are inside a static compile with Drone CI")
if(MINGW)
execute_process(COMMAND bash -c "cp /mxe/usr/x86_64-w64-mingw32.static/bin/tor.exe ${CMAKE_SOURCE_DIR}/src/tor/tor.exe")
elseif(UNIX AND NOT APPLE)
execute_process(COMMAND bash -c "cp /usr/local/tor/bin/tor ${CMAKE_SOURCE_DIR}/src/tor/tor")
execute_process(COMMAND bash -c "cp /usr/local/torsocks/lib/torsocks/* ${CMAKE_SOURCE_DIR}/src/tor/")
# To build Feather with embedded (and static) XMRig, pass CMake -DXMRIG=/path/to/xmrig
if(XMRIG)
# on the buildbot XMRig is baked into the image
# - linux: See `Dockerfile`
# - windows: See `Dockerfile_windows`
# - macos: manually downloaded an official release
set(XMRIG_COPY_CMD "cp ${XMRIG} ${CMAKE_CURRENT_SOURCE_DIR}/src/assets/exec/xmrig")
message(STATUS "${XMRIG_COPY_CMD}")
execute_process(COMMAND bash -c "${XMRIG_COPY_CMD}" RESULT_VARIABLE ret)
if(ret EQUAL "1")
message(FATAL_ERROR "XMRig copy failure: ${XMRIG_COPY_CMD}")
endif()
message(STATUS "Embedding XMRig binary at ${XMRIG}")
else()
if(BUILD_TOR)
if(UNIX OR APPLE)
execute_process(COMMAND bash -c "ls -al src/tor/${TOR_LIB} 2>/dev/null" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE TOR_FOUND OUTPUT_STRIP_TRAILING_WHITESPACE)
if(TOR_FOUND)
message(STATUS "${TOR_LIB} found, skipping Tor build")
else()
message(STATUS "${TOR_LIB} not found, building Tor")
execute_process(COMMAND bash -c "bash build_tor.sh ${TOR_TAG} ${CMAKE_SOURCE_DIR} 'ON'" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/contrib)
endif()
endif()
else()
message(STATUS "Skipping Tor build because -DBUILD_TOR=OFF")
endif()
message(STATUS "Skipping XMRig inclusion because -DXMRIG=Off")
endif()
if(MINGW)
@ -358,4 +366,8 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11 ${C_SECURITY_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 ${CXX_SECURITY_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${LD_SECURITY_FLAGS} ${STATIC_FLAGS}")
if(APPLE)
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/contrib/KDMacTouchBar")
endif()
add_subdirectory(src)

@ -191,7 +191,7 @@ RUN cd /qt-everywhere-src-5.15.0 && \
-skip qtscript -skip qtscxml -skip qtsensors -skip qtserialbus -skip qtserialport -skip qtspeech -skip qttools \
-skip qtvirtualkeyboard -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebview \
-skip qtwinextras -skip qtx11extras -skip gamepad -skip serialbus -skip location -skip webengine \
-skip qtdeclarative -skip qtmultimedia \
-skip qtdeclarative \
-no-feature-cups -no-feature-ftp -no-feature-pdf -no-feature-animation \
-nomake examples -nomake tests -nomake tools
@ -305,3 +305,9 @@ RUN git clone https://git.wownero.com/feather/monero-seed.git && \
cmake -DCMAKE_BUILD_TYPE=Release -Bbuild && \
make -Cbuild -j$THREADS && \
make -Cbuild install
RUN apt install -y curl && \
curl -LO "https://github.com/xmrig/xmrig/releases/download/v6.3.5/xmrig-6.3.5-linux-static-x64.tar.gz" && \
echo "24d4f07cf5850f00ab513b228f95769a5a5ed68d35808d98f9959b58d97985a0 xmrig-6.3.5-linux-static-x64.tar.gz" > hashsum.txt && \
sha256sum -c hashsum.txt && \
tar xvf xmrig-6.3.5-linux-static-x64.tar.gz --one-top-level=/xmrig --strip 1

@ -50,7 +50,7 @@ RUN apt install -y \
RUN git clone -b feather-patch --depth 1 https://git.wownero.com/feather/mxe.git && \
cd mxe && \
make -j$THREADS MXE_TARGETS='x86_64-w64-mingw32.static' gcc libqrencode pkgconf libgpg_error libgcrypt cmake libsodium lzma readline libzmq boost qtbase qtsvg qtwebsockets qtimageformats
make -j$THREADS MXE_TARGETS='x86_64-w64-mingw32.static' gcc libqrencode pkgconf libgpg_error libgcrypt cmake libsodium lzma readline libzmq boost qtbase qtsvg qtwebsockets qtimageformats qtmultimedia
# plugins
RUN cd mxe && make -j$THREADS MXE_PLUGIN_DIRS='/mxe/plugins/apps/' MXE_TARGETS='x86_64-w64-mingw32.static' tor
@ -72,3 +72,10 @@ RUN git clone https://git.wownero.com/feather/monero-seed.git && \
cmake -DCMAKE_BUILD_TYPE=Release -Bbuild && \
make -Cbuild -j$THREADS && \
make -Cbuild install
RUN apt install -y curl && \
curl -LO "https://github.com/xmrig/xmrig/releases/download/v6.3.5/xmrig-6.3.5-gcc-win64.zip" && \
echo "e45915ada7e6e30f6ab40abf33831056449d5914307d7706bb0ad439b6d64c12 xmrig-6.3.5-gcc-win64.zip" > hashsum.txt && \
sha256sum -c hashsum.txt && \
unzip -q xmrig-6.3.5-gcc-win64.zip -d /xmrig && \
mv /xmrig/xmrig-6.3.5/* /xmrig/

@ -42,7 +42,9 @@ via the `CMAKE_PREFIX_PATH` definition. For me this is:
There are some Monero/Feather related options/definitions that you may pass:
- `-DXMRTO=OFF` - disable Xmr.To feature
- `-DBUILD_TOR=OFF` - disable embedded Tor
- `-DMORPHTOKEN=OFF` - diable MorphToken feature
- `-DTOR=/path/to/tor` - Embed a Tor executable inside Feather
- `-DXMRIG=/path/to/xmrig` - Embed a XMRig executable inside Feather
- `-DDONATE_BEG=OFF` - disable the dreaded donate requests
And:

@ -31,6 +31,9 @@ CMAKEFLAGS = \
-DBUILD_64=On \
-DBUILD_TESTS=Off \
-DXMRTO=ON \
-DMORPHTOKEN=ON \
-DXMRIG=Off \
-DTOR=Off \
-DCMAKE_CXX_STANDARD=11 \
-DCMAKE_VERBOSE_MAKEFILE=On \
-DINSTALL_VENDORED_LIBUNBOUND=Off \
@ -40,29 +43,33 @@ CMAKEFLAGS = \
$(CMAKEFLAGS_EXTRA)
release-static: CMAKEFLAGS += -DBUILD_TAG="linux-x64"
release-static: CMAKEFLAGS += -DBUILD_TOR=On
release-static: CMAKEFLAGS += -DTOR=$(or ${TOR},OFF)
release-static: CMAKEFLAGS += -DXMRIG=$(or ${XMRIG},OFF)
release-static: CMAKEFLAGS += -DCMAKE_BUILD_TYPE=Release
release-static:
cmake -Bbuild $(CMAKEFLAGS)
$(MAKE) -Cbuild
windows-mxe-release: CMAKEFLAGS += -DBUILD_TAG="win-x64"
windows-mxe-release: CMAKEFLAGS += -DBUILD_TOR=On
windows-mxe-release: CMAKEFLAGS += -DTOR=$(or ${TOR},OFF)
windows-mxe-release: CMAKEFLAGS += -DXMRIG=$(or ${XMRIG},OFF)
windows-mxe-release: CMAKEFLAGS += -DCMAKE_BUILD_TYPE=Release
windows-mxe-release:
cmake -Bbuild $(CMAKEFLAGS)
$(MAKE) -Cbuild
windows-mxe-debug: CMAKEFLAGS += -DBUILD_TAG="win-x64"
windows-mxe-debug: CMAKEFLAGS += -DBUILD_TOR=Off
windows-mxe-debug: CMAKEFLAGS += -DTOR=$(or ${TOR},OFF)
windows-mxe-debug: CMAKEFLAGS += -DXMRIG=$(or ${XMRIG},OFF)
windows-mxe-debug: CMAKEFLAGS += -DCMAKE_BUILD_TYPE=Debug
windows-mxe-debug:
cmake -Bbuild $(CMAKEFLAGS)
$(MAKE) -Cbuild
mac-release: CMAKEFLAGS += -DSTATIC=Off
mac-release: CMAKEFLAGS += -DTOR=$(or ${TOR},OFF)
mac-release: CMAKEFLAGS += -DXMRIG=$(or ${XMRIG},OFF)
mac-release: CMAKEFLAGS += -DBUILD_TAG="mac-x64"
mac-release: CMAKEFLAGS += -DBUILD_TOR=Off
mac-release: CMAKEFLAGS += -DCMAKE_BUILD_TYPE=Release
mac-release:
cmake -Bbuild $(CMAKEFLAGS)

@ -1,16 +1,15 @@
# Maintainer: wowario <wowario[at]protonmail[dot]com>
# Contributor: wowario <wowario[at]protonmail[dot]com>
pkgbase=('monero-feather-git')
pkgname=('monero-feather-git')
pkgver=v0.1.0.0.cd1cd5cb75
pkgrel=1
pkgdesc="a free Monero desktop wallet"
pkgbase='monero-feather-git'
pkgname='monero-feather-git'
pkgver='v0.1.0.0.ca5b1df7df'
pkgrel='1'
pkgdesc='a free Monero desktop wallet'
license=('BSD')
arch=('x86_64')
url="https://featherwallet.org"
depends=('boost-libs' 'libunwind' 'openssl' 'readline' 'zeromq' 'pcsclite' 'hidapi' 'protobuf' 'miniupnpc'
'libgcrypt' 'qrencode' 'ccache' 'libsodium' 'libpgm' 'expat' 'base-devel' 'qt5-base')
depends=('boost-libs' 'libunwind' 'openssl' 'readline' 'zeromq' 'pcsclite' 'hidapi' 'protobuf' 'miniupnpc' 'libgcrypt' 'qrencode' 'ccache' 'libsodium' 'libpgm' 'expat' 'qt5-base' 'qt5-websockets' 'tor')
makedepends=('git' 'cmake' 'boost')
provides=('monero-feather-git')
@ -18,17 +17,16 @@ source=("${pkgname}"::"git+https://git.wownero.com/feather/feather")
sha256sums=('SKIP')
pkgver() {
cd "$srcdir/$pkgname"
git describe --long --tags | sed 's/\([^-]*-\)g/r\1/;s/-/./g'
}
build() {
cd "${srcdir}/${pkgname}"
mkdir build && cd build && cmake .. && make release-static -j2
git submodule update --init --recursive
mkdir build
cd build
cmake ..
make -j2
}
package_feather-git() {
package_monero-feather-git() {
install -Dm644 "${srcdir}/${pkgname}/LICENSE" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
install -Dm755 "${srcdir}/${pkgname}/build/bin/feather" "${pkgdir}/usr/bin/feather"
}

@ -0,0 +1 @@
Subproject commit 470c4316460bb8c3e23bfa37c79c8621ef3f1b4c

@ -1 +1 @@
Subproject commit a1404e92cb439ba0f120e7c4a579ed0b9a0372a4
Subproject commit 2fc0c6355d7f3756f9cc01f1165aeec42bc52201

@ -42,14 +42,16 @@ file(GLOB SOURCE_FILES
"dialog/*.cpp"
)
if((APPLE AND BUILD_TOR) OR (APPLE AND "$ENV{DRONE}" STREQUAL "true"))
set(ASSETS_OS "assets_macos_tor.qrc")
elseif(UNIX AND NOT APPLE AND BUILD_TOR)
set(ASSETS_OS "assets_linux_tor.qrc")
elseif(MINGW AND BUILD_TOR)
set(ASSETS_OS "assets_windows_tor.qrc")
else()
message(STATUS "Building without embedded Tor")
if(TOR)
if(APPLE)
set(ASSETS_TOR "assets_tor_macos.qrc")
else()
set(ASSETS_TOR "assets_tor.qrc")
endif()
endif()
if(XMRIG)
set(ASSETS_XMRIG "assets_mining.qrc")
endif()
set(EXECUTABLE_FLAG)
@ -74,7 +76,8 @@ endif()
add_executable(feather ${EXECUTABLE_FLAG} main.cpp
${SOURCE_FILES}
${RESOURCES}
${ASSETS_OS}
${ASSETS_TOR}
${ASSETS_XMRIG}
)
# mac os bundle
@ -122,14 +125,22 @@ if(DONATE_BEG)
target_compile_definitions(feather PRIVATE DONATE_BEG=1)
endif()
if(BUILD_TOR)
target_compile_definitions(feather PRIVATE BUILD_TOR=1)
endif()
if(XMRTO)
target_compile_definitions(feather PRIVATE XMRTO=1)
endif()
if(MORPHTOKEN)
target_compile_definitions(feather PRIVATE HAS_MORPHTOKEN=1)
endif()
if(TOR)
target_compile_definitions(feather PRIVATE HAS_TOR=1)
endif()
if(XMRIG)
target_compile_definitions(feather PRIVATE HAS_XMRIG=1)
endif()
if(HAVE_SYS_PRCTL_H)
target_compile_definitions(feather PRIVATE HAVE_SYS_PRCTL_H=1)
endif()
@ -194,6 +205,14 @@ target_link_libraries(feather
${QRENCODE_LIBRARY}
)
if(APPLE)
target_link_libraries(feather
KDMacTouchBar
)
target_include_directories(feather
PUBLIC ../contrib/KDMacTouchBar)
endif()
if(NOT APPLE)
target_link_libraries(feather
Qt5::QSvgIconPlugin

@ -0,0 +1,226 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020, The Monero Project.
#include "MorphTokenWidget.h"
#include "ui_MorphTokenWidget.h"
#include "mainwindow.h"
#include "qrcode/QrCode.h"
#include "dialog/qrcodedialog.h"
#include <QMessageBox>
MorphTokenWidget::MorphTokenWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::MorphTokenWidget)
{
ui->setupUi(this);
m_ctx = MainWindow::getContext();
m_network = new UtilsNetworking(this->m_ctx->network);
m_api = new MorphTokenApi(this, m_network);
connect(ui->btnCreateTrade, &QPushButton::clicked, this, &MorphTokenWidget::createTrade);
connect(ui->btn_lookupTrade, &QPushButton::clicked, this, &MorphTokenWidget::lookupTrade);
connect(ui->btn_getRates, &QPushButton::clicked, this, &MorphTokenWidget::getRates);
connect(m_api, &MorphTokenApi::ApiResponse, this, &MorphTokenWidget::onApiResponse);
connect(ui->combo_From, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index){
this->displayRate();
ui->label_refundAddress->setText(QString("Refund address (%1):").arg(ui->combo_From->currentText()));
});
connect(ui->combo_To, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index){
this->displayRate();
ui->label_destinationAddress->setText(QString("Destination address (%1):").arg(ui->combo_To->currentText()));
});
connect(ui->check_autorefresh, &QCheckBox::toggled, [this](bool toggled){
m_countdown = 30;
toggled ? m_countdownTimer.start(1000) : m_countdownTimer.stop();
ui->check_autorefresh->setText("Autorefresh");
});
connect(&m_countdownTimer, &QTimer::timeout, this, &MorphTokenWidget::onCountdown);
connect(ui->line_Id, &QLineEdit::textChanged, [this](const QString &text){
ui->btn_lookupTrade->setEnabled(!text.isEmpty());
ui->check_autorefresh->setEnabled(!text.isEmpty());
});
// Default to BTC -> XMR
ui->combo_From->setCurrentIndex(1);
ui->combo_To->setCurrentIndex(0);
ui->label_rate->setVisible(false);
m_ratesTimer.setSingleShot(true);
connect(&m_ratesTimer, &QTimer::timeout, [this]{
ui->label_rate->setVisible(false);
});
ui->qrCode->setVisible(false);
ui->label_depositAddress->setVisible(false);
connect(ui->qrCode, &ClickableLabel::clicked, this, &MorphTokenWidget::showQrCodeDialog);
ui->tabWidget->setTabVisible(2, false);
}
void MorphTokenWidget::createTrade() {
QString inputAsset = ui->combo_From->currentText();
QString outputAsset = ui->combo_To->currentText();
QString refundAddress = ui->line_refundAddress->text();
QString destinationAddress = ui->line_destinationAddress->text();
m_api->createTrade(inputAsset, outputAsset, refundAddress, destinationAddress);
}
void MorphTokenWidget::lookupTrade() {
QString morphId = ui->line_Id->text();
if (!morphId.isEmpty())
m_api->getTrade(morphId);
}
void MorphTokenWidget::getRates() {
m_api->getRates();
}
void MorphTokenWidget::onApiResponse(const MorphTokenApi::MorphTokenResponse &resp) {
if (!resp.ok) {
ui->check_autorefresh->setChecked(false);
QMessageBox::warning(this, "MorphToken error", QString("Request failed:\n\n%1").arg(resp.message));
return;
}
ui->debugInfo->setPlainText(QJsonDocument(resp.obj).toJson(QJsonDocument::Indented));
bool shouldShowQr = (resp.endpoint == MorphTokenApi::Endpoint::CREATE_TRADE || resp.endpoint == MorphTokenApi::Endpoint::GET_TRADE);
ui->qrCode->setVisible(shouldShowQr);
ui->label_depositAddress->setVisible(shouldShowQr);
if (resp.endpoint == MorphTokenApi::Endpoint::CREATE_TRADE || resp.endpoint == MorphTokenApi::Endpoint::GET_TRADE) {
ui->tabWidget->setCurrentIndex(1);
ui->line_Id->setText(resp.obj.value("id").toString());
auto obj = resp.obj;
auto input = obj["input"].toObject();
auto output = obj["output"].toArray()[0].toObject();
QString state = obj.value("state").toString();
QString statusText;
ui->trade->setTitle(QString("Trade (%1)").arg(state));
statusText += QString("Morph ID: %1\n\n").arg(obj["id"].toString());
if (state == "PENDING") {
statusText += QString("Waiting for a deposit, send %1 to %2\n").arg(input["asset"].toString(),
input["deposit_address"].toString());
statusText += QString("Rate: 1 %1 -> %2 %3\n\n").arg(input["asset"].toString(),
output["seen_rate"].toString(),
output["asset"].toString());
statusText += "Limits:\n";
statusText += QString(" Minimum amount accepted: %1 %2\n").arg(formatAmount(input["asset"].toString(), input["limits"].toObject()["min"].toDouble()),
input["asset"].toString());
statusText += QString(" Maximum amount accepted: %1 %2\n").arg(formatAmount(input["asset"].toString(), input["limits"].toObject()["max"].toDouble()),
input["asset"].toString());
statusText += QString("\nSend a single deposit. If the amount is outside the limits, a refund will happen.");
m_depositAddress = input["deposit_address"].toString();
const QrCode qrc(m_depositAddress, QrCode::Version::AUTO, QrCode::ErrorCorrectionLevel::MEDIUM);
int width = ui->qrCode->width();
if (qrc.isValid()) {
ui->qrCode->setPixmap(qrc.toPixmap(1).scaled(width, width, Qt::KeepAspectRatio));
}
} else if (state == "PROCESSING" || state == "TRADING" || state == "CONFIRMING") {
if (state == "CONFIRMING") {
statusText += QString("Waiting for confirmations\n");
} else if (state == "TRADING") {
statusText += QString("Your transaction has been received and is confirmed. MorphToken is now executing your trade.\n"
"Usually this step takes no longer than a minute, "
"but in rare cases it can take a couple hours.\n"
"Wait a bit before contacting support.\n");
}
statusText += QString("Converting %1 to %2\n").arg(input["asset"].toString(), output["asset"].toString());
statusText += QString("Sending to %1\n").arg(output["address"].toString());
statusText += QString("Stuck? Contact support at contact@morphtoken.com");
} else if (state == "COMPLETE") {
if (output["txid"].toString().isEmpty()) {
statusText += QString("MorphToken is sending your transaction.\n");
statusText += QString("MorphToken will send %1 %2 to %2").arg(this->formatAmount(output["asset"].toString(), output["converted_amount"].toDouble() - output["network_fee"].toObject()["fee"].toDouble()),
output["asset"].toString(),
output["address"].toString());
} else {
statusText += QString("Sent %1 %2 to %3\ntxid: {}").arg(this->formatAmount(output["asset"].toString(), output["converted_amount"].toDouble() - output["network_fee"].toObject()["fee"].toDouble()),
output["asset"].toString(),
output["address"].toString(),
output["txid"].toString());
}
} else if (state == "PROCESSING_REFUND" || state == "COMPLETE_WITH_REFUND") {
statusText += QString("MorphToken will refund %1 %2\nReason: %3\n").arg(obj["final_amount"].toString(),
obj["asset"].toString(),
obj["reason"].toString());
if (obj.contains("txid")) {
statusText += QString("txid: %1").arg(obj["txid"].toString());
}
} else if (state == "COMPLETE_WITHOUT_REFUND") {
statusText += "Deposit amount below network fee, too small to refund.";
}
ui->label_status->setText(statusText);
} else if (resp.endpoint == MorphTokenApi::Endpoint::GET_RATES) {
m_rates = resp.obj.value("data").toObject();
this->displayRate();
ui->label_rate->setVisible(true);
m_ratesTimer.start(120 * 1000);
}
if (resp.endpoint == MorphTokenApi::Endpoint::CREATE_TRADE) {
QMessageBox::information(this, "MorphToken", "Trade created!\n\nMake sure to save your Morph ID. You may need it in case something goes wrong.");
}
}
void MorphTokenWidget::onCountdown() {
if (m_countdown > 0) {
m_countdown -= 1;
} else {
this->lookupTrade();
m_countdown = 30;
}
ui->check_autorefresh->setText(QString("Autorefresh (%1)").arg(m_countdown));
}
void MorphTokenWidget::displayRate() {
QString inputAsset = ui->combo_From->currentText();
QString outputAsset = ui->combo_To->currentText();
QString outputRate = m_rates.value(inputAsset).toObject().value(outputAsset).toString("1");
QString rateStr = QString("1 %1 -> %2 %3").arg(inputAsset, outputRate, outputAsset);
ui->label_rate->setText(rateStr);
}
void MorphTokenWidget::showQrCodeDialog() {
QrCode qr(m_depositAddress, QrCode::Version::AUTO, QrCode::ErrorCorrectionLevel::HIGH);
auto *dialog = new QrCodeDialog(this, qr, "Deposit address");
dialog->exec();
dialog->deleteLater();
}
QString MorphTokenWidget::formatAmount(const QString &asset, double amount) {
double displayAmount;
double div;
if (asset == "ETH")
div = 1e18;
else if (asset == "XMR")
div = 1e12;
else
div = 1e8;
displayAmount = amount / div;
return QString::number(displayAmount, 'f', 8);
}
MorphTokenWidget::~MorphTokenWidget() {
delete ui;
}

@ -0,0 +1,47 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020, The Monero Project.
#ifndef FEATHER_MORPHTOKENWIDGET_H
#define FEATHER_MORPHTOKENWIDGET_H
#include <QWidget>
#include "appcontext.h"
#include "utils/MorphTokenApi.h"
namespace Ui {
class MorphTokenWidget;
}
class MorphTokenWidget : public QWidget
{
Q_OBJECT
public:
explicit MorphTokenWidget(QWidget *parent = nullptr);
~MorphTokenWidget() override;
private:
void createTrade();
void lookupTrade();
void getRates();
void onApiResponse(const MorphTokenApi::MorphTokenResponse &resp);
void onCountdown();
void displayRate();
void showQrCodeDialog();
QString formatAmount(const QString &asset, double amount);
Ui::MorphTokenWidget *ui;
AppContext *m_ctx;
MorphTokenApi *m_api;
UtilsNetworking *m_network;
QTimer m_countdownTimer;
int m_countdown = 30;
QJsonObject m_rates;
QTimer m_ratesTimer;
QString m_depositAddress;
};
#endif //FEATHER_MORPHTOKENWIDGET_H

@ -0,0 +1,423 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MorphTokenWidget</class>
<widget class="QWidget" name="MorphTokenWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1036</width>
<height>614</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>1</number>
</property>
<widget class="QWidget" name="tabCreateTrade">
<attribute name="title">
<string>Create trade</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>From:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="combo_From">
<item>
<property name="text">
<string>XMR</string>
</property>
</item>
<item>
<property name="text">
<string>BTC</string>
</property>
</item>
<item>
<property name="text">
<string>ETH</string>
</property>
</item>
<item>
<property name="text">
<string>BCH</string>
</property>
</item>
<item>
<property name="text">
<string>LTC</string>
</property>
</item>
<item>
<property name="text">
<string>DASH</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>To:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="combo_To">
<item>
<property name="text">
<string>XMR</string>
</property>
</item>
<item>
<property name="text">
<string>BTC</string>
</property>
</item>
<item>
<property name="text">
<string>ETH</string>
</property>
</item>
<item>
<property name="text">
<string>BCH</string>
</property>
</item>
<item>
<property name="text">
<string>LTC</string>
</property>
</item>
<item>
<property name="text">
<string>DASH</string>
</property>
</item>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_rate">
<property name="text">
<string>rate</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_refundAddress">
<property name="text">
<string>Refund address (XMR):</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="line_refundAddress"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_destinationAddress">
<property name="text">
<string>Destination address (XMR):</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="line_destinationAddress"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_4">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Powered by MorphToken.com</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btn_getRates">
<property name="text">
<string>Get Rates</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnCreateTrade">
<property name="text">
<string>Create Trade</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabLookupTrade">
<attribute name="title">
<string>Lookup trade</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Morph ID or MorphToken deposit address:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="line_Id"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QCheckBox" name="check_autorefresh">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Autorefresh</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btn_lookupTrade">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Lookup trade</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="trade">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Trade</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_status">
<property name="text">
<string>No trade loaded.</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="ClickableLabel" name="qrCode">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>qrcode</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_depositAddress">
<property name="text">
<string>Deposit address</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabDebug">
<attribute name="title">
<string>Debug</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QPlainTextEdit" name="debugInfo">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>ClickableLabel</class>
<extends>QLabel</extends>
<header>components.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

@ -9,12 +9,14 @@
#include <QDesktopWidget>
#include "appcontext.h"
#include "globals.h"
#include "utils/tails.h"
#include "utils/whonix.h"
#include "utils/utils.h"
#include "utils/prices.h"
#include "utils/networktype.h"
#include "utils/wsclient.h"
#include "utils/config.h"
// libwalletqt
#include "libwalletqt/WalletManager.h"
@ -28,7 +30,6 @@
#include "model/SubaddressModel.h"
#include "utils/keysfiles.h"
#include "utils/networktype.h"
#include "utils/config.h"
Prices *AppContext::prices = nullptr;
@ -112,7 +113,6 @@ AppContext::AppContext(QCommandLineParser *cmdargs) {
this->nodes = new Nodes(this, this->networkClearnet);
connect(this, &AppContext::nodeSourceChanged, this->nodes, &Nodes::onNodeSourceChanged);
connect(this, &AppContext::setCustomNodes, this->nodes, &Nodes::setCustomNodes);
connect(this, &AppContext::walletClosing, this->nodes, &Nodes::onWalletClosing);
// Tor & socks proxy
this->ws = new WSClient(this, m_wsUrl);
@ -147,10 +147,16 @@ AppContext::AppContext(QCommandLineParser *cmdargs) {
AppContext::prices = new Prices();
// xmr.to
#if defined(XMRTO)
#ifdef XMRTO
this->XMRTo = new XmrTo(this);
#endif
// XMRig
#ifdef HAS_XMRIG
this->XMRig = new XmRig(this->configDirectory, this);
this->XMRig->prepare();
#endif
this->walletManager = WalletManager::instance();
QString logPath = QString("%1/daemon.log").arg(configDirectory);
Monero::Utils::onStartup();
@ -173,7 +179,7 @@ void AppContext::initTor() {
this->tor = new Tor(this, this);
this->tor->start();
if (!(isTails || isWhonix)) {
if (!(isWhonix)) {
auto networkProxy = new QNetworkProxy(QNetworkProxy::Socks5Proxy, Tor::torHost, Tor::torPort);
this->network->setProxy(*networkProxy);
if (m_wsUrl.host().endsWith(".onion"))
@ -188,7 +194,7 @@ void AppContext::initWS() {
void AppContext::onCancelTransaction(PendingTransaction *tx, const QString &address) {
// tx cancelled by user
double amount = tx->amount() / AppContext::cdiv;
double amount = tx->amount() / globals::cdiv;
emit createTransactionCancelled(address, amount);
this->currentWallet->disposeTransaction(tx);
}
@ -227,8 +233,8 @@ void AppContext::onCreateTransaction(const QString &address, const double amount
return;
}
auto balance = this->currentWallet->balance() / AppContext::cdiv;
auto unlocked_balance = this->currentWallet->unlockedBalance() / AppContext::cdiv;
auto balance = this->currentWallet->balance() / globals::cdiv;
auto unlocked_balance = this->currentWallet->unlockedBalance() / globals::cdiv;
if(!all && amount > unlocked_balance) {
emit createTransactionError("Not enough money to spend");
return;
@ -237,7 +243,7 @@ void AppContext::onCreateTransaction(const QString &address, const double amount
return;
}
auto amount_num = static_cast<quint64>(amount * AppContext::cdiv);
auto amount_num = static_cast<quint64>(amount * globals::cdiv);
qDebug() << "creating tx";
if(all || amount == balance)
this->currentWallet->createTransactionAllAsync(address, "", this->tx_mixin, this->tx_priority);
@ -253,7 +259,6 @@ void AppContext::onCreateTransactionError(const QString &msg) {
}
void AppContext::walletClose(bool emitClosedSignal) {
this->nodes->stopTimer();
if(this->currentWallet == nullptr) return;
emit walletClosing();
//ctx->currentWallet->store(); @TODO: uncomment to store on wallet close
@ -276,6 +281,10 @@ void AppContext::onOpenWallet(const QString &path, const QString &password){
return;
}
if (password.isEmpty()) {
this->walletPassword = "";
}
config()->set(Config::firstRun, false);
this->walletPath = path;
@ -310,13 +319,14 @@ void AppContext::onWalletOpened(Wallet *wallet) {
emit walletOpenedError(errMsg);
} else {
this->walletClose(false);
emit walletOpenPasswordNeeded(this->walletPassword.isEmpty());
emit walletOpenPasswordNeeded(!this->walletPassword.isEmpty(), wallet->path());
}
return;
}
this->currentWallet = wallet;
this->walletPath = this->currentWallet->path() + ".keys";
this->walletViewOnly = this->currentWallet->viewOnly();
config()->set(Config::walletPath, this->walletPath);
connect(this->currentWallet, &Wallet::moneySpent, this, &AppContext::onMoneySpent);
@ -330,9 +340,12 @@ void AppContext::onWalletOpened(Wallet *wallet) {
connect(this->currentWallet, &Wallet::transactionCreated, this, &AppContext::onTransactionCreated);
connect(this->currentWallet, &Wallet::connectionStatusChanged, this, &AppContext::onConnectionStatusChanged);
this->nodes->connectToNode();
emit walletOpened();
connect(this->currentWallet, &Wallet::connectionStatusChanged, [this]{
this->nodes->autoConnect();
});
this->nodes->connectToNode();
this->updateBalance();
#ifdef DONATE_BEG
@ -341,6 +354,18 @@ void AppContext::onWalletOpened(Wallet *wallet) {
// force trigger preferredFiat signal for history model
this->onPreferredFiatCurrencyChanged(config()->get(Config::preferredFiatCurrency).toString());
this->setWindowTitle();
}
void AppContext::setWindowTitle(bool mining) {
QFileInfo fileInfo(this->walletPath);
auto title = QString("Feather - [%1]").arg(fileInfo.fileName());
if(this->walletViewOnly)
title += " [view-only]";
if(mining)
title += " [mining]";
emit setTitle(title);
}
void AppContext::onWSMessage(const QJsonObject &msg) {
@ -380,7 +405,11 @@ void AppContext::onWSMessage(const QJsonObject &msg) {
else if(cmd == "nodes") {
this->onWSNodes(msg.value("data").toArray());
}
#if defined(HAS_XMRIG)
else if(cmd == "xmrig") {
this->XMRigDownloads(msg.value("data").toObject());
}
#endif
else if(cmd == "crypto_rates") {
QJsonArray crypto_rates = msg.value("data").toArray();
AppContext::prices->cryptoPricesReceived(crypto_rates);
@ -434,7 +463,8 @@ void AppContext::onWSNodes(const QJsonArray &nodes) {
auto node = new FeatherNode(
obj.value("address").toString(),
(unsigned int)obj.value("height").toInt(),
obj.value("height").toInt(),
obj.value("target_height").toInt(),
obj.value("online").toBool());
QSharedPointer<FeatherNode> r = QSharedPointer<FeatherNode>(node);
l.append(r);
@ -494,31 +524,17 @@ void AppContext::onWSCCS(const QJsonArray &ccs_data) {
}
void AppContext::createConfigDirectory(const QString &dir) {
if(!Utils::dirExists(dir)) {
qDebug() << QString("Creating directory: %1").arg(dir);
if(!QDir().mkpath(dir))
throw std::runtime_error("Could not create directory " + dir.toStdString());
}
QString config_dir_tor = QString("%1%2").arg(dir).arg("tor");
if(!Utils::dirExists(config_dir_tor)) {
qDebug() << QString("Creating directory: %1").arg(config_dir_tor);
if (!QDir().mkpath(config_dir_tor))
throw std::runtime_error("Could not create directory " + config_dir_tor.toStdString());
}
QString config_dir_tordata = QString("%1%2").arg(dir).arg("tor/data");
if(!Utils::dirExists(config_dir_tordata)) {
qDebug() << QString("Creating directory: %1").arg(config_dir_tordata);
if (!QDir().mkpath(config_dir_tordata))
throw std::runtime_error("Could not create directory " + config_dir_tordata.toStdString());
}
QString config_dir_torsocks = QString("%1%2").arg(dir).arg("torsocks");
if(!Utils::dirExists(config_dir_torsocks)) {
qDebug() << QString("Creating directory: %1").arg(config_dir_torsocks);
if (!QDir().mkpath(config_dir_torsocks))
throw std::runtime_error("Could not create directory " + config_dir_torsocks.toStdString());
QString config_dir_xmrig = QString("%1%2").arg(dir).arg("xmrig");
QStringList createDirs({dir, config_dir_tor, config_dir_tordata, config_dir_xmrig});
for(const auto &d: createDirs) {
if(!Utils::dirExists(d)) {
qDebug() << QString("Creating directory: %1").arg(d);
if (!QDir().mkpath(d))
throw std::runtime_error("Could not create directory " + d.toStdString());
}
}
}
@ -541,6 +557,43 @@ void AppContext::createWallet(FeatherSeed seed, const QString &path, const QStri
return;
}
this->createWalletFinish(password);
}
void AppContext::createWalletViewOnly(const QString &path, const QString &password, const QString &address, const QString &viewkey, const QString &spendkey, quint64 restoreHeight) {
if(Utils::fileExists(path)) {
auto err = QString("Failed to write wallet to path: \"%1\"; file already exists.").arg(path);
qCritical() << err;
emit walletCreatedError(err);
return;
}
if(!this->walletManager->addressValid(address, this->networkType)) {
auto err = QString("Failed to create wallet. Invalid address provided.").arg(path);
qCritical() << err;
emit walletCreatedError(err);
return;
}
if(!this->walletManager->keyValid(viewkey, address, true, this->networkType)) {
auto err = QString("Failed to create wallet. Invalid viewkey provided.").arg(path);
qCritical() << err;
emit walletCreatedError(err);
return;
}
if(!spendkey.isEmpty() && !this->walletManager->keyValid(spendkey, address, false, this->networkType)) {
auto err = QString("Failed to create wallet. Invalid spendkey provided.").arg(path);
qCritical() << err;
emit walletCreatedError(err);
return;
}
this->currentWallet = this->walletManager->createWalletFromKeys(path, this->seedLanguage, this->networkType, address, viewkey, spendkey, restoreHeight);
this->createWalletFinish(password);
}
void AppContext::createWalletFinish(const QString &password) {
this->currentWallet->setPassword(password);
this->currentWallet->store();
this->walletPassword = password;
@ -562,7 +615,7 @@ void AppContext::onSetRestoreHeight(unsigned int height){
}
this->currentWallet->setWalletCreationHeight(height);
this->currentWallet->setPassword(this->walletPassword); // trigger .keys write
this->currentWallet->setPassword(this->currentWallet->getPassword()); // trigger .keys write
// nuke wallet cache
const auto fn = this->currentWallet->path();
@ -614,8 +667,9 @@ void AppContext::onOpenAliasResolve(const QString &openAlias) {
}
void AppContext::donateBeg() {
if(this->networkType != NetworkType::Type::MAINNET)
return;
if(this->currentWallet == nullptr) return;
if(this->networkType != NetworkType::Type::MAINNET) return;
if(this->currentWallet->viewOnly()) return;
auto donationCounter = config()->get(Config::donateBeg).toInt();
if(donationCounter == -1)
@ -643,19 +697,19 @@ AppContext::~AppContext() {
// ############################################## LIBWALLET QT #########################################################
void AppContext::onMoneySpent(const QString &txId, quint64 amount) {
auto amount_num = amount / AppContext::cdiv;
auto amount_num = amount / globals::cdiv;
qDebug() << Q_FUNC_INFO << txId << " " << QString::number(amount_num);
}
void AppContext::onMoneyReceived(const QString &txId, quint64 amount) {
// Incoming tx included in a block.
auto amount_num = amount / AppContext::cdiv;
auto amount_num = amount / globals::cdiv;
qDebug() << Q_FUNC_INFO << txId << " " << QString::number(amount_num);
}
void AppContext::onUnconfirmedMoneyReceived(const QString &txId, quint64 amount) {
// Incoming transaction in pool
auto amount_num = amount / AppContext::cdiv;
auto amount_num = amount / globals::cdiv;
qDebug() << Q_FUNC_INFO << txId << " " << QString::number(amount_num);
if(this->currentWallet->synchronized()) {
@ -673,13 +727,15 @@ void AppContext::onWalletUpdate() {
this->storeWallet();
}
void AppContext::onWalletRefreshed() {
void AppContext::onWalletRefreshed(bool success) {
if (!this->refreshed) {
refreshModels();
this->refreshed = true;
this->storeWallet();
}
qDebug() << "Wallet refresh status: " << success;
this->currentWallet->refreshHeightAsync();
}
@ -696,7 +752,7 @@ void AppContext::onWalletNewBlock(quint64 blockheight, quint64 targetHeight) {
void AppContext::onHeightRefreshed(quint64 walletHeight, quint64 daemonHeight, quint64 targetHeight) {
qDebug() << Q_FUNC_INFO << walletHeight << daemonHeight << targetHeight;
if (!this->currentWallet->connected())
if (this->currentWallet->connectionStatus() == Wallet::ConnectionStatus_Disconnected)
return;
if (daemonHeight < targetHeight) {
@ -742,12 +798,12 @@ void AppContext::storeWallet() {
void AppContext::updateBalance() {
if(!this->currentWallet)
throw std::runtime_error("this should not happen, ever");
return;
AppContext::balance = this->currentWallet->balance() / AppContext::cdiv;
AppContext::balance = this->currentWallet->balance() / globals::cdiv;
auto balance_str = QString::number(balance, 'f');
double unlocked = this->currentWallet->unlockedBalance() / AppContext::cdiv;
double unlocked = this->currentWallet->unlockedBalance() / globals::cdiv;
auto unlocked_str = QString::number(unlocked, 'f');
emit balanceUpdated(balance, unlocked, balance_str, unlocked_str);

@ -14,6 +14,7 @@
#include "utils/networking.h"
#include "utils/tor.h"
#include "utils/xmrto.h"
#include "utils/xmrig.h"
#include "utils/wsclient.h"
#include "utils/txfiathistory.h"
#include "widgets/RedditPost.h"
@ -56,6 +57,7 @@ public:
QString walletPath;
QString walletPassword = "";
bool walletViewOnly = false;
NetworkType::Type networkType;
QString applicationPath;
@ -67,7 +69,7 @@ public:
const unsigned int kdfRounds = 1;
PendingTransaction::Priority tx_priority = PendingTransaction::Priority::Priority_Low;
quint32 tx_mixin = static_cast<const quint32 &>(10);
static constexpr const double cdiv = 1e12;
QString seedLanguage = "English"; // 14 word `monero-seed` only has English
QNetworkAccessManager *network;
QNetworkAccessManager *networkClearnet;
@ -76,6 +78,7 @@ public:
Tor *tor;
WSClient *ws;
XmrTo *XMRTo;
XmRig *XMRig;
Nodes *nodes;
static Prices *prices;
static WalletKeysFilesModel *wallets;
@ -89,6 +92,8 @@ public:
WalletManager *walletManager;
Wallet *currentWallet = nullptr;
void createWallet(FeatherSeed seed, const QString &path, const QString &password);
void createWalletViewOnly(const QString &path, const QString &password, const QString &address, const QString &viewkey, const QString &spendkey, quint64 restoreHeight);
void createWalletFinish(const QString &password);
void syncStatusUpdated(quint64 height, quint64 target);
void updateBalance();
void initTor();
@ -98,6 +103,7 @@ public:
void walletClose(bool emitClosedSignal = true);
void storeWallet();
void refreshModels();
void setWindowTitle(bool mining = false);
public slots:
void onOpenWallet(const QString& path, const QString &password);
@ -120,7 +126,7 @@ private slots:
void onMoneyReceived(const QString &txId, quint64 amount);
void onUnconfirmedMoneyReceived(const QString &txId, quint64 amount);
void onWalletUpdate();
void onWalletRefreshed();
void onWalletRefreshed(bool success);
void onWalletOpened(Wallet *wallet);
void onWalletNewBlock(quint64 blockheight, quint64 targetHeight);
void onHeightRefreshed(quint64 walletHeight, quint64 daemonHeight, quint64 targetHeight);
@ -140,7 +146,7 @@ signals:
void walletCreatedError(const QString &msg);
void walletCreated(Wallet *wallet);
void walletOpenedError(QString msg);
void walletOpenPasswordNeeded(bool invalidPassword);
void walletOpenPasswordNeeded(bool invalidPassword, QString path);
void transactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid);
void createTransactionError(QString message);
void createTransactionCancelled(QString address, double amount);
@ -149,6 +155,7 @@ signals:
void nodesUpdated(QList<QSharedPointer<FeatherNode>> &nodes);
void ccsUpdated(QList<QSharedPointer<CCSEntry>> &entries);
void nodeSourceChanged(NodeSource nodeSource);
void XMRigDownloads(const QJsonObject &data);
void setCustomNodes(QList<FeatherNode> nodes);
void ccsEmpty();
void openAliasResolveError(const QString &msg);
@ -160,13 +167,14 @@ signals:
void initiateTransaction();
void endTransaction();
void walletClosing();
void setTitle(const QString &title); // set window title
private:
void sorry();
const unsigned int m_donationBoundary = 15;
UtilsNetworking *m_utilsNetworkingNodes;
QTimer *m_storeTimer = new QTimer(this);
QUrl m_wsUrl = QUrl(QStringLiteral("ws://dtg2clrd6iand4mwp2x6nhbqd3nqbxlbiw65f6vkwmmutxy2sijsnjyd.onion/ws"));
QUrl m_wsUrl = QUrl(QStringLiteral("ws://7e6egbawekbkxzkv4244pqeqgoo4axko2imgjbedwnn6s5yb6b7oliqd.onion/ws"));
};
#endif //FEATHER_APPCONTEXT_H

@ -23,6 +23,7 @@
<file>assets/images/coldcard.png</file>
<file>assets/images/coldcard_unpaired.png</file>
<file>assets/images/confirmed.png</file>
<file>assets/images/confirmed_icon.png</file>
<file>assets/images/confirmed.svg</file>
<file>assets/images/connect.svg</file>
<file>assets/images/copy.png</file>
@ -30,10 +31,13 @@
<file>assets/images/edit.png</file>
<file>assets/images/exchange.png</file>
<file>assets/images/expired.png</file>
<file>assets/images/expired_icon.png</file>
<file>assets/images/eye1.png</file>
<file>assets/images/eye_blind.png</file>
<file>assets/images/feather.png</file>
<file>assets/images/file.png</file>
<file>assets/images/ghost.png</file>
<file>assets/images/ghost_icon.png</file>
<file>assets/images/history.png</file>
<file>assets/images/info.png</file>
<file>assets/images/key.png</file>
@ -41,8 +45,10 @@
<file>assets/images/ledger_unpaired.png</file>
<file>assets/images/lightning.png</file>
<file>assets/images/lock.png</file>
<file>assets/images/lock_icon.png</file>
<file>assets/images/lock.svg</file>
<file>assets/images/microphone.png</file>
<file>assets/images/morphtoken.png</file>
<file>assets/images/network.png</file>
<file>assets/images/offline_tx.png</file>
<file>assets/images/person.svg</file>
@ -91,6 +97,8 @@
<file>assets/images/warning.png</file>
<file>assets/images/xmrto_big.png</file>
<file>assets/images/xmrto.png</file>
<file>assets/images/xmrig.ico</file>
<file>assets/images/xmrig.svg</file>
<file>assets/images/zoom.png</file>
<file>assets/mnemonic_25_english.txt</file>
<file>assets/restore_heights_monero_mainnet.txt</file>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 496.06 496.06"><title>XMRig</title><defs><style>.a{fill:none;}.b{clip-path:url(#a);}.c{fill:#ec671a;}.d{fill:#575756;}.e{fill:#fff;}</style><clipPath id="a" transform="translate(-106.3 -106.3)"><circle class="a" cx="354.33" cy="354.33" r="248.03"/></clipPath></defs><title>Xr_icon</title><g class="b"><rect class="c" width="496.06" height="496.06"/><polygon class="d" points="496.06 496.06 0 496.06 0 387.21 124.02 387.55 189.12 291.44 254.23 387.55 496.06 387.55 496.06 496.06"/><path class="e" d="M481.44,441.14c0-13.18,0-27,0-37.32,0-54.11,22.15-77.57,55.82-52.71-0.39.07,27.9-43.41,27.9-43.41-35.68-24.09-67.09-10.17-86.81,19.45V295.42H428.74V441.14H391.12l-61-91.46,89.91-134.87H351.85L296,298.52l-55.81-83.71H172l89.91,134.87-61,91.46H106.3v52.37l59.65,0.16-0.12.18H234l62-93,62,93H602.36V441.14H481.44Z" transform="translate(-106.3 -106.3)"/></g></svg>

After

Width:  |  Height:  |  Size: 955 B

@ -1,6 +0,0 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource prefix="/">
<file>tor/tor</file>
<file>tor/libevent-2.1.7.dylib</file>
</qresource>
</RCC>

@ -1,5 +1,5 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource prefix="/">
<file>tor/tor.exe</file>
<file>assets/exec/xmrig</file>
</qresource>
</RCC>

@ -1,5 +1,5 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource prefix="/">
<file>tor/tor</file>
<file>assets/exec/tor</file>
</qresource>
</RCC>

@ -0,0 +1,6 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource prefix="/">
<file>assets/exec/tor</file>
<file>assets/exec/libevent-2.1.7.dylib</file>
</qresource>
</RCC>

@ -58,7 +58,7 @@ void CLI::onWalletOpenedError(const QString &err) {
return this->finishedError(err);
}
void CLI::onWalletOpenPasswordRequired(bool invalidPassword) {
void CLI::onWalletOpenPasswordRequired(bool invalidPassword, const QString &path) {
if(mode == CLIMode::CLIModeExportContacts ||
mode == CLIMode::CLIModeExportTxHistory)
return this->finishedError("invalid password");

@ -26,7 +26,7 @@ public slots:
//libwalletqt
void onWalletOpened();
void onWalletOpenedError(const QString& err);
void onWalletOpenPasswordRequired(bool invalidPassword);
void onWalletOpenPasswordRequired(bool invalidPassword, const QString &path);
private:
AppContext *ctx;

@ -7,6 +7,7 @@
#include "utils/utils.h"
#include "dialog/outputinfodialog.h"
#include "dialog/outputsweepdialog.h"
#include "mainwindow.h"
#include <QClipboard>
#include <QDebug>
@ -21,6 +22,7 @@ CoinsWidget::CoinsWidget(QWidget *parent)
, m_copyMenu(new QMenu("Copy",this))
{
ui->setupUi(this);
m_ctx = MainWindow::getContext();
// header context menu
ui->coins->header()->setContextMenuPolicy(Qt::CustomContextMenu);
@ -40,14 +42,14 @@ CoinsWidget::CoinsWidget(QWidget *parent)
// context menu
ui->coins->setContextMenuPolicy(Qt::CustomContextMenu);
m_thawOutputAction = new QAction("Thaw output");
m_freezeOutputAction = new QAction("Freeze output");
m_thawOutputAction = new QAction("Thaw output", this);
m_freezeOutputAction = new QAction("Freeze output", this);
m_freezeAllSelectedAction = new QAction("Freeze selected");
m_thawAllSelectedAction = new QAction("Thaw selected");
m_freezeAllSelectedAction = new QAction("Freeze selected", this);
m_thawAllSelectedAction = new QAction("Thaw selected", this);
m_viewOutputAction = new QAction(QIcon(":/assets/images/info.png"), "Details");
m_sweepOutputAction = new QAction("Sweep output");
m_viewOutputAction = new QAction(QIcon(":/assets/images/info.png"), "Details", this);
m_sweepOutputAction = new QAction("Sweep output", this);
connect(m_freezeOutputAction, &QAction::triggered, this, &CoinsWidget::freezeOutput);
connect(m_thawOutputAction, &QAction::triggered, this, &CoinsWidget::thawOutput);
connect(m_viewOutputAction, &QAction::triggered, this, &CoinsWidget::viewOutput);
@ -63,13 +65,19 @@ CoinsWidget::CoinsWidget(QWidget *parent)
void CoinsWidget::setModel(CoinsModel * model, Coins * coins) {
m_coins = coins;
m_model = model;
m_proxyModel = new CoinsProxyModel;
m_proxyModel = new CoinsProxyModel(this);
m_proxyModel->setSourceModel(m_model);
ui->coins->setModel(m_proxyModel);
ui->coins->setColumnHidden(CoinsModel::Spent, true);
ui->coins->setColumnHidden(CoinsModel::SpentHeight, true);
ui->coins->setColumnHidden(CoinsModel::Frozen, true);
if (!m_ctx->currentWallet->viewOnly()) {
ui->coins->setColumnHidden(CoinsModel::KeyImageKnown, true);
} else {
ui->coins->setColumnHidden(CoinsModel::KeyImageKnown, false);
}
ui->coins->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
ui->coins->header()->setSectionResizeMode(CoinsModel::AddressLabel, QHeaderView::Stretch);
ui->coins->header()->setSortIndicator(CoinsModel::BlockHeight, Qt::DescendingOrder);
@ -103,10 +111,15 @@ void CoinsWidget::showContextMenu(const QPoint &point) {
if (!isSpent) {
isFrozen ? menu->addAction(m_thawOutputAction) : menu->addAction(m_freezeOutputAction);
}
if (!isFrozen && isUnlocked) {
menu->addAction(m_sweepOutputAction);
if (isFrozen || !isUnlocked) {
m_sweepOutputAction->setDisabled(true);
} else {
m_sweepOutputAction->setEnabled(true);
}
}
menu->addAction(m_viewOutputAction);
}
@ -194,6 +207,7 @@ void CoinsWidget::onSweepOutput() {
qCritical() << "key image: " << keyImage;
emit sweepOutput(keyImage, dialog->address(), dialog->churn(), dialog->outputs());
dialog->deleteLater();
}
void CoinsWidget::copy(copyField field) {

@ -67,6 +67,7 @@ private:
Coins *m_coins;
CoinsModel * m_model;
CoinsProxyModel * m_proxyModel;
AppContext *m_ctx;
void showContextMenu(const QPoint & point);
void copy(copyField field);

@ -68,9 +68,7 @@ HelpLabel::HelpLabel(QWidget *parent) : QLabel(parent)
void HelpLabel::mouseReleaseEvent(QMouseEvent *event)
{
Q_UNUSED(event)
QMessageBox msgBox(QApplication::activeWindow());
msgBox.setText(this->help_text);
msgBox.exec();
QMessageBox::information(this, "Help", this->help_text);
}
void HelpLabel::enterEvent(QEvent *event)

@ -53,7 +53,7 @@
<item>
<widget class="QPlainTextEdit" name="copyrightText">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>

@ -0,0 +1,62 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020, The Monero Project.
#include "broadcasttxdialog.h"
#include "ui_broadcasttxdialog.h"
#include <QMessageBox>
BroadcastTxDialog::BroadcastTxDialog(QWidget *parent, AppContext *ctx)
: QDialog(parent)
, m_ctx(ctx)
, ui(new Ui::BroadcastTxDialog)
{
ui->setupUi(this);
m_network = new UtilsNetworking(m_ctx->network, this);
auto node = ctx->nodes->connection();
m_rpc = new DaemonRpc(this, m_network, node.full);
connect(ui->btn_Broadcast, &QPushButton::clicked, this, &BroadcastTxDialog::broadcastTx);
connect(ui->btn_Close, &QPushButton::clicked, this, &BroadcastTxDialog::reject);
connect(m_rpc, &DaemonRpc::ApiResponse, this, &BroadcastTxDialog::onApiResponse);
this->adjustSize();
}
void BroadcastTxDialog::broadcastTx() {
QString tx = ui->transaction->toPlainText();
QString node = [this]{
QString node;
if (ui->radio_useCustom->isChecked())
node = ui->customNode->text();
else
node = m_ctx->nodes->connection().full;
if (!node.startsWith("http://"))
node = QString("http://%1").arg(node);
return node;
}();
m_rpc->setDaemonAddress(node);
m_rpc->sendRawTransaction(tx);
}
void BroadcastTxDialog::onApiResponse(const DaemonRpc::DaemonResponse &resp) {
if (!resp.ok) {
QMessageBox::warning(this, "Transaction broadcast", resp.status);
return;
}
if (resp.endpoint == DaemonRpc::Endpoint::SEND_RAW_TRANSACTION) {
QMessageBox::information(this, "Transaction broadcast", "Transaction submitted successfully.\n\n"
"If the transaction belongs to this wallet it may take several minutes before it shows up in the history tab.");
}
}
BroadcastTxDialog::~BroadcastTxDialog() {
delete ui;
}

@ -0,0 +1,35 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020, The Monero Project.
#ifndef FEATHER_BROADCASTTXDIALOG_H
#define FEATHER_BROADCASTTXDIALOG_H
#include <QDialog>
#include "appcontext.h"
#include "utils/daemonrpc.h"
namespace Ui {
class BroadcastTxDialog;
}
class BroadcastTxDialog : public QDialog
{
Q_OBJECT
public:
explicit BroadcastTxDialog(QWidget *parent, AppContext *ctx);
~BroadcastTxDialog() override;
private slots:
void broadcastTx();
void onApiResponse(const DaemonRpc::DaemonResponse &resp);
private:
Ui::BroadcastTxDialog *ui;
UtilsNetworking *m_network;
AppContext *m_ctx;
DaemonRpc *m_rpc;
};
#endif //FEATHER_BROADCASTTXDIALOG_H

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>BroadcastTxDialog</class>
<widget class="QDialog" name="BroadcastTxDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>797</width>
<height>575</height>
</rect>
</property>
<property name="windowTitle">
<string>Broadcast transaction</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Transaction:</string>
</property>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="transaction">
<property name="minimumSize">
<size>
<width>500</width>
<height>0</height>
</size>
</property>
<property name="placeholderText">
<string>Paste hex encoded signed tx string here</string>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Node</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QRadioButton" name="radio_useDefault">
<property name="text">
<string>Use currently connected node</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radio_useCustom">
<property name="text">
<string>Use custom node</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="customNode"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>All transactions are broadcast over Tor</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btn_Broadcast">
<property name="text">
<string>Broadcast</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_Close">
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

@ -12,15 +12,26 @@
DebugInfoDialog::DebugInfoDialog(AppContext *ctx, QWidget *parent)
: QDialog(parent)
, ui(new Ui::DebugInfoDialog)
, m_ctx(ctx)
{
ui->setupUi(this);
connect(ui->btn_Copy, &QPushButton::clicked, this, &DebugInfoDialog::copyToClipboad);
m_updateTimer.start(5000);
connect(&m_updateTimer, &QTimer::timeout, this, &DebugInfoDialog::updateInfo);
this->updateInfo();
this->adjustSize();
}
void DebugInfoDialog::updateInfo() {
QString torStatus;
if(ctx->isTorSocks)
if(m_ctx->isTorSocks)
torStatus = "Torsocks";
else if(ctx->tor->localTor)
else if(m_ctx->tor->localTor)
torStatus = "Local (assumed to be running)";
else if(ctx->tor->torConnected)
else if(m_ctx->tor->torConnected)
torStatus = "Running";
else
torStatus = "Unknown";
@ -28,27 +39,28 @@ DebugInfoDialog::DebugInfoDialog(AppContext *ctx, QWidget *parent)
ui->label_featherVersion->setText(QString("%1-%2").arg(FEATHER_VERSION, FEATHER_BRANCH));
ui->label_moneroVersion->setText(QString("%1-%2").arg(MONERO_VERSION, MONERO_BRANCH));
ui->label_walletHeight->setText(QString::number(ctx->currentWallet->blockChainHeight()));
ui->label_daemonHeight->setText(QString::number(ctx->currentWallet->daemonBlockChainHeight()));
ui->label_restoreHeight->setText(QString::number(ctx->currentWallet->getWalletCreationHeight()));
ui->label_synchronized->setText(ctx->currentWallet->synchronized() ? "True" : "False");
ui->label_walletHeight->setText(QString::number(m_ctx->currentWallet->blockChainHeight()));
ui->label_daemonHeight->setText(QString::number(m_ctx->currentWallet->daemonBlockChainHeight()));
ui->label_targetHeight->setText(QString::number(m_ctx->currentWallet->daemonBlockChainTargetHeight()));
ui->label_restoreHeight->setText(QString::number(m_ctx->currentWallet->getWalletCreationHeight()));
ui->label_synchronized->setText(m_ctx->currentWallet->synchronized() ? "True" : "False");
auto node = ctx->nodes->connection();
auto node = m_ctx->nodes->connection();
ui->label_remoteNode->setText(node.full);
ui->label_walletStatus->setText(this->statusToString(ctx->currentWallet->connected()));
ui->label_walletStatus->setText(this->statusToString(m_ctx->currentWallet->connectionStatus()));
ui->label_torStatus->setText(torStatus);
ui->label_websocketStatus->setText(Utils::QtEnumToString(ctx->ws->webSocket.state()));
ui->label_websocketStatus->setText(Utils::QtEnumToString(m_ctx->ws->webSocket.state()));
ui->label_netType->setText(Utils::QtEnumToString(ctx->currentWallet->nettype()));
ui->label_seedType->setText(ctx->currentWallet->getCacheAttribute("feather.seed").isEmpty() ? "25 word" : "14 word");
ui->label_viewOnly->setText(ctx->currentWallet->viewOnly() ? "True" : "False");
ui->label_netType->setText(Utils::QtEnumToString(m_ctx->currentWallet->nettype()));
ui->label_seedType->setText(m_ctx->currentWallet->getCacheAttribute("feather.seed").isEmpty() ? "25 word" : "14 word");
ui->label_viewOnly->setText(m_ctx->currentWallet->viewOnly() ? "True" : "False");
ui->label_OS->setText(QSysInfo::prettyProductName());
QString os = QSysInfo::prettyProductName();
if (m_ctx->isTails) {
os = QString("Tails %1").arg(TailsOS::version());
}
ui->label_OS->setText(os);
ui->label_timestamp->setText(QString::number(QDateTime::currentSecsSinceEpoch()));
connect(ui->btn_Copy, &QPushButton::clicked, this, &DebugInfoDialog::copyToClipboad);
this->adjustSize();
}
QString DebugInfoDialog::statusToString(Wallet::ConnectionStatus status) {
@ -67,26 +79,28 @@ QString DebugInfoDialog::statusToString(Wallet::ConnectionStatus status) {
}
void DebugInfoDialog::copyToClipboad() {
// Two spaces at the end of each line are for newlines in Markdown
QString text = "";
text += QString("Feather version: %1\n").arg(ui->label_featherVersion->text());
text += QString("Monero version: %1\n").arg(ui->label_moneroVersion->text());
text += QString("Wallet height: %1\n").arg(ui->label_walletHeight->text());
text += QString("Daemon height: %1\n").arg(ui->label_daemonHeight->text());
text += QString("Restore height: %1\n").arg(ui->label_restoreHeight->text());
text += QString("Synchronized: %1\n").arg(ui->label_synchronized->text());
text += QString("Remote node: %1\n").arg(ui->label_remoteNode->text());
text += QString("Wallet status: %1\n").arg(ui->label_walletStatus->text());
text += QString("Tor status: %1\n").arg(ui->label_torStatus->text());
text += QString("Websocket status: %1\n").arg(ui->label_websocketStatus->text());
text += QString("Network type: %1\n").arg(ui->label_netType->text());
text += QString("Seed type: %1\n").arg(ui->label_seedType->text());
text += QString("View only: %1\n").arg(ui->label_viewOnly->text());
text += QString("Operating system: %1\n").arg(ui->label_OS->text());
text += QString("Timestamp: %1\n").arg(ui->label_timestamp->text());
text += QString("Feather version: %1 \n").arg(ui->label_featherVersion->text());
text += QString("Monero version: %1 \n").arg(ui->label_moneroVersion->text());
text += QString("Wallet height: %1 \n").arg(ui->label_walletHeight->text());
text += QString("Daemon height: %1 \n").arg(ui->label_daemonHeight->text());
text += QString("Target height: %1 \n").arg(ui->label_targetHeight->text());
text += QString("Restore height: %1 \n").arg(ui->label_restoreHeight->text());
text += QString("Synchronized: %1 \n").arg(ui->label_synchronized->text());
text += QString("Remote node: %1 \n").arg(ui->label_remoteNode->text());
text += QString("Wallet status: %1 \n").arg(ui->label_walletStatus->text());
text += QString("Tor status: %1 \n").arg(ui->label_torStatus->text());
text += QString("Websocket status: %1 \n").arg(ui->label_websocketStatus->text());
text += QString("Network type: %1 \n").arg(ui->label_netType->text());
text += QString("Seed type: %1 \n").arg(ui->label_seedType->text());
text += QString("View only: %1 \n").arg(ui->label_viewOnly->text());
text += QString("Operating system: %1 \n").arg(ui->label_OS->text());
text += QString("Timestamp: %1 \n").arg(ui->label_timestamp->text());
Utils::copyToClipboard(text);
}

@ -23,6 +23,10 @@ public:
private:
QString statusToString(Wallet::ConnectionStatus status);
void copyToClipboad();
void updateInfo();
QTimer m_updateTimer;
AppContext *m_ctx;
Ui::DebugInfoDialog *ui;
};

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>693</width>
<height>580</height>
<height>612</height>
</rect>
</property>
<property name="windowTitle">
@ -91,14 +91,14 @@
</property>
</widget>
</item>
<item row="5" column="0">
<item row="6" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Restore height:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<item row="6" column="1">
<widget class="QLabel" name="label_restoreHeight">
<property name="text">
<string>TextLabel</string>
@ -108,14 +108,14 @@
</property>
</widget>
</item>
<item row="6" column="0">
<item row="7" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Synchronized:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<item row="7" column="1">
<widget class="QLabel" name="label_synchronized">
<property name="text">
<string>TextLabel</string>
@ -125,21 +125,21 @@
</property>
</widget>
</item>
<item row="7" column="1">
<item row="8" column="1">
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="8" column="0">
<item row="9" column="0">
<widget class="QLabel" name="label_27">
<property name="text">
<string>Remote node:</string>
</property>
</widget>
</item>
<item row="8" column="1">
<item row="9" column="1">
<widget class="QLabel" name="label_remoteNode">
<property name="text">
<string>TextLabel</string>
@ -149,14 +149,14 @@
</property>
</widget>
</item>
<item row="9" column="0">
<item row="10" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>Wallet status:</string>
</property>
</widget>
</item>
<item row="9" column="1">
<item row="10" column="1">
<widget class="QLabel" name="label_walletStatus">
<property name="text">
<string>TextLabel</string>
@ -166,14 +166,14 @@
</property>
</widget>
</item>
<item row="10" column="0">
<item row="11" column="0">
<widget class="QLabel" name="label_18">
<property name="text">
<string>Tor status:</string>
</property>
</widget>
</item>
<item row="10" column="1">
<item row="11" column="1">
<widget class="QLabel" name="label_torStatus">
<property name="text">
<string>TextLabel</string>
@ -183,14 +183,14 @@
</property>
</widget>
</item>
<item row="11" column="0">
<item row="12" column="0">
<widget class="QLabel" name="label_19">
<property name="text">
<string>Websocket status:</string>
</property>
</widget>
</item>
<item row="11" column="1">
<item row="12" column="1">
<widget class="QLabel" name="label_websocketStatus">
<property name="text">
<string>TextLabel</string>
@ -200,21 +200,21 @@
</property>
</widget>
</item>
<item row="12" column="1">
<item row="13" column="1">
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="13" column="0">
<item row="14" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Network type:</string>
</property>
</widget>
</item>
<item row="13" column="1">
<item row="14" column="1">
<widget class="QLabel" name="label_netType">
<property name="text">
<string>TextLabel</string>
@ -224,14 +224,14 @@
</property>
</widget>
</item>
<item row="14" column="0">
<item row="15" column="0">
<widget class="QLabel" name="label_23">
<property name="text">
<string>Seed type:</string>
</property>
</widget>
</item>
<item row="14" column="1">
<item row="15" column="1">
<widget class="QLabel" name="label_seedType">
<property name="text">
<string>TextLabel</string>
@ -241,14 +241,14 @@
</property>
</widget>
</item>
<item row="15" column="0">
<item row="16" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>View only:</string>
</property>
</widget>
</item>
<item row="15" column="1">
<item row="16" column="1">
<widget class="QLabel" name="label_viewOnly">
<property name="text">
<string>TextLabel</string>
@ -258,14 +258,14 @@
</property>
</widget>
</item>
<item row="18" column="0">
<item row="19" column="0">
<widget class="QLabel" name="label_24">
<property name="text">
<string>Timestamp:</string>
</property>
</widget>
</item>
<item row="18" column="1">
<item row="19" column="1">
<widget class="QLabel" name="label_timestamp">
<property name="text">
<string>TextLabel</string>
@ -275,27 +275,47 @@
</property>
</widget>
</item>
<item row="17" column="0">
<item row="18" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Operating system:</string>
</property>
</widget>
</item>
<item row="17" column="1">
<item row="18" column="1">
<widget class="QLabel" name="label_OS">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="16" column="1">
<item row="17" column="1">
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Target height:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLabel" name="label_targetHeight">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</item>
<item>

@ -5,17 +5,39 @@
#include "ui_passwordchangedialog.h"
#include <QPushButton>
#include <QMessageBox>
PasswordChangeDialog::PasswordChangeDialog(QWidget *parent)
PasswordChangeDialog::PasswordChangeDialog(QWidget *parent, Wallet *wallet)
: QDialog(parent)
, ui(new Ui::PasswordChangeDialog)
, m_wallet(wallet)
{
ui->setupUi(this);
ui->icon->setPixmap(QPixmap(":/assets/images/lock.png").scaledToWidth(32, Qt::SmoothTransformation));
bool noPassword = wallet->getPassword().isEmpty();
QString warning_str = noPassword ? "Your wallet is not password protected. Use this dialog to add a password to your wallet." :
"Your wallet is password protected and encrypted. Use this dialog to change your password.";
ui->label_warning->setText(warning_str);
QPixmap pixmap = noPassword ? QPixmap(":/assets/images/unlock.png") : QPixmap(":/assets/images/lock.png");
ui->icon->setPixmap(pixmap.scaledToWidth(32, Qt::SmoothTransformation));
if (noPassword) {
ui->label_currentPassword->hide();
ui->lineEdit_currentPassword->hide();
}
connect(ui->lineEdit_newPassword, &QLineEdit::textChanged, this, &PasswordChangeDialog::passwordsMatch);
connect(ui->lineEdit_confirmPassword, &QLineEdit::textChanged, this, &PasswordChangeDialog::passwordsMatch);
connect(ui->btn_Cancel, &QPushButton::clicked, [this]{
this->reject();
});
connect(ui->btn_OK, &QPushButton::clicked, this, &PasswordChangeDialog::setPassword);
ui->label_match->setVisible(false);
this->adjustSize();
}
@ -24,15 +46,28 @@ PasswordChangeDialog::~PasswordChangeDialog()
delete ui;
}
QString PasswordChangeDialog::getCurrentPassword() {
return ui->lineEdit_currentPassword->text();
void PasswordChangeDialog::passwordsMatch() {
bool match = ui->lineEdit_newPassword->text() == ui->lineEdit_confirmPassword->text();
ui->btn_OK->setEnabled(match);
ui->label_match->setHidden(match);
}
QString PasswordChangeDialog::getNewPassword() {
return ui->lineEdit_newPassword->text();
}
void PasswordChangeDialog::setPassword() {
QString currentPassword = ui->lineEdit_currentPassword->text();
QString newPassword = ui->lineEdit_newPassword->text();
void PasswordChangeDialog::passwordsMatch() {
bool match = ui->lineEdit_newPassword->text() == ui->lineEdit_confirmPassword->text();
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(match);
if (currentPassword != m_wallet->getPassword()) {
QMessageBox::warning(this, "Error", "Incorrect password");
ui->lineEdit_currentPassword->setText("");
ui->lineEdit_currentPassword->setFocus();
return;
}
if (m_wallet->setPassword(newPassword)) {
QMessageBox::information(this, "Information", "Password changed successfully");
this->accept();
}
else {
QMessageBox::warning(this, "Error", QString("Error: %1").arg(m_wallet->errorString()));
}
}

@ -5,6 +5,7 @@
#define FEATHER_PASSWORDCHANGEDIALOG_H
#include <QDialog>
#include "libwalletqt/Wallet.h"
namespace Ui {
class PasswordChangeDialog;
@ -15,16 +16,15 @@ class PasswordChangeDialog : public QDialog
Q_OBJECT
public:
explicit PasswordChangeDialog(QWidget *parent = nullptr);
explicit PasswordChangeDialog(QWidget *parent, Wallet *wallet);
~PasswordChangeDialog() override;
QString getCurrentPassword();
QString getNewPassword();
private:
Ui::PasswordChangeDialog *ui;
Wallet *m_wallet;
void passwordsMatch();
void setPassword();
};
#endif //FEATHER_PASSWORDCHANGEDIALOG_H

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>500</width>
<height>237</height>
<width>556</width>
<height>309</height>
</rect>
</property>
<property name="sizePolicy">
@ -30,7 +30,7 @@
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<widget class="QLabel" name="label_warning">
<property name="text">
<string>Your wallet is password protected and encrypted. Use this dialog to change your password.</string>
</property>
@ -41,6 +41,22 @@
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>10</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
@ -65,7 +81,7 @@
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<widget class="QLabel" name="label_currentPassword">
<property name="text">
<string>Current Password:</string>
</property>
@ -88,14 +104,45 @@
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_match">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Passwords do not match</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btn_Cancel">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_OK">
<property name="text">
<string>OK</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
@ -105,38 +152,5 @@
<tabstop>lineEdit_confirmPassword</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>PasswordChangeDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>PasswordChangeDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
<connections/>
</ui>

@ -0,0 +1,26 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020, The Monero Project.
#include "passworddialog.h"
#include "ui_passworddialog.h"
PasswordDialog::PasswordDialog(QWidget *parent, const QString &walletName, bool incorrectPassword)
: QDialog(parent)
, ui(new Ui::PasswordDialog)
{
ui->setupUi(this);
ui->label_wallet->setText(QString("Please enter password for wallet: %1").arg(walletName));
ui->label_incorrectPassword->setVisible(incorrectPassword);
connect(ui->buttonBox, &QDialogButtonBox::accepted, [this]{
password = ui->line_password->text();
});
this->adjustSize();
}
PasswordDialog::~PasswordDialog()
{
delete ui;
}

@ -0,0 +1,27 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020, The Monero Project.
#ifndef FEATHER_PASSWORDDIALOG_H
#define FEATHER_PASSWORDDIALOG_H
#include <QDialog>
namespace Ui {
class PasswordDialog;
}
class PasswordDialog : public QDialog
{
Q_OBJECT
public:
explicit PasswordDialog(QWidget *parent, const QString &walletName, bool incorrectPassword);
~PasswordDialog() override;
QString password = "";
private:
Ui::PasswordDialog *ui;
};
#endif //FEATHER_PASSWORDDIALOG_H

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PasswordDialog</class>
<widget class="QDialog" name="PasswordDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>832</width>
<height>158</height>
</rect>
</property>
<property name="windowTitle">
<string>Password required</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_wallet">
<property name="text">
<string>Please enter password for wallet: </string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_incorrectPassword">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; color:#a40000;&quot;&gt;Incorrect password, try again.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="line_password">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>PasswordDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>PasswordDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

@ -9,15 +9,14 @@
#include <QFileDialog>
#include <QMessageBox>
QrCodeDialog::QrCodeDialog(QWidget *parent, const QString &text, const QString &title)
QrCodeDialog::QrCodeDialog(QWidget *parent, const QrCode &qrCode, const QString &title)
: QDialog(parent)
, ui(new Ui::QrCodeDialog)
{
ui->setupUi(this);
this->setWindowTitle(title);
m_qrc = new QrCode(text, QrCode::Version::AUTO, QrCode::ErrorCorrectionLevel::HIGH);
m_pixmap = m_qrc->toPixmap(1).scaled(500, 500, Qt::KeepAspectRatio);
m_pixmap = qrCode.toPixmap(1).scaled(500, 500, Qt::KeepAspectRatio);
ui->QrCode->setPixmap(m_pixmap);
connect(ui->btn_CopyImage, &QPushButton::clicked, this, &QrCodeDialog::copyImage);
@ -32,7 +31,11 @@ QrCodeDialog::QrCodeDialog(QWidget *parent, const QString &text, const QString &
QrCodeDialog::~QrCodeDialog()
{
delete ui;
delete m_qrc;
}
void QrCodeDialog::setQrCode(const QrCode &qrCode) {
m_pixmap = qrCode.toPixmap(1).scaled(500, 500, Qt::KeepAspectRatio);
ui->QrCode->setPixmap(m_pixmap);
}
void QrCodeDialog::copyImage() {

@ -16,15 +16,15 @@ class QrCodeDialog : public QDialog
Q_OBJECT
public:
explicit QrCodeDialog(QWidget *parent, const QString &text, const QString &title = "Qr Code");
explicit QrCodeDialog(QWidget *parent, const QrCode &qrCode, const QString &title = "Qr Code");
~QrCodeDialog() override;
void setQrCode(const QrCode &qrCode);
private:
void copyImage();
void saveImage();
Ui::QrCodeDialog *ui;
QrCode *m_qrc;
QPixmap m_pixmap;
};

@ -4,27 +4,51 @@
#include "ui_seeddialog.h"
#include "seeddialog.h"
SeedDialog::SeedDialog(const QString &seed, QWidget *parent)
SeedDialog::SeedDialog(Wallet *wallet, QWidget *parent)
: QDialog(parent)
, ui(new Ui::SeedDialog)
{
ui->setupUi(this);
ui->label_seedIcon->setPixmap(QPixmap(":/assets/images/seed.png").scaledToWidth(64, Qt::SmoothTransformation));
ui->label_restoreHeight->setText(QString::number(wallet->getWalletCreationHeight()));
QString seed_14_words = wallet->getCacheAttribute("feather.seed");
QString seed_25_words = wallet->getSeed();
if (seed_14_words.isEmpty()) {
ui->check_toggleSeedType->hide();
this->setSeed(seed_25_words);
} else {
this->setSeed(seed_14_words);
ui->widgetRestoreHeight->setVisible(false);
}
connect(ui->check_toggleSeedType, &QCheckBox::toggled, [this, seed_25_words, seed_14_words](bool toggled){
this->setSeed(toggled ? seed_25_words : seed_14_words);
ui->widgetRestoreHeight->setVisible(toggled);
});
ui->label_restoreHeightHelp->setHelpText("Should you restore your wallet in the future, "
"specifying this block number will recover your wallet quicker.");
this->adjustSize();
}
void SeedDialog::setSeed(const QString &seed) {
ui->seed->setPlainText(seed);
int words = seed.split(" ").size();
ui->label_warning->setText(QString("<p>Please save these %1 words on paper (order is important). "
"This seed will allow you to recover your wallet in case "
"of computer failure."
"</p>"
"<b>WARNING:</b>"
"<ul>"
"<li>Never disclose your seed.</li>"
"<li>Never type it on a website</li>"
"<li>Do not store it electronically</li>"
"</ul>").arg(words));
this->adjustSize();
"This seed will allow you to recover your wallet in case "
"of computer failure."
"</p>"
"<b>WARNING:</b>"
"<ul>"
"<li>Never disclose your seed.</li>"
"<li>Never type it on a website</li>"
"<li>Do not store it electronically</li>"
"</ul>").arg(words));
}
SeedDialog::~SeedDialog()

@ -5,6 +5,7 @@
#define FEATHER_SEEDDIALOG_H
#include <QDialog>
#include "libwalletqt/Wallet.h"
namespace Ui {
class SeedDialog;
@ -15,10 +16,12 @@ class SeedDialog : public QDialog
Q_OBJECT
public:
explicit SeedDialog(const QString& seed, QWidget *parent = nullptr);
explicit SeedDialog(Wallet *wallet, QWidget *parent = nullptr);
~SeedDialog() override;
private:
void setSeed(const QString &seed);
Ui::SeedDialog *ui;
};

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>542</width>
<height>244</height>
<width>590</width>
<height>346</height>
</rect>
</property>
<property name="windowTitle">
@ -34,29 +34,94 @@
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="seed">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>125</height>
</size>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QPlainTextEdit" name="seed">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>125</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>300</height>
</size>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="widgetRestoreHeight" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="HelpLabel" name="label_restoreHeightHelp">
<property name="text">
<string>Restore height:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_restoreHeight">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="check_toggleSeedType">
<property name="text">
<string>Show 25 word seed</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_warning">
<property name="text">
@ -79,6 +144,13 @@
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>HelpLabel</class>
<extends>QLabel</extends>
<header>components.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>

@ -6,18 +6,29 @@
#include "libwalletqt/CoinsInfo.h"
#include "libwalletqt/WalletManager.h"
#include <QDebug>
#include "utils.h"
TransactionInfoDialog::TransactionInfoDialog(Coins *coins, TransactionInfo *txInfo, QWidget *parent)
#include <QMessageBox>
TransactionInfoDialog::TransactionInfoDialog(Wallet *wallet, TransactionInfo *txInfo, QWidget *parent)
: QDialog(parent)
, ui(new Ui::TransactionInfoDialog)
, m_coins(coins)
, m_wallet(wallet)
, m_txInfo(txInfo)
{
ui->setupUi(this);
ui->txid->setText(QString(txInfo->hash()));
ui->txid->setCursorPosition(0);
m_txProofWidget = new TxProofWidget(this, wallet, txInfo);
ui->label_txid->setText(QString(txInfo->hash()));
if (txInfo->direction() == TransactionInfo::Direction_In) {
ui->txKey->hide();
} else {
QString txKey = m_wallet->getTxKey(txInfo->hash());
txKey = txKey.isEmpty() ? "unknown" : txKey;
ui->label_txKey->setText(txKey);
}
ui->label_status->setText(QString("Status: %1 confirmations").arg(txInfo->confirmations()));
ui->label_date->setText(QString("Date: %1").arg(txInfo->timestamp().toString("yyyy-MM-dd HH:mm")));
@ -30,7 +41,7 @@ TransactionInfoDialog::TransactionInfoDialog(Coins *coins, TransactionInfo *txIn
ui->label_fee->setText(QString("Fee: %1").arg(txInfo->isCoinbase() ? WalletManager::displayAmount(0) : fee));
ui->label_unlockTime->setText(QString("Unlock time: %1 (height)").arg(txInfo->unlockTime()));
qDebug() << m_coins->coins_from_txid(txInfo->hash());
qDebug() << m_wallet->coins()->coins_from_txid(txInfo->hash());
QString destinations = txInfo->destinations_formatted();
if (destinations.isEmpty()) {
@ -41,6 +52,8 @@ TransactionInfoDialog::TransactionInfoDialog(Coins *coins, TransactionInfo *txIn
ui->destinations->setText(destinations);
}
ui->txProofWidget->addWidget(m_txProofWidget);
this->adjustSize();
}

@ -8,6 +8,8 @@
#include <QtSvg/QSvgWidget>
#include "libwalletqt/Coins.h"
#include "libwalletqt/TransactionInfo.h"
#include "libwalletqt/Wallet.h"
#include "widgets/txproofwidget.h"
namespace Ui {
class TransactionInfoDialog;
@ -18,14 +20,15 @@ class TransactionInfoDialog : public QDialog
Q_OBJECT
public:
explicit TransactionInfoDialog(Coins *coins, TransactionInfo *txInfo, QWidget *parent = nullptr);
explicit TransactionInfoDialog(Wallet *wallet, TransactionInfo *txInfo, QWidget *parent = nullptr);
~TransactionInfoDialog() override;
private:
Ui::TransactionInfoDialog *ui;
TransactionInfo *m_txInfo;
Coins *m_coins;
Wallet *m_wallet;
TxProofWidget *m_txProofWidget;
};
#endif //FEATHER_TRANSACTIONINFODIALOG_H

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>547</width>
<height>332</height>
<width>829</width>
<height>570</height>
</rect>
</property>
<property name="windowTitle">
@ -15,32 +15,41 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="label">
<property name="text">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Transaction ID:</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_txid">
<property name="text">
<string>txid</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLineEdit" name="txid">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>525</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>txid</string>
</property>
<property name="readOnly">
<bool>true</bool>
<widget class="QGroupBox" name="txKey">
<property name="title">
<string>Transaction key:</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QLabel" name="label_txKey">
<property name="text">
<string>txKey</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
@ -149,6 +158,16 @@
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="txProofWidget"/>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">

@ -0,0 +1,186 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020, The Monero Project.
#include "txconfadvdialog.h"
#include "ui_txconfadvdialog.h"
#include "libwalletqt/WalletManager.h"
#include "qrcode/QrCode.h"
#include "dialog/qrcodedialog.h"
#include "utils/utils.h"
#include "libwalletqt/PendingTransactionInfo.h"
#include "libwalletqt/Transfer.h"
#include "libwalletqt/Input.h"
#include "model/ModelUtils.h"
#include <QFileDialog>
#include <QMessageBox>
TxConfAdvDialog::TxConfAdvDialog(AppContext *ctx, const QString &description, QWidget *parent)
: QDialog(parent)
, ui(new Ui::TxConfAdvDialog)
, m_ctx(ctx)
, m_exportUnsignedMenu(new QMenu(this))
, m_exportSignedMenu(new QMenu(this))
{
ui->setupUi(this);
m_exportUnsignedMenu->addAction("Copy to clipboard", this, &TxConfAdvDialog::unsignedCopy);
m_exportUnsignedMenu->addAction("Show as QR code", this, &TxConfAdvDialog::unsignedQrCode);
m_exportUnsignedMenu->addAction("Save to file", this, &TxConfAdvDialog::unsignedSaveFile);
ui->btn_exportUnsigned->setMenu(m_exportUnsignedMenu);
m_exportSignedMenu->addAction("Copy to clipboard", this, &TxConfAdvDialog::signedCopy);
m_exportSignedMenu->addAction("Save to file", this, &TxConfAdvDialog::signedSaveFile);
ui->btn_exportSigned->setMenu(m_exportSignedMenu);
if (m_ctx->currentWallet->viewOnly()) {
ui->btn_exportSigned->hide();
ui->btn_send->hide();
}
ui->label_description->setText(QString("Description: %1").arg(description));
connect(ui->btn_sign, &QPushButton::clicked, this, &TxConfAdvDialog::signTransaction);
connect(ui->btn_send, &QPushButton::clicked, this, &TxConfAdvDialog::broadcastTransaction);
connect(ui->btn_close, &QPushButton::clicked, this, &TxConfAdvDialog::closeDialog);
ui->inputs->setFont(ModelUtils::getMonospaceFont());
ui->outputs->setFont(ModelUtils::getMonospaceFont());
this->adjustSize();
}
void TxConfAdvDialog::setTransaction(PendingTransaction *tx) {
ui->btn_sign->hide();
m_tx = tx;
m_tx->refresh();
PendingTransactionInfo *ptx = m_tx->transaction(0);
ui->txid->setText(tx->txid().first());
ui->amount->setText(WalletManager::displayAmount(tx->amount()));
ui->fee->setText(WalletManager::displayAmount(ptx->fee()));
ui->total->setText(WalletManager::displayAmount(tx->amount() + ptx->fee()));
auto size_str = [this]{
if (m_ctx->currentWallet->viewOnly()) {
return QString("Size: %1 bytes (unsigned)").arg(QString::number(m_tx->unsignedTxToBin().size()));
} else {
auto size = m_tx->signedTxToHex(0).size() / 2;
return QString("Size: %1 bytes (%2 bytes unsigned)").arg(QString::number(size), QString::number(m_tx->unsignedTxToBin().size()));
}
}();
ui->label_size->setText(size_str);
this->setupConstructionData(ptx);
}
void TxConfAdvDialog::setUnsignedTransaction(UnsignedTransaction *utx) {
m_utx = utx;
m_utx->refresh();
ui->btn_exportUnsigned->hide();
ui->btn_exportSigned->hide();
ui->btn_sign->show();
ui->btn_send->hide();
ui->txid->setText("n/a");
ui->label_size->setText("Size: n/a");
ui->amount->setText(WalletManager::displayAmount(utx->amount(0)));
ui->fee->setText(WalletManager::displayAmount(utx->fee(0)));
ui->total->setText(WalletManager::displayAmount(utx->amount(0) + utx->fee(0)));
ConstructionInfo *ci = m_utx->constructionInfo(0);
this->setupConstructionData(ci);
}
void TxConfAdvDialog::setupConstructionData(ConstructionInfo *ci) {
QString inputs_str;
auto inputs = ci->inputs();
for (const auto& i: inputs) {
inputs_str += QString("%1 %2\n").arg(i->pubKey(), WalletManager::displayAmount(i->amount()));
}
ui->inputs->setText(inputs_str);
ui->label_inputs->setText(QString("Inputs (%1)").arg(QString::number(inputs.size())));
QString outputs_str;
auto outputs = ci->outputs();
for (const auto& o: outputs) {
outputs_str += QString("%1 %2\n").arg(o->address(), WalletManager::displayAmount(o->amount()));
}
ui->outputs->setText(outputs_str);
ui->label_outputs->setText(QString("Outputs (%1)").arg(QString::number(outputs.size())));
ui->label_ringSize->setText(QString("Ring size: %1").arg(QString::number(ci->minMixinCount() + 1)));
ui->label_unlockTime->setText(QString("Unlock time: %1 (height)").arg(QString::number(ci->unlockTime())));
}
void TxConfAdvDialog::signTransaction() {
QString defaultName = QString("%1_signed_monero_tx").arg(QString::number(QDateTime::currentSecsSinceEpoch()));
QString fn = QFileDialog::getSaveFileName(this, "Save signed transaction to file", QDir::home().filePath(defaultName), "Transaction (*signed_monero_tx)");
if(fn.isEmpty()) return;
m_utx->sign(fn) ? QMessageBox::information(this, "Sign transaction", "Transaction saved successfully")
: QMessageBox::warning(this, "Sign transaction", "Failes to save transaction to file.");
}
void TxConfAdvDialog::unsignedSaveFile() {
QString defaultName = QString("%1_unsigned_monero_tx").arg(QString::number(QDateTime::currentSecsSinceEpoch()));
QString fn = QFileDialog::getSaveFileName(this, "Save transaction to file", QDir::home().filePath(defaultName), "Transaction (*unsigned_monero_tx)");
if(fn.isEmpty()) return;
m_tx->saveToFile(fn) ? QMessageBox::information(this, "Transaction saved to file", "Transaction saved successfully")
: QMessageBox::warning(this, "Save transaction", "Failed to save transaction to file.");
}
void TxConfAdvDialog::signedSaveFile() {
QString defaultName = QString("%1_signed_monero_tx").arg(QString::number(QDateTime::currentSecsSinceEpoch()));
QString fn = QFileDialog::getSaveFileName(this, "Save transaction to file", QDir::home().filePath(defaultName), "Transaction (*signed_monero_tx)");
if(fn.isEmpty()) return;
m_tx->saveToFile(fn) ? QMessageBox::information(this, "Transaction saved to file", "Transaction saved successfully")
: QMessageBox::warning(this, "Save transaction", "Failed to save transaction to file.");
}
void TxConfAdvDialog::unsignedQrCode() {
if (m_tx->unsignedTxToBin().size() > 2953) {
QMessageBox::warning(this, "Unable to show QR code", "Transaction size exceeds the maximum size for QR codes (2953 bytes).");
return;
}
QrCode qr(m_tx->unsignedTxToBin(), QrCode::Version::AUTO, QrCode::ErrorCorrectionLevel::LOW);
auto *dialog = new QrCodeDialog(this, qr, "Unsigned Transaction");
dialog->exec();
dialog->deleteLater();
}
void TxConfAdvDialog::unsignedCopy() {
Utils::copyToClipboard(m_tx->unsignedTxToBase64());
}
void TxConfAdvDialog::signedCopy() {
Utils::copyToClipboard(m_tx->signedTxToHex(0));
}
void TxConfAdvDialog::signedQrCode() {
}
void TxConfAdvDialog::broadcastTransaction() {
if (m_tx == nullptr) return;
m_ctx->currentWallet->commitTransactionAsync(m_tx);
QDialog::accept();
}
void TxConfAdvDialog::closeDialog() {
if (m_tx != nullptr)
m_ctx->currentWallet->disposeTransaction(m_tx);
if (m_utx != nullptr)
m_ctx->currentWallet->disposeTransaction(m_utx);
QDialog::reject();
}
TxConfAdvDialog::~TxConfAdvDialog() {
delete ui;
}

@ -0,0 +1,54 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020, The Monero Project.
#ifndef FEATHER_TXCONFADVDIALOG_H
#define FEATHER_TXCONFADVDIALOG_H
#include <QDialog>
#include <QStandardItemModel>
#include <QAbstractButton>
#include <QMenu>
#include "libwalletqt/PendingTransaction.h"
#include "appcontext.h"
namespace Ui {
class TxConfAdvDialog;
}
class TxConfAdvDialog : public QDialog
{
Q_OBJECT
public:
explicit TxConfAdvDialog(AppContext *ctx, const QString &description, QWidget *parent = nullptr);
~TxConfAdvDialog() override;
void setTransaction(PendingTransaction *tx);
void setUnsignedTransaction(UnsignedTransaction *utx);
private:
void setupConstructionData(ConstructionInfo *ci);
void signTransaction();
void broadcastTransaction();
void closeDialog();
void unsignedCopy();
void unsignedQrCode();
void unsignedSaveFile();
void signedCopy();
void signedQrCode();
void signedSaveFile();
Ui::TxConfAdvDialog *ui;
AppContext *m_ctx;
PendingTransaction *m_tx = nullptr;
UnsignedTransaction *m_utx = nullptr;
QMenu *m_exportUnsignedMenu;
QMenu *m_exportSignedMenu;
};
#endif //FEATHER_TXCONFADVDIALOG_H

@ -0,0 +1,279 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TxConfAdvDialog</class>
<widget class="QDialog" name="TxConfAdvDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>542</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>800</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Transaction</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Transaction ID:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="txid">
<property name="text">
<string>txid</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_amount">
<property name="text">
<string>Amount: </string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="amount">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_fee">
<property name="text">
<string>Fee: </string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="fee">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Total:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="total">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label_description">
<property name="text">
<string>Description:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_size">
<property name="text">
<string>Size: </string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_unlockTime">
<property name="text">
<string>Unlock time: </string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_ringSize">
<property name="text">
<string>Ringsize:</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>10</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_inputs">
<property name="text">
<string>Inputs</string>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="inputs">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>100</height>
</size>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_outputs">
<property name="text">
<string>Outputs</string>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="outputs">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>100</height>
</size>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QToolButton" name="btn_exportUnsigned">
<property name="text">
<string>Export unsigned</string>
</property>
<property name="popupMode">
<enum>QToolButton::InstantPopup</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="btn_exportSigned">
<property name="text">
<string>Export signed</string>
</property>
<property name="popupMode">
<enum>QToolButton::InstantPopup</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btn_sign">
<property name="text">
<string>Sign</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_send">
<property name="text">
<string>Send</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_close">
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

@ -6,12 +6,16 @@
#include "appcontext.h"
#include "utils/config.h"
#include "model/ModelUtils.h"
#include "libwalletqt/WalletManager.h"
#include "txconfadvdialog.h"
#include "globals.h"
#include <QMessageBox>
TxConfDialog::TxConfDialog(PendingTransaction *tx, const QString &address, const QString &description, int mixin, QWidget *parent)
TxConfDialog::TxConfDialog(AppContext *ctx, PendingTransaction *tx, const QString &address, const QString &description, int mixin, QWidget *parent)
: QDialog(parent)
, ui(new Ui::TxConfDialog)
, m_ctx(ctx)
, m_tx(tx)
, m_address(address)
, m_description(description)
@ -28,44 +32,31 @@ TxConfDialog::TxConfDialog(PendingTransaction *tx, const QString &address, const
};
QString amount = WalletManager::displayAmount(tx->amount());
QString amount_fiat = convert(tx->amount() / AppContext::cdiv);
QString amount_fiat = convert(tx->amount() / globals::cdiv);
ui->label_amount->setText(QString("%1 (%2 %3)").arg(amount, amount_fiat, preferredCur));
QString fee = WalletManager::displayAmount(tx->fee());
QString fee_fiat = convert(tx->fee() / AppContext::cdiv);
QString fee_fiat = convert(tx->fee() / globals::cdiv);
ui->label_fee->setText(QString("%1 (%2 %3)").arg(fee, fee_fiat, preferredCur));
QString total = WalletManager::displayAmount(tx->amount() + tx->fee());
QString total_fiat = convert((tx->amount() + tx->fee()) / AppContext::cdiv);
QString total_fiat = convert((tx->amount() + tx->fee()) / globals::cdiv);
ui->label_total->setText(QString("%1 (%2 %3)").arg(total, total_fiat, preferredCur));
ui->label_address->setText(ModelUtils::displayAddress(address, 2));
ui->label_address->setFont(ModelUtils::getMonospaceFont());
ui->label_address->setToolTip(address);
connect(ui->btn_Advanced, &QPushButton::clicked, this, &TxConfDialog::showAdvanced);
ui->buttonBox->button(QDialogButtonBox::Ok)->setText("Send");
connect(ui->btn_Advanced, &QPushButton::clicked, this, &TxConfDialog::setShowAdvanced);
this->adjustSize();
}
void TxConfDialog::showAdvanced() {
const auto amount = m_tx->amount() / AppContext::cdiv;
const auto fee = m_tx->fee() / AppContext::cdiv;
QString body = QString("Address: %2\n").arg(m_address.left(60));
body += m_address.mid(60) + "\n";
if(!m_description.isEmpty())
body = QString("%1Description: %2\n").arg(body, m_description);
body = QString("%1Amount: %2 XMR\n").arg(body, QString::number(amount));
body = QString("%1Fee: %2 XMR\n").arg(body, QString::number(fee));
body = QString("%1Ringsize: %2").arg(body, QString::number(m_mixin + 1));
auto subaddrIndices = m_tx->subaddrIndices();
for (int i = 0; i < subaddrIndices.count(); ++i){
body = QString("%1\nSpending address index: %2").arg(body, QString::number(subaddrIndices.at(i).toInt()));
}
QMessageBox::information(this, "Transaction information", body);
void TxConfDialog::setShowAdvanced() {
this->showAdvanced = true;
QDialog::reject();
}
TxConfDialog::~TxConfDialog() {

@ -7,6 +7,7 @@
#include <QDialog>
#include "libwalletqt/PendingTransaction.h"
#include "libwalletqt/WalletManager.h"
#include "appcontext.h"
namespace Ui {
class TxConfDialog;
@ -17,13 +18,18 @@ class TxConfDialog : public QDialog
Q_OBJECT
public:
explicit TxConfDialog(PendingTransaction *tx, const QString &address, const QString &description, int mixin, QWidget *parent = nullptr);
explicit TxConfDialog(AppContext *ctx, PendingTransaction *tx, const QString &address, const QString &description, int mixin, QWidget *parent = nullptr);
~TxConfDialog() override;
bool showAdvanced = false;
private:
void showAdvanced();
void setShowAdvanced();
void saveToFile();
void copyToClipboard();
Ui::TxConfDialog *ui;
AppContext *m_ctx;
PendingTransaction *m_tx;
QString m_address;
QString m_description;

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>655</width>
<width>844</width>
<height>248</height>
</rect>
</property>

@ -0,0 +1,102 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020, The Monero Project.
#include "tximportdialog.h"
#include "ui_tximportdialog.h"
#include <QMessageBox>
TxImportDialog::TxImportDialog(QWidget *parent, AppContext *ctx)
: QDialog(parent)
, m_ctx(ctx)
, m_loadTimer(new QTimer(this))
, ui(new Ui::TxImportDialog)
{
ui->setupUi(this);
ui->resp->hide();
ui->label_loading->hide();
m_network = new UtilsNetworking(m_ctx->network, this);
auto node = ctx->nodes->connection();
m_rpc = new DaemonRpc(this, m_network, node.full);
connect(ui->btn_load, &QPushButton::clicked, this, &TxImportDialog::loadTx);
connect(ui->btn_import, &QPushButton::clicked, this, &TxImportDialog::onImport);
connect(m_rpc, &DaemonRpc::ApiResponse, this, &TxImportDialog::onApiResponse);
connect(m_loadTimer, &QTimer::timeout, [this]{
ui->label_loading->setText(ui->label_loading->text() + ".");
});
this->adjustSize();
}
void TxImportDialog::loadTx() {
QString txid = ui->line_txid->text();
QString node = m_ctx->nodes->connection().full;
if (!node.startsWith("http://"))
node = QString("http://%1").arg(node);
m_rpc->setDaemonAddress(node);
m_rpc->getTransactions(QStringList() << txid, false, true);
ui->label_loading->setText("Loading transaction");
ui->label_loading->setHidden(false);
m_loadTimer->start(1000);
}
void TxImportDialog::onApiResponse(const DaemonRpc::DaemonResponse &resp) {
m_loadTimer->stop();
ui->label_loading->setHidden(true);
if (!resp.ok) {
QMessageBox::warning(this, "Import transaction", resp.status);
return;
}
if (resp.endpoint == DaemonRpc::Endpoint::GET_TRANSACTIONS) {
ui->resp->setVisible(true);
ui->resp->setPlainText(QJsonDocument(resp.obj).toJson(QJsonDocument::Indented));
this->adjustSize();
if (resp.obj.contains("missed_tx")) {
ui->btn_import->setEnabled(false);
QMessageBox::warning(this, "Load transaction", "Transaction could not be found. Make sure the txid is correct, or try connecting to a different node.");
return;
}
QMessageBox::information(this, "Load transaction", "Transaction loaded successfully.\n\nAfter closing this message box click the Import button to import the transaction into your wallet.");
m_transaction = resp.obj;
ui->btn_import->setEnabled(true);
}
}
void TxImportDialog::onImport() {
QJsonObject tx = m_transaction.value("txs").toArray().first().toObject();
QString txid = tx.value("tx_hash").toString();
QVector<quint64> output_indices;
for (const auto &o: tx.value("output_indices").toArray()) {
output_indices.push_back(o.toInt());
}
quint64 height = tx.value("block_height").toInt();
quint64 timestamp = tx.value("block_timestamp").toInt();
bool pool = tx.value("in_pool").toBool();
bool double_spend_seen = tx.value("double_spend_seen").toBool();
if (m_ctx->currentWallet->importTransaction(tx.value("tx_hash").toString(), output_indices, height, timestamp, false, pool, double_spend_seen)) {
QMessageBox::information(this, "Import transaction", "Transaction imported successfully.");
} else {
QMessageBox::warning(this, "Import transaction", "Transaction import failed.");
}
m_ctx->refreshModels();
}
TxImportDialog::~TxImportDialog() {
delete ui;
}

@ -0,0 +1,39 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020, The Monero Project.
#ifndef FEATHER_TXIMPORTDIALOG_H
#define FEATHER_TXIMPORTDIALOG_H
#include <QDialog>
#include "appcontext.h"
#include "utils/daemonrpc.h"
namespace Ui {
class TxImportDialog;
}
class TxImportDialog : public QDialog
{
Q_OBJECT
public:
explicit TxImportDialog(QWidget *parent, AppContext *ctx);
~TxImportDialog() override;
private slots:
void loadTx();
void onImport();
void onApiResponse(const DaemonRpc::DaemonResponse &resp);
private:
Ui::TxImportDialog *ui;
UtilsNetworking *m_network;
AppContext *m_ctx;
DaemonRpc *m_rpc;
QTimer *m_loadTimer;
QJsonObject m_transaction;
};
#endif //FEATHER_TXIMPORTDIALOG_H

@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TxImportDialog</class>
<widget class="QDialog" name="TxImportDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>700</width>
<height>442</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>700</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Import Transaction</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLineEdit" name="line_txid">
<property name="placeholderText">
<string>Transaction ID</string>
</property>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="resp">
<property name="readOnly">
<bool>true</bool>
</property>
<property name="placeholderText">
<string>Debug info..</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="btn_load">
<property name="text">
<string>Load</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_import">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Import</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_loading">
<property name="text">
<string>Loading transaction</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>TxImportDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>TxImportDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

@ -16,26 +16,28 @@ VerifyProofDialog::VerifyProofDialog(Wallet *wallet, QWidget *parent)
{
ui->setupUi(this);
connect(ui->btn_verifySpendProof, &QPushButton::clicked, this, &VerifyProofDialog::checkSpendProof);
connect(ui->btn_verifyOutProof, &QPushButton::clicked, this, &VerifyProofDialog::checkOutProof);
connect(ui->btn_verifyInProof, &QPushButton::clicked, this, &VerifyProofDialog::checkInProof);
connect(ui->btn_spendClear, &QPushButton::clicked, [this](){
ui->lineEdit_spendTxID->clear();
ui->lineEdit_spendMessage->clear();
ui->input_SpendProof->clear();
});
connect(ui->btn_outClear, &QPushButton::clicked, [this](){
ui->lineEdit_outTxID->clear();
ui->lineEdit_outAddress->clear();
ui->lineEdit_outMessage->clear();
ui->input_OutProof->clear();
});
connect(ui->btn_inClear, &QPushButton::clicked, [this](){
ui->lineEdit_inTxID->clear();
ui->lineEdit_inAddress->clear();
ui->lineEdit_inMessage->clear();
ui->input_InProof->clear();
connect(ui->btn_verify, &QPushButton::clicked, this, &VerifyProofDialog::checkProof);
connect(ui->btn_clear, &QPushButton::clicked, [this]{
switch (ui->tabWidget->currentIndex()) {
case 0:
ui->lineEdit_spendTxID->clear();
ui->lineEdit_spendMessage->clear();
ui->input_SpendProof->clear();
break;
case 1:
ui->lineEdit_outTxID->clear();
ui->lineEdit_outAddress->clear();
ui->lineEdit_outMessage->clear();
ui->input_OutProof->clear();
break;
case 2:
ui->lineEdit_inTxID->clear();
ui->lineEdit_inAddress->clear();
ui->lineEdit_inMessage->clear();
ui->input_InProof->clear();
break;
}
});
}
@ -44,6 +46,20 @@ VerifyProofDialog::~VerifyProofDialog()
delete ui;
}
void VerifyProofDialog::checkProof() {
switch (ui->tabWidget->currentIndex()) {
case 0:
this->checkSpendProof();
break;
case 1:
this->checkOutProof();
break;
case 2:
this->checkInProof();
break;
}
}
void VerifyProofDialog::checkSpendProof() {
auto r = m_wallet->checkSpendProof(ui->lineEdit_spendTxID->text(), ui->lineEdit_spendMessage->text(), ui->input_SpendProof->toPlainText());

@ -20,12 +20,13 @@ public:
~VerifyProofDialog() override;
private slots:
void checkSpendProof();
void checkOutProof();
void checkInProof();
void checkProof();
private:
void checkTxProof(const QString &txId, const QString &address, const QString &message, const QString &signature);
void checkSpendProof();
void checkOutProof();
void checkInProof();
Ui::VerifyProofDialog *ui;
Wallet *m_wallet;

@ -39,17 +39,7 @@
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Transaction ID:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit_spendTxID"/>
</item>
<layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
@ -57,10 +47,13 @@
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_5">
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit_spendTxID"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>SpendProof:</string>
<string>Transaction ID:</string>
</property>
</widget>
</item>
@ -71,6 +64,13 @@
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>SpendProof:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPlainTextEdit" name="input_SpendProof">
<property name="overwriteMode">
@ -96,37 +96,6 @@
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btn_spendClear">
<property name="text">
<string>Clear</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_verifySpendProof">
<property name="text">
<string>Verify</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="OutProof">
@ -213,37 +182,6 @@
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btn_outClear">
<property name="text">
<string>Clear</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_verifyOutProof">
<property name="text">
<string>Verify</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="InProof">
@ -331,41 +269,44 @@
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btn_inClear">
<property name="text">
<string>Clear</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_verifyInProof">
<property name="text">
<string>Verify</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btn_clear">
<property name="text">
<string>Clear</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_verify">
<property name="text">
<string>Verify</string>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>

@ -0,0 +1,59 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020, The Monero Project.
#include <QFileDialog>
#include <QInputDialog>
#include <QMessageBox>
#include "viewonlydialog.h"
#include "ui_viewonlydialog.h"
ViewOnlyDialog::ViewOnlyDialog(AppContext *ctx, QWidget *parent)
: QDialog(parent)
, ui(new Ui::ViewOnlyDialog), m_ctx(ctx)
{
ui->setupUi(this);
ui->label_restoreHeight->setText(QString::number(ctx->currentWallet->getWalletCreationHeight()));
ui->label_primaryAddress->setText(ctx->currentWallet->address(0, 0));
ui->label_secretViewKey->setText(ctx->currentWallet->getSecretViewKey());
connect(ui->btn_Copy, &QPushButton::clicked, this, &ViewOnlyDialog::copyToClipboad);
connect(ui->btn_Save, &QPushButton::clicked, this, &ViewOnlyDialog::onWriteViewOnlyWallet);
ui->btn_Save->setEnabled(!m_ctx->currentWallet->viewOnly());
this->adjustSize();
}
void ViewOnlyDialog::onWriteViewOnlyWallet(){
QString fn = QFileDialog::getSaveFileName(this, "Save .keys wallet file", QDir::homePath(), "Monero wallet (*.keys)");
if(fn.isEmpty()) return;
if(!fn.endsWith(".keys")) fn += ".keys";
QString passwd;
QInputDialog passwordDialog(this);
passwordDialog.setInputMode(QInputDialog::TextInput);
passwordDialog.setTextEchoMode(QLineEdit::Password);
passwordDialog.setWindowTitle("View-Only wallet password");
passwordDialog.setLabelText("Protect this view-only wallet with a password?");
passwordDialog.resize(300, 100);
if((bool)passwordDialog.exec())
passwd = passwordDialog.textValue();
m_ctx->currentWallet->createViewOnly(fn, passwd);
QMessageBox::information(this, "Information", "View-only wallet successfully written to disk.");
}
void ViewOnlyDialog::copyToClipboad() {
QString text = "";
text += QString("Address: %1\n").arg(ui->label_primaryAddress->text());
text += QString("Secret view key: %1\n").arg(ui->label_secretViewKey->text());
text += QString("Restore height: %1\n").arg(ui->label_restoreHeight->text());
Utils::copyToClipboard(text);
}
ViewOnlyDialog::~ViewOnlyDialog()
{
delete ui;
}

@ -0,0 +1,32 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020, The Monero Project.
#ifndef FEATHER_VIEWONLYDIALOG_H
#define FEATHER_VIEWONLYDIALOG_H
#include <QDialog>
#include "appcontext.h"
namespace Ui {
class ViewOnlyDialog;
}
class ViewOnlyDialog : public QDialog
{
Q_OBJECT
public:
explicit ViewOnlyDialog(AppContext *ctx, QWidget *parent = nullptr);
~ViewOnlyDialog() override;
private slots:
void onWriteViewOnlyWallet();
private:
Ui::ViewOnlyDialog *ui;
AppContext *m_ctx = nullptr;
void copyToClipboad();
};
#endif //FEATHER_KEYSDIALOG_H

@ -0,0 +1,139 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ViewOnlyDialog</class>
<widget class="QDialog" name="ViewOnlyDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>659</width>
<height>254</height>
</rect>
</property>
<property name="windowTitle">
<string>View-Only</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox_6">
<property name="title">
<string>Restore height</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QLabel" name="label_restoreHeight">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Primary address</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_primaryAddress">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Secret view key</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_secretViewKey">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="btn_Copy">
<property name="text">
<string>Copy</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_Save">
<property name="text">
<string>Create view-only wallet</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ViewOnlyDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ViewOnlyDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

@ -31,7 +31,7 @@ WalletInfoDialog::WalletInfoDialog(AppContext *ctx, QWidget *parent)
void WalletInfoDialog::openWalletDir() {
QFileInfo file(m_ctx->walletPath);
QDesktopServices::openUrl(file.absolutePath());
QDesktopServices::openUrl(QUrl(QString("file://%1").arg(file.absolutePath()), QUrl::TolerantMode));
}
WalletInfoDialog::~WalletInfoDialog() {

@ -0,0 +1,14 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020, The Monero Project.
#ifndef FEATHER_GLOBALS_H
#define FEATHER_GLOBALS_H
#include <QtGlobal>
namespace globals
{
const qreal cdiv = 1e12;
}
#endif //FEATHER_GLOBALS_H

@ -25,12 +25,17 @@ HistoryWidget::HistoryWidget(QWidget *parent)
m_copyMenu->addAction("Transaction ID", this, [this]{copy(copyField::TxID);});
m_copyMenu->addAction("Date", this, [this]{copy(copyField::Date);});
m_copyMenu->addAction("Amount", this, [this]{copy(copyField::Amount);});
m_copyMenu->addAction("Spend proof", this, &HistoryWidget::getSpendProof);
auto spendProof = m_copyMenu->addAction("Spend proof", this, &HistoryWidget::getSpendProof);
ui->history->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->history, &QTreeView::customContextMenuRequested, [=](const QPoint & point){
QModelIndex index = ui->history->indexAt(point);
if (index.isValid()) {
TransactionInfo::Direction direction;
m_txHistory->transaction(m_model->mapToSource(index).row(), [&direction](TransactionInfo &tInfo) {
direction = tInfo.direction();
});
spendProof->setVisible(direction == TransactionInfo::Direction_Out);
m_contextMenu->exec(ui->history->viewport()->mapToGlobal(point));
}
});
@ -45,11 +50,11 @@ HistoryWidget::HistoryWidget(QWidget *parent)
}
void HistoryWidget::setModel(Coins *coins, TransactionHistoryProxyModel *model, TransactionHistory *txHistory)
void HistoryWidget::setModel(TransactionHistoryProxyModel *model, Wallet *wallet)
{
m_coins = coins;
m_model = model;
m_txHistory = txHistory;
m_wallet = wallet;
m_txHistory = m_wallet->history();
ui->history->setModel(m_model);
ui->history->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
@ -66,7 +71,7 @@ void HistoryWidget::showTxDetails() {
});
if (i != nullptr) {
auto * dialog = new TransactionInfoDialog(m_coins, i, this);
auto * dialog = new TransactionInfoDialog(m_wallet, i, this);
dialog->exec();
}
}

@ -7,6 +7,7 @@
#include "model/TransactionHistoryModel.h"
#include "model/TransactionHistoryProxyModel.h"
#include "libwalletqt/Coins.h"
#include "libwalletqt/Wallet.h"
#include <QWidget>
#include <QMenu>
@ -21,7 +22,7 @@ Q_OBJECT
public:
explicit HistoryWidget(QWidget *parent = nullptr);
void setModel(Coins * coins, TransactionHistoryProxyModel * model, TransactionHistory * txHistory);
void setModel(TransactionHistoryProxyModel *model, Wallet *wallet);
~HistoryWidget() override;
public slots:
@ -51,7 +52,7 @@ private:
QMenu *m_copyMenu;
TransactionHistory *m_txHistory;
TransactionHistoryProxyModel *m_model;
Coins *m_coins;
Wallet *m_wallet = nullptr;
};
#endif //FEATHER_HISTORYWIDGET_H

@ -0,0 +1,53 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2014-2020, The Monero Project.
#include "ConstructionInfo.h"
#include "Input.h"
#include "Transfer.h"
quint64 ConstructionInfo::unlockTime() const {
return m_unlockTime;
}
QSet<quint32> ConstructionInfo::subaddressIndices() const {
return m_subaddressIndices;
}
QVector<QString> ConstructionInfo::subaddresses() const {
return m_subaddresses;
}
quint64 ConstructionInfo::minMixinCount() const {
return m_minMixinCount;
}
QList<Input *> ConstructionInfo::inputs() const {
return m_inputs;
}
QList<Transfer *> ConstructionInfo::outputs() const {
return m_outputs;
}
ConstructionInfo::ConstructionInfo(const Monero::TransactionConstructionInfo *pimpl, QObject *parent)
: QObject(parent)
, m_unlockTime(pimpl->unlockTime())
, m_minMixinCount(pimpl->minMixinCount())
{
for (auto const &i : pimpl->inputs())
{
Input *input = new Input(i.amount, QString::fromStdString(i.pubkey), this);
m_inputs.append(input);
}
for (auto const &o : pimpl->outputs())
{
Transfer *output = new Transfer(o.amount, QString::fromStdString(o.address), this);
m_outputs.append(output);
}
for (uint32_t i : pimpl->subaddressIndices())
{
m_subaddressIndices.insert(i);
}
}

@ -0,0 +1,46 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2014-2020, The Monero Project.
#ifndef FEATHER_CONSTRUCTIONINFO_H
#define FEATHER_CONSTRUCTIONINFO_H
#include <wallet/api/wallet2_api.h>
#include <QObject>
#include <QSet>
class Input;
class Transfer;
class ConstructionInfo : public QObject
{
Q_OBJECT
Q_PROPERTY(quint64 unlockTime READ unlockTime)
Q_PROPERTY(QSet<quint32> subaddressIndices READ subaddressIndices)
Q_PROPERTY(QVector<QString> subaddresses READ subaddresses)
Q_PROPERTY(quint64 minMixinCount READ minMixinCount)
Q_PROPERTY(QList<Input*> inputs READ inputs)
Q_PROPERTY(QList<Transfer*> outputs READ outputs)
public:
quint64 unlockTime() const;
QSet<quint32> subaddressIndices() const;
QVector<QString> subaddresses() const;
quint64 minMixinCount() const;
QList<Input*> inputs() const;
QList<Transfer*> outputs() const;
private:
explicit ConstructionInfo(const Monero::TransactionConstructionInfo *pimpl, QObject *parent = nullptr);
friend class PendingTransactionInfo;
friend class UnsignedTransaction;
quint64 m_unlockTime;
QSet<quint32> m_subaddressIndices;
QVector<QString> m_subaddresses;
quint64 m_minMixinCount;
mutable QList<Input*> m_inputs;
mutable QList<Transfer*> m_outputs;
};
#endif //FEATHER_CONSTRUCTIONINFO_H

@ -0,0 +1,29 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2014-2020, The Monero Project.
#ifndef FEATHER_INPUT_H
#define FEATHER_INPUT_H
#include <wallet/api/wallet2_api.h>
#include <QObject>
#include <utility>
class Input : public QObject
{
Q_OBJECT
Q_PROPERTY(quint64 amount READ amount)
Q_PROPERTY(QString pubKey READ pubKey)
private:
explicit Input(uint64_t _amount, QString _address, QObject *parent = nullptr): QObject(parent), m_amount(_amount), m_pubkey(std::move(_address)) {};
friend class ConstructionInfo;
quint64 m_amount;
QString m_pubkey;
public:
quint64 amount() const { return m_amount; }
QString pubKey() const { return m_pubkey; }
};
#endif //FEATHER_INPUT_H

@ -16,10 +16,12 @@ QString PendingTransaction::errorString() const
bool PendingTransaction::commit()
{
// Save transaction to file if fileName is set.
if(!m_fileName.isEmpty())
return m_pimpl->commit(m_fileName.toStdString());
return m_pimpl->commit(m_fileName.toStdString());
return m_pimpl->commit();
}
bool PendingTransaction::saveToFile(const QString &fileName)
{
return m_pimpl->commit(fileName.toStdString());
}
quint64 PendingTransaction::amount() const
@ -37,7 +39,6 @@ quint64 PendingTransaction::fee() const
return m_pimpl->fee();
}
QStringList PendingTransaction::txid() const
{
QStringList list;
@ -63,9 +64,33 @@ QList<QVariant> PendingTransaction::subaddrIndices() const
return result;
}
void PendingTransaction::setFilename(const QString &fileName)
QByteArray PendingTransaction::unsignedTxToBin() const {
return QByteArray::fromStdString(m_pimpl->unsignedTxToBin());
}
QString PendingTransaction::unsignedTxToBase64() const
{
m_fileName = fileName;
return QString::fromStdString(m_pimpl->unsignedTxToBase64());
}
QString PendingTransaction::signedTxToHex(int index) const
{
return QString::fromStdString(m_pimpl->signedTxToHex(index));
}
PendingTransactionInfo * PendingTransaction::transaction(int index) const {
return m_pending_tx_info[index];
}
void PendingTransaction::refresh()
{
qDeleteAll(m_pending_tx_info);
m_pending_tx_info.clear();
m_pimpl->refresh();
for (const auto i : m_pimpl->getAll()) {
m_pending_tx_info.append(new PendingTransactionInfo(i, this));
}
}
PendingTransaction::PendingTransaction(Monero::PendingTransaction *pt, QObject *parent)

@ -9,6 +9,8 @@
#include <QVariant>
#include <wallet/api/wallet2_api.h>
#include "PendingTransactionInfo.h"
//namespace Monero {
//class PendingTransaction;
@ -30,7 +32,7 @@ public:
enum Status {
Status_Ok = Monero::PendingTransaction::Status_Ok,
Status_Error = Monero::PendingTransaction::Status_Error,
Status_Critical = Monero::PendingTransaction::Status_Critical
Status_Critical = Monero::PendingTransaction::Status_Critical
};
Q_ENUM(Status)
@ -45,21 +47,27 @@ public:
Status status() const;
QString errorString() const;
Q_INVOKABLE bool commit();
bool saveToFile(const QString &fileName);
quint64 amount() const;
quint64 dust() const;
quint64 fee() const;
QStringList txid() const;
quint64 txCount() const;
QList<QVariant> subaddrIndices() const;
Q_INVOKABLE void setFilename(const QString &fileName);
QByteArray unsignedTxToBin() const;
QString unsignedTxToBase64() const;
QString signedTxToHex(int index) const;
void refresh();
PendingTransactionInfo * transaction(int index) const;
private:
explicit PendingTransaction(Monero::PendingTransaction * pt, QObject *parent = 0);
explicit PendingTransaction(Monero::PendingTransaction * pt, QObject *parent = nullptr);
private:
friend class Wallet;
Monero::PendingTransaction * m_pimpl;
QString m_fileName;
mutable QList<PendingTransactionInfo*> m_pending_tx_info;
};
#endif // PENDINGTRANSACTION_H

@ -0,0 +1,32 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2014-2020, The Monero Project.
#include "PendingTransactionInfo.h"
#include "Input.h"
#include "Transfer.h"
quint64 PendingTransactionInfo::fee() const {
return m_fee;
}
quint64 PendingTransactionInfo::dust() const {
return m_dust;
}
bool PendingTransactionInfo::dustAddedToFee() const {
return m_dustAddedToFee;
}
QString PendingTransactionInfo::txKey() const {
return m_txKey;
}
PendingTransactionInfo::PendingTransactionInfo(const Monero::PendingTransactionInfo *pimpl, QObject *parent)
: ConstructionInfo(pimpl->constructionData(), parent)
, m_fee(pimpl->fee())
, m_dust(pimpl->dust())
, m_dustAddedToFee(pimpl->dustAddedToFee())
, m_txKey(QString::fromStdString(pimpl->txKey()))
{
}

@ -0,0 +1,46 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2014-2020, The Monero Project.
#ifndef FEATHER_PENDINGTRANSACTIONINFO_H
#define FEATHER_PENDINGTRANSACTIONINFO_H
#include <wallet/api/wallet2_api.h>
#include "ConstructionInfo.h"
#include <QObject>
#include <QSet>
class Input;
class Transfer;
class PendingTransactionInfo : public ConstructionInfo
{
Q_OBJECT
Q_PROPERTY(quint64 fee READ fee)
Q_PROPERTY(quint64 dust READ dust)
Q_PROPERTY(bool dustAddedToFee READ dustAddedToFee)
Q_PROPERTY(QString txKey READ txKey)
Q_PROPERTY(quint64 unlockTime READ unlockTime)
Q_PROPERTY(QSet<quint32> subaddressIndices READ subaddressIndices)
Q_PROPERTY(QVector<QString> subaddresses READ subaddresses)
Q_PROPERTY(quint64 minMixinCount READ minMixinCount)
Q_PROPERTY(QList<Input*> inputs READ inputs)
Q_PROPERTY(QList<Transfer*> outputs READ outputs)
public:
quint64 fee() const;
quint64 dust() const;
bool dustAddedToFee() const;
QString txKey() const;
private:
explicit PendingTransactionInfo(const Monero::PendingTransactionInfo *pimpl, QObject *parent = nullptr);
friend class PendingTransaction;
quint64 m_fee;
quint64 m_dust;
bool m_dustAddedToFee;
QString m_txKey;
};
#endif //FEATHER_PENDINGTRANSACTIONINFO_H

@ -28,6 +28,11 @@ bool TransactionInfo::isCoinbase() const
return m_coinbase;
}
quint64 TransactionInfo::balanceDelta() const
{
return m_amount + m_fee;
}
double TransactionInfo::amount() const
{
// there's no unsigned uint64 for JS, so better use double
@ -44,6 +49,11 @@ QString TransactionInfo::displayAmount() const
return WalletManager::displayAmount(m_amount);
}
quint64 TransactionInfo::atomicFee() const
{
return m_fee;
}
QString TransactionInfo::fee() const
{
if(m_fee == 0)
@ -127,6 +137,15 @@ QString TransactionInfo::destinations_formatted() const
return destinations;
}
QList<QString> TransactionInfo::destinations() const
{
QList<QString> dests;
for (auto const& t: m_transfers) {
dests.append(t->address());
}
return dests;
}
QString TransactionInfo::rings_formatted() const
{
QString rings;

@ -52,10 +52,12 @@ public:
bool isPending() const;
bool isFailed() const;
bool isCoinbase() const;
quint64 balanceDelta() const;
double amount() const;
quint64 atomicAmount() const;
QString displayAmount() const;
QString fee() const;
quint64 atomicFee() const;
quint64 blockHeight() const;
QString description() const;
QSet<quint32> subaddrIndex() const;
@ -72,6 +74,7 @@ public:
QString paymentId() const;
//! only applicable for output transactions
//! used in tx details popup
QList<QString> destinations() const;
QString destinations_formatted() const;
QString rings_formatted() const;

@ -17,6 +17,7 @@ private:
explicit Transfer(uint64_t _amount, QString _address, QObject *parent = 0): QObject(parent), m_amount(_amount), m_address(std::move(_address)) {};
private:
friend class TransactionInfo;
friend class ConstructionInfo;
quint64 m_amount;
QString m_address;
public:

@ -83,6 +83,21 @@ void UnsignedTransaction::setFilename(const QString &fileName)
m_fileName = fileName;
}
ConstructionInfo * UnsignedTransaction::constructionInfo(int index) const {
return m_construction_info[index];
}
void UnsignedTransaction::refresh()
{
qDeleteAll(m_construction_info);
m_construction_info.clear();
m_pimpl->refresh();
for (const auto i : m_pimpl->getAll()) {
m_construction_info.append(new ConstructionInfo(i, this));
}
}
UnsignedTransaction::UnsignedTransaction(Monero::UnsignedTransaction *pt, Monero::Wallet *walletImpl, QObject *parent)
: QObject(parent), m_pimpl(pt), m_walletImpl(walletImpl)
{

@ -7,14 +7,13 @@
#include <QObject>
#include <wallet/api/wallet2_api.h>
#include "libwalletqt/PendingTransactionInfo.h"
class UnsignedTransaction : public QObject
{
Q_OBJECT
Q_PROPERTY(Status status READ status)
Q_PROPERTY(QString errorString READ errorString)
// Q_PROPERTY(QList<qulonglong> amount READ amount)
// Q_PROPERTY(QList<qulonglong> fee READ fee)
Q_PROPERTY(quint64 txCount READ txCount)
Q_PROPERTY(QString confirmationMessage READ confirmationMessage)
Q_PROPERTY(QStringList recipientAddress READ recipientAddress)
@ -41,15 +40,19 @@ public:
quint64 minMixinCount() const;
Q_INVOKABLE bool sign(const QString &fileName) const;
Q_INVOKABLE void setFilename(const QString &fileName);
void refresh();
ConstructionInfo * constructionInfo(int index) const;
private:
explicit UnsignedTransaction(Monero::UnsignedTransaction * pt, Monero::Wallet *walletImpl, QObject *parent = 0);
explicit UnsignedTransaction(Monero::UnsignedTransaction * pt, Monero::Wallet *walletImpl, QObject *parent = nullptr);
~UnsignedTransaction();
private:
friend class Wallet;
Monero::UnsignedTransaction * m_pimpl;
QString m_fileName;
Monero::Wallet * m_walletImpl;
mutable QList<ConstructionInfo*> m_construction_info;
};
#endif // UNSIGNEDTRANSACTION_H

@ -2,6 +2,11 @@
// Copyright (c) 2014-2020, The Monero Project.
#include "Wallet.h"
#include <chrono>
#include <stdexcept>
#include <thread>
#include "PendingTransaction.h"
#include "UnsignedTransaction.h"
#include "TransactionHistory.h"
@ -27,6 +32,8 @@
#include <QVector>
#include <QMutexLocker>
#include "utils/ScopeGuard.h"
namespace {
static const int DAEMON_BLOCKCHAIN_HEIGHT_CACHE_TTL_SECONDS = 5;
static const int DAEMON_BLOCKCHAIN_TARGET_HEIGHT_CACHE_TTL_SECONDS = 30;
@ -36,10 +43,15 @@ namespace {
}
Wallet::Wallet(QObject * parent)
: Wallet(nullptr, parent)
: Wallet(nullptr, parent)
{
}
Wallet::ConnectionStatus Wallet::connectionStatus() const
{
return m_connectionStatus;
}
QString Wallet::getSeed() const
{
return QString::fromStdString(m_walletImpl->seed());
@ -65,39 +77,26 @@ NetworkType::Type Wallet::nettype() const
return static_cast<NetworkType::Type>(m_walletImpl->nettype());
}
bool Wallet::disconnected() const
{
return m_disconnected;
}
void Wallet::updateConnectionStatusAsync()
bool Wallet::refreshing() const
{
m_scheduler.run([this] {
if (m_connectionStatus == Wallet::ConnectionStatus_Disconnected)
{
setConnectionStatus(ConnectionStatus_Connecting);
}
ConnectionStatus newStatus = static_cast<ConnectionStatus>(m_walletImpl->connected());
if (newStatus != m_connectionStatus || !m_initialized) {
m_initialized = true;
setConnectionStatus(newStatus);
}
// Release lock
m_connectionStatusRunning = false;
});
return m_refreshing;
}
Wallet::ConnectionStatus Wallet::connected(bool forceCheck)
void Wallet::refreshingSet(bool value)
{
// cache connection status
if (forceCheck || !m_initialized || (m_connectionStatusTime.elapsed() / 1000 > m_connectionStatusTtl && !m_connectionStatusRunning) || m_connectionStatusTime.elapsed() > 30000) {
m_connectionStatusRunning = true;
m_connectionStatusTime.restart();
updateConnectionStatusAsync();
if (m_refreshing.exchange(value) != value)
{
emit refreshingChanged();
}
return m_connectionStatus;
}
bool Wallet::disconnected() const
{
return m_disconnected;
void Wallet::setConnectionTimeout(int timeout) {
m_connectionTimeout = timeout;
}
void Wallet::setConnectionStatus(ConnectionStatus value)
@ -111,7 +110,7 @@ void Wallet::setConnectionStatus(ConnectionStatus value)
emit connectionStatusChanged(m_connectionStatus);
bool disconnected = m_connectionStatus == Wallet::ConnectionStatus_Connecting ||
m_connectionStatus == Wallet::ConnectionStatus_Disconnected;
m_connectionStatus == Wallet::ConnectionStatus_Disconnected;
if (m_disconnected != disconnected)
{
@ -120,6 +119,29 @@ void Wallet::setConnectionStatus(ConnectionStatus value)
}
}
QString Wallet::getProxyAddress() const
{
QMutexLocker locker(&m_proxyMutex);
return m_proxyAddress;
}
void Wallet::setProxyAddress(QString address)
{
m_scheduler.run([this, address] {
{
QMutexLocker locker(&m_proxyMutex);
if (!m_walletImpl->setProxy(address.toStdString()))
{
qCritical() << "failed to set proxy" << address;
}
m_proxyAddress = address;
}
emit proxyAddressChanged();
});
}
bool Wallet::synchronized() const
{
return m_walletImpl->synchronized();
@ -135,6 +157,11 @@ bool Wallet::setPassword(const QString &password)
return m_walletImpl->setPassword(password.toStdString());
}
QString Wallet::getPassword()
{
return QString::fromStdString(m_walletImpl->getPassword());
}
QString Wallet::address(quint32 accountIndex, quint32 addressIndex) const
{
return QString::fromStdString(m_walletImpl->address(accountIndex, addressIndex));
@ -145,20 +172,18 @@ QString Wallet::path() const
return QDir::toNativeSeparators(QString::fromStdString(m_walletImpl->path()));
}
//void Wallet::storeAsync(const QVariant &callback, const QString &path /* = "" */)
//void Wallet::storeAsync(const QJSValue &callback, const QString &path /* = "" */)
//{
// const auto future = m_scheduler.run(
// [this, path] {
// QMutexLocker locker(&m_storeMutex);
// return QVariantList({m_walletImpl->store(path.toStdString())});
// },
// callback);
// [this, path] {
// QMutexLocker locker(&m_asyncMutex);
//
// return QJSValueList({m_walletImpl->store(path.toStdString())});
// },
// callback);
// if (!future.first)
// {
// QVariant(callback).call(QVariantList({false}));
// QJSValue(callback).call(QJSValueList({false}));
// }
//}
@ -167,7 +192,7 @@ void Wallet::store(const QString &path)
m_walletImpl->store(path.toStdString());
}
bool Wallet::init(const QString &daemonAddress, bool trustedDaemon, quint64 upperTransactionLimit, bool isRecovering, bool isRecoveringFromDevice, quint64 restoreHeight)
bool Wallet::init(const QString &daemonAddress, bool trustedDaemon, quint64 upperTransactionLimit, bool isRecovering, bool isRecoveringFromDevice, quint64 restoreHeight, const QString& proxyAddress)
{
qDebug() << "init non async";
if (isRecovering){
@ -181,7 +206,20 @@ bool Wallet::init(const QString &daemonAddress, bool trustedDaemon, quint64 uppe
if (isRecovering || isRecoveringFromDevice) {
m_walletImpl->setRefreshFromBlockHeight(restoreHeight);
}
m_walletImpl->init(daemonAddress.toStdString(), upperTransactionLimit, m_daemonUsername.toStdString(), m_daemonPassword.toStdString());
{
QMutexLocker locker(&m_proxyMutex);
if (!m_walletImpl->init(daemonAddress.toStdString(), upperTransactionLimit, m_daemonUsername.toStdString(), m_daemonPassword.toStdString(), m_useSSL, false, proxyAddress.toStdString()))
{
return false;
}
m_proxyAddress = proxyAddress;
}
emit proxyAddressChanged();
setTrustedDaemon(trustedDaemon);
setTrustedDaemon(trustedDaemon);
return true;
}
@ -193,17 +231,24 @@ void Wallet::setDaemonLogin(const QString &daemonUsername, const QString &daemon
m_daemonPassword = daemonPassword;
}
void Wallet::initAsync(const QString &daemonAddress, bool trustedDaemon, quint64 upperTransactionLimit, bool isRecovering, bool isRecoveringFromDevice, quint64 restoreHeight)
void Wallet::initAsync(
const QString &daemonAddress,
bool trustedDaemon /* = false */,
quint64 upperTransactionLimit /* = 0 */,
bool isRecovering /* = false */,
bool isRecoveringFromDevice /* = false */,
quint64 restoreHeight /* = 0 */,
const QString &proxyAddress /* = "" */)
{
qDebug() << "initAsync: " + daemonAddress;
const auto future = m_scheduler.run([this, daemonAddress, trustedDaemon, upperTransactionLimit, isRecovering, isRecoveringFromDevice, restoreHeight] {
bool success = init(daemonAddress, trustedDaemon, upperTransactionLimit, isRecovering, isRecoveringFromDevice, restoreHeight);
const auto future = m_scheduler.run([this, daemonAddress, trustedDaemon, upperTransactionLimit, isRecovering, isRecoveringFromDevice, restoreHeight, proxyAddress] {
bool success = init(daemonAddress, trustedDaemon, upperTransactionLimit, isRecovering, isRecoveringFromDevice, restoreHeight, proxyAddress);
if (success)
{
emit walletCreationHeightChanged();
qDebug() << "init async finished - starting refresh";
connected(true);
m_walletImpl->startRefresh();
refreshHeightAsync();
startRefresh();
}
});
if (future.first)
@ -246,6 +291,11 @@ void Wallet::setTrustedDaemon(bool arg)
m_walletImpl->setTrustedDaemon(arg);
}
void Wallet::setUseSSL(bool ssl)
{
m_useSSL = ssl;
}
bool Wallet::viewOnly() const
{
return m_walletImpl->watchOnly();
@ -358,6 +408,8 @@ void Wallet::refreshHeightAsync()
daemonHeightFuture.second.waitForFinished();
targetHeightFuture.second.waitForFinished();
setConnectionStatus(ConnectionStatus_Connected);
emit heightRefreshed(walletHeight, daemonHeight, targetHeight);
});
}
@ -372,7 +424,8 @@ quint64 Wallet::daemonBlockChainHeight() const
// cache daemon blockchain height for some time (60 seconds by default)
if (m_daemonBlockChainHeight == 0
|| m_daemonBlockChainHeightTime.elapsed() / 1000 > m_daemonBlockChainHeightTtl) {
|| m_daemonBlockChainHeightTime.elapsed() / 1000 > m_daemonBlockChainHeightTtl)
{
m_daemonBlockChainHeight = m_walletImpl->daemonBlockChainHeight();
m_daemonBlockChainHeightTime.restart();
}
@ -382,7 +435,8 @@ quint64 Wallet::daemonBlockChainHeight() const
quint64 Wallet::daemonBlockChainTargetHeight() const
{
if (m_daemonBlockChainTargetHeight <= 1
|| m_daemonBlockChainTargetHeightTime.elapsed() / 1000 > m_daemonBlockChainTargetHeightTtl) {
|| m_daemonBlockChainTargetHeightTime.elapsed() / 1000 > m_daemonBlockChainTargetHeightTtl)
{
m_daemonBlockChainTargetHeight = m_walletImpl->daemonBlockChainTargetHeight();
// Target height is set to 0 if daemon is synced.
@ -396,9 +450,9 @@ quint64 Wallet::daemonBlockChainTargetHeight() const
return m_daemonBlockChainTargetHeight;
}
bool Wallet::exportKeyImages(const QString& path)
bool Wallet::exportKeyImages(const QString& path, bool all)
{
return m_walletImpl->exportKeyImages(path.toStdString());
return m_walletImpl->exportKeyImages(path.toStdString(), all);
}
bool Wallet::importKeyImages(const QString& path)
@ -406,42 +460,40 @@ bool Wallet::importKeyImages(const QString& path)
return m_walletImpl->importKeyImages(path.toStdString());
}
bool Wallet::refresh()
{
bool result = m_walletImpl->refresh();
m_history->refresh(currentSubaddressAccount());
m_subaddress->refresh(currentSubaddressAccount());
m_coins->refresh(currentSubaddressAccount());
m_subaddressAccount->getAll();
if (result)
emit updated();
return result;
bool Wallet::exportOutputs(const QString& path, bool all) {
return m_walletImpl->exportOutputs(path.toStdString(), all);
}
void Wallet::refreshAsync()
{
qDebug() << "refresh async";
m_walletImpl->refreshAsync();
bool Wallet::importOutputs(const QString& path) {
return m_walletImpl->importOutputs(path.toStdString());
}
void Wallet::setAutoRefreshInterval(int seconds)
{
m_walletImpl->setAutoRefreshInterval(seconds);
}
bool Wallet::importTransaction(const QString& txid, const QVector<quint64>& output_indeces, quint64 height, quint64 timestamp, bool miner_tx, bool pool, bool double_spend_seen) {
std::vector<uint64_t> o_indeces;
for (const auto &o : output_indeces) {
o_indeces.push_back(o);
}
int Wallet::autoRefreshInterval() const
{
return m_walletImpl->autoRefreshInterval();
return m_walletImpl->importTransaction(
txid.toStdString(),
o_indeces,
height,
17, // todo: get actual block_version
timestamp,
miner_tx,
pool,
double_spend_seen);
}
void Wallet::startRefresh() const
void Wallet::startRefresh()
{
m_walletImpl->startRefresh();
m_refreshEnabled = true;
m_refreshNow = true;
}
void Wallet::pauseRefresh() const
void Wallet::pauseRefresh()
{
m_walletImpl->pauseRefresh();
m_refreshEnabled = false;
}
PendingTransaction *Wallet::createTransaction(const QString &dst_addr, const QString &payment_id,
@ -450,15 +502,15 @@ PendingTransaction *Wallet::createTransaction(const QString &dst_addr, const QSt
{
std::set<uint32_t> subaddr_indices;
Monero::PendingTransaction * ptImpl = m_walletImpl->createTransaction(
dst_addr.toStdString(), payment_id.toStdString(), amount, mixin_count,
static_cast<Monero::PendingTransaction::Priority>(priority), currentSubaddressAccount(), subaddr_indices);
dst_addr.toStdString(), payment_id.toStdString(), amount, mixin_count,
static_cast<Monero::PendingTransaction::Priority>(priority), currentSubaddressAccount(), subaddr_indices);
PendingTransaction * result = new PendingTransaction(ptImpl,0);
return result;
}
void Wallet::createTransactionAsync(const QString &dst_addr, const QString &payment_id,
quint64 amount, quint32 mixin_count,
PendingTransaction::Priority priority)
quint64 amount, quint32 mixin_count,
PendingTransaction::Priority priority)
{
m_scheduler.run([this, dst_addr, payment_id, amount, mixin_count, priority] {
PendingTransaction *tx = createTransaction(dst_addr, payment_id, amount, mixin_count, priority);
@ -471,15 +523,15 @@ PendingTransaction *Wallet::createTransactionAll(const QString &dst_addr, const
{
std::set<uint32_t> subaddr_indices;
Monero::PendingTransaction * ptImpl = m_walletImpl->createTransaction(
dst_addr.toStdString(), payment_id.toStdString(), Monero::optional<uint64_t>(), mixin_count,
static_cast<Monero::PendingTransaction::Priority>(priority), currentSubaddressAccount(), subaddr_indices);
dst_addr.toStdString(), payment_id.toStdString(), Monero::optional<uint64_t>(), mixin_count,
static_cast<Monero::PendingTransaction::Priority>(priority), currentSubaddressAccount(), subaddr_indices);
PendingTransaction * result = new PendingTransaction(ptImpl, this);
return result;
}
void Wallet::createTransactionAllAsync(const QString &dst_addr, const QString &payment_id,
quint32 mixin_count,
PendingTransaction::Priority priority)
quint32 mixin_count,
PendingTransaction::Priority priority)
{
m_scheduler.run([this, dst_addr, payment_id, mixin_count, priority] {
PendingTransaction *tx = createTransactionAll(dst_addr, payment_id, mixin_count, priority);
@ -501,7 +553,7 @@ void Wallet::createTransactionSingleAsync(const QString &key_image, const QStrin
{
m_scheduler.run([this, key_image, dst_addr, outputs, priority] {
PendingTransaction *tx = createTransactionSingle(key_image, dst_addr, outputs, priority);
emit transactionCreated(tx, dst_addr, "", 0); // todo: return true mixincount
emit transactionCreated(tx, dst_addr, "", 10); // todo: return true mixincount
});
}
@ -528,6 +580,21 @@ UnsignedTransaction * Wallet::loadTxFile(const QString &fileName)
return result;
}
UnsignedTransaction * Wallet::loadTxFromBase64Str(const QString &unsigned_tx)
{
Monero::UnsignedTransaction * ptImpl = m_walletImpl->loadUnsignedTxFromBase64Str(unsigned_tx.toStdString());
UnsignedTransaction * result = new UnsignedTransaction(ptImpl, m_walletImpl, this);
return result;
}
PendingTransaction * Wallet::loadSignedTxFile(const QString &fileName)
{
qDebug() << "Tying to load " << fileName;
Monero::PendingTransaction * ptImpl = m_walletImpl->loadSignedTx(fileName.toStdString());
PendingTransaction * result = new PendingTransaction(ptImpl, this);
return result;
}
bool Wallet::submitTxFile(const QString &fileName) const
{
qDebug() << "Trying to submit " << fileName;
@ -537,6 +604,28 @@ bool Wallet::submitTxFile(const QString &fileName) const
return m_walletImpl->importKeyImages(fileName.toStdString() + "_keyImages");
}
bool Wallet::refresh(bool historyAndSubaddresses /* = true */)
{
refreshingSet(true);
const auto cleanup = sg::make_scope_guard([this]() noexcept {
refreshingSet(false);
});
{
QMutexLocker locker(&m_asyncMutex);
bool result = m_walletImpl->refresh();
if (historyAndSubaddresses)
{
m_history->refresh(currentSubaddressAccount());
m_subaddress->refresh(currentSubaddressAccount());
m_subaddressAccount->getAll();
}
return result;
}
}
void Wallet::commitTransactionAsync(PendingTransaction *t)
{
m_scheduler.run([this, t] {
@ -556,7 +645,6 @@ void Wallet::disposeTransaction(UnsignedTransaction *t)
delete t;
}
// @ TODO: QJSValue could probably be QVARIANT
//void Wallet::estimateTransactionFeeAsync(const QString &destination,
// quint64 amount,
// PendingTransaction::Priority priority,
@ -564,8 +652,8 @@ void Wallet::disposeTransaction(UnsignedTransaction *t)
//{
// m_scheduler.run([this, destination, amount, priority] {
// const uint64_t fee = m_walletImpl->estimateTransactionFee(
// {std::make_pair(destination.toStdString(), amount)},
// static_cast<Monero::PendingTransaction::Priority>(priority));
// {std::make_pair(destination.toStdString(), amount)},
// static_cast<Monero::PendingTransaction::Priority>(priority));
// return QJSValueList({QString::fromStdString(Monero::Wallet::displayAmount(fee))});
// }, callback);
//}
@ -683,17 +771,17 @@ bool Wallet::setCacheAttribute(const QString &key, const QString &val)
bool Wallet::setUserNote(const QString &txid, const QString &note)
{
return m_walletImpl->setUserNote(txid.toStdString(), note.toStdString());
return m_walletImpl->setUserNote(txid.toStdString(), note.toStdString());
}
QString Wallet::getUserNote(const QString &txid) const
{
return QString::fromStdString(m_walletImpl->getUserNote(txid.toStdString()));
return QString::fromStdString(m_walletImpl->getUserNote(txid.toStdString()));
}
QString Wallet::getTxKey(const QString &txid) const
{
return QString::fromStdString(m_walletImpl->getTxKey(txid.toStdString()));
return QString::fromStdString(m_walletImpl->getTxKey(txid.toStdString()));
}
//void Wallet::getTxKeyAsync(const QString &txid, const QJSValue &callback)
@ -713,12 +801,10 @@ QString Wallet::checkTxKey(const QString &txid, const QString &tx_key, const QSt
return QString::fromStdString(result);
}
QString Wallet::getTxProof(const QString &txid, const QString &address, const QString &message) const
TxProof Wallet::getTxProof(const QString &txid, const QString &address, const QString &message) const
{
std::string result = m_walletImpl->getTxProof(txid.toStdString(), address.toStdString(), message.toStdString());
if (result.empty())
result = "error|" + m_walletImpl->errorString();
return QString::fromStdString(result);
return TxProof(QString::fromStdString(result), QString::fromStdString(m_walletImpl->errorString()));
}
//void Wallet::getTxProofAsync(const QString &txid, const QString &address, const QString &message, const QJSValue &callback)
@ -738,12 +824,10 @@ TxProofResult Wallet::checkTxProof(const QString &txid, const QString &address,
return {success, good, received, in_pool, confirmations};
}
Q_INVOKABLE QString Wallet::getSpendProof(const QString &txid, const QString &message) const
Q_INVOKABLE TxProof Wallet::getSpendProof(const QString &txid, const QString &message) const
{
std::string result = m_walletImpl->getSpendProof(txid.toStdString(), message.toStdString());
if (result.empty())
result = "error|" + m_walletImpl->errorString();
return QString::fromStdString(result);
return TxProof(QString::fromStdString(result), QString::fromStdString(m_walletImpl->errorString()));
}
//void Wallet::getSpendProofAsync(const QString &txid, const QString &message, const QJSValue &callback)
@ -831,7 +915,6 @@ bool Wallet::verifySignedMessage(const QString &message, const QString &address,
return m_walletImpl->verifySignedMessage(message.toStdString(), address.toStdString(), signature.toStdString());
}
}
bool Wallet::parse_uri(const QString &uri, QString &address, QString &payment_id, uint64_t &amount, QString &tx_description, QString &recipient_name, QVector<QString> &unknown_parameters, QString &error)
{
std::string s_address, s_payment_id, s_tx_description, s_recipient_name, s_error;
@ -852,6 +935,8 @@ bool Wallet::parse_uri(const QString &uri, QString &address, QString &payment_id
bool Wallet::rescanSpent()
{
QMutexLocker locker(&m_asyncMutex);
return m_walletImpl->rescanSpent();
}
@ -928,8 +1013,8 @@ QString Wallet::getRing(const QString &key_image)
{
if (!ring.isEmpty())
ring = ring + " ";
QString s;
s.setNum(out);
QString s;
s.setNum(out);
ring = ring + s;
}
return ring;
@ -949,8 +1034,8 @@ QString Wallet::getRings(const QString &txid)
for (uint64_t out: cring.second)
{
ring = ring + " ";
QString s;
s.setNum(out);
QString s;
s.setNum(out);
ring = ring + s;
}
}
@ -964,9 +1049,9 @@ bool Wallet::setRing(const QString &key_image, const QString &ring, bool relativ
foreach(QString str, strOuts)
{
uint64_t out;
bool ok;
out = str.toULong(&ok);
if (ok)
bool ok;
out = str.toULong(&ok);
if (ok)
cring.push_back(out);
}
return m_walletImpl->setRing(key_image.toStdString(), cring, relative);
@ -992,6 +1077,14 @@ void Wallet::onWalletPassphraseNeeded(bool on_device)
emit this->walletPassphraseNeeded(on_device);
}
quint64 Wallet::getBytesReceived() const {
return m_walletImpl->getBytesReceived();
}
quint64 Wallet::getBytesSent() const {
return m_walletImpl->getBytesSent();
}
void Wallet::onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort)
{
if (m_walletListener != nullptr)
@ -1001,26 +1094,30 @@ void Wallet::onPassphraseEntered(const QString &passphrase, bool enter_on_device
}
Wallet::Wallet(Monero::Wallet *w, QObject *parent)
: QObject(parent)
, m_walletImpl(w)
, m_history(nullptr)
, m_historyModel(nullptr)
, m_addressBook(nullptr)
, m_addressBookModel(nullptr)
, m_daemonBlockChainHeight(0)
, m_daemonBlockChainHeightTtl(DAEMON_BLOCKCHAIN_HEIGHT_CACHE_TTL_SECONDS)
, m_daemonBlockChainTargetHeight(0)
, m_daemonBlockChainTargetHeightTtl(DAEMON_BLOCKCHAIN_TARGET_HEIGHT_CACHE_TTL_SECONDS)
, m_connectionStatus(Wallet::ConnectionStatus_Disconnected)
, m_connectionStatusTtl(WALLET_CONNECTION_STATUS_CACHE_TTL_SECONDS)
, m_disconnected(true)
, m_currentSubaddressAccount(0)
, m_subaddress(nullptr)
, m_subaddressModel(nullptr)
, m_subaddressAccount(nullptr)
, m_subaddressAccountModel(nullptr)
, m_coinsModel(nullptr)
, m_scheduler(this)
: QObject(parent)
, m_walletImpl(w)
, m_history(nullptr)
, m_historyModel(nullptr)
, m_addressBook(nullptr)
, m_addressBookModel(nullptr)
, m_daemonBlockChainHeight(0)
, m_daemonBlockChainHeightTtl(DAEMON_BLOCKCHAIN_HEIGHT_CACHE_TTL_SECONDS)
, m_daemonBlockChainTargetHeight(0)
, m_daemonBlockChainTargetHeightTtl(DAEMON_BLOCKCHAIN_TARGET_HEIGHT_CACHE_TTL_SECONDS)
, m_connectionStatus(Wallet::ConnectionStatus_Disconnected)
, m_connectionStatusTtl(WALLET_CONNECTION_STATUS_CACHE_TTL_SECONDS)
, m_disconnected(true)
, m_currentSubaddressAccount(0)
, m_subaddress(nullptr)
, m_subaddressModel(nullptr)
, m_subaddressAccount(nullptr)
, m_subaddressAccountModel(nullptr)
, m_coinsModel(nullptr)
, m_refreshNow(false)
, m_refreshEnabled(false)
, m_refreshing(false)
, m_scheduler(this)
, m_useSSL(true)
{
m_history = new TransactionHistory(m_walletImpl->history(), this);
m_addressBook = new AddressBook(m_walletImpl->addressBook(), this);
@ -1038,11 +1135,16 @@ Wallet::Wallet(Monero::Wallet *w, QObject *parent)
m_connectionStatusRunning = false;
m_daemonUsername = "";
m_daemonPassword = "";
startRefreshThread();
}
Wallet::~Wallet()
{
qDebug() << "~Wallet: Closing wallet";
qDebug("~Wallet: Closing wallet");
pauseRefresh();
m_walletImpl->stop();
m_scheduler.shutdownWaitForFinished();
@ -1062,13 +1164,52 @@ Wallet::~Wallet()
//Monero::WalletManagerFactory::getWalletManager()->closeWallet(m_walletImpl);
if(status() == Status_Critical)
qDebug("Not storing wallet cache");
else if( m_walletImpl->store(""))
qDebug("Wallet cache stored successfully");
else
qDebug("Error storing wallet cache");
// Don't store on wallet close for now
// else if( m_walletImpl->store(""))
// qDebug("Wallet cache stored successfully");
// else
// qDebug("Error storing wallet cache");
delete m_walletImpl;
m_walletImpl = NULL;
delete m_walletListener;
m_walletListener = NULL;
qDebug("m_walletImpl deleted");
}
void Wallet::startRefreshThread()
{
const auto future = m_scheduler.run([this] {
constexpr const std::chrono::seconds refreshInterval{10};
constexpr const std::chrono::milliseconds intervalResolution{100};
auto last = std::chrono::steady_clock::now();
while (!m_scheduler.stopping())
{
if (m_refreshEnabled)
{
const auto now = std::chrono::steady_clock::now();
const auto elapsed = now - last;
if (elapsed >= refreshInterval || m_refreshNow)
{
m_refreshNow = false;
refresh(false);
last = std::chrono::steady_clock::now();
}
}
std::this_thread::sleep_for(intervalResolution);
}
});
if (!future.first)
{
throw std::runtime_error("failed to start auto refresh thread");
}
}
void Wallet::onRefreshed(bool success) {
if (success) {
setConnectionStatus(ConnectionStatus_Connected);
} else {
setConnectionStatus(ConnectionStatus_Disconnected);
}
}

@ -8,7 +8,6 @@
#include <QObject>
#include <QMutex>
#include <QList>
//#include <QJSValue>
#include <QtConcurrent/QtConcurrent>
#include "wallet/api/wallet2_api.h" // we need to have an access to the Monero::Wallet::Status enum here;
@ -20,14 +19,20 @@
#include "WalletListenerImpl.h"
namespace Monero {
struct Wallet; // forward declaration
struct Wallet; // forward declaration
}
struct TxProof {
TxProof(QString proof, QString error = "")
: proof(std::move(proof)), error(std::move(error)){}
QString proof;
QString error;
};
class TransactionHistory;
class TransactionHistoryModel;
class TransactionHistoryProxyModel;
class TransactionHistorySortFilterModel;
class AddressBook;
class AddressBookModel;
class Subaddress;
@ -50,13 +55,14 @@ struct TxProofResult {
class Wallet : public QObject, public PassprasePrompter
{
Q_OBJECT
Q_OBJECT
Q_PROPERTY(bool disconnected READ disconnected NOTIFY disconnectedChanged)
Q_PROPERTY(bool refreshing READ refreshing NOTIFY refreshingChanged)
Q_PROPERTY(QString seed READ getSeed)
Q_PROPERTY(QString seedLanguage READ getSeedLanguage)
Q_PROPERTY(Status status READ status)
Q_PROPERTY(NetworkType::Type nettype READ nettype)
Q_PROPERTY(ConnectionStatus connected READ connected)
Q_PROPERTY(ConnectionStatus connectionStatus READ connectionStatus)
Q_PROPERTY(quint32 currentSubaddressAccount READ currentSubaddressAccount NOTIFY currentSubaddressAccountChanged)
Q_PROPERTY(bool synchronized READ synchronized)
Q_PROPERTY(QString errorString READ errorString)
@ -76,6 +82,7 @@ class Wallet : public QObject, public PassprasePrompter
Q_PROPERTY(QString secretSpendKey READ getSecretSpendKey)
Q_PROPERTY(QString publicSpendKey READ getPublicSpendKey)
Q_PROPERTY(QString daemonLogPath READ getDaemonLogPath CONSTANT)
Q_PROPERTY(QString proxyAddress READ getProxyAddress WRITE setProxyAddress NOTIFY proxyAddressChanged)
Q_PROPERTY(quint64 walletCreationHeight READ getWalletCreationHeight WRITE setWalletCreationHeight NOTIFY walletCreationHeightChanged)
public:
@ -98,6 +105,9 @@ public:
Q_ENUM(ConnectionStatus)
//! return connection status
ConnectionStatus connectionStatus() const;
//! returns mnemonic seed
QString getSeed() const;
@ -113,19 +123,19 @@ public:
//! returns network type of the wallet.
NetworkType::Type nettype() const;
//! returns whether the wallet is connected, and version status
Q_INVOKABLE ConnectionStatus connected(bool forceCheck = false);
void updateConnectionStatusAsync();
//! returns true if wallet was ever synchronized
bool synchronized() const;
//! returns last operation's error message
QString errorString() const;
//! changes the password using existing parameters (path, seed, seed lang)
Q_INVOKABLE bool setPassword(const QString &password);
//! get current wallet password
Q_INVOKABLE QString getPassword();
//! returns wallet's public address
Q_INVOKABLE QString address(quint32 accountIndex, quint32 addressIndex) const;
@ -138,7 +148,14 @@ public:
// Q_INVOKABLE void storeAsync(const QJSValue &callback, const QString &path = "");
//! initializes wallet asynchronously
Q_INVOKABLE void initAsync(const QString &daemonAddress, bool trustedDaemon = false, quint64 upperTransactionLimit = 0, bool isRecovering = false, bool isRecoveringFromDevice = false, quint64 restoreHeight = 0);
Q_INVOKABLE void initAsync(
const QString &daemonAddress,
bool trustedDaemon = false,
quint64 upperTransactionLimit = 0,
bool isRecovering = false,
bool isRecoveringFromDevice = false,
quint64 restoreHeight = 0,
const QString &proxyAddress = "");
// Set daemon rpc user/pass
Q_INVOKABLE void setDaemonLogin(const QString &daemonUsername = "", const QString &daemonPassword = "");
@ -149,9 +166,15 @@ public:
//! connects to daemon
Q_INVOKABLE bool connectToDaemon();
//! indicates id daemon is trusted
//! set connect to daemon timeout
Q_INVOKABLE void setConnectionTimeout(int timeout);
//! indicates if daemon is trusted
Q_INVOKABLE void setTrustedDaemon(bool arg);
//! indicates if ssl should be used to connect to daemon
Q_INVOKABLE void setUseSSL(bool ssl);
//! returns balance
Q_INVOKABLE quint64 balance() const;
Q_INVOKABLE quint64 balance(quint32 accountIndex) const;
@ -184,24 +207,22 @@ public:
Q_INVOKABLE void refreshHeightAsync();
//! export/import key images
Q_INVOKABLE bool exportKeyImages(const QString& path);
Q_INVOKABLE bool exportKeyImages(const QString& path, bool all = false);
Q_INVOKABLE bool importKeyImages(const QString& path);
//! refreshes the wallet
Q_INVOKABLE bool refresh();
//! refreshes the wallet asynchronously
Q_INVOKABLE void refreshAsync();
//! export/import outputs
Q_INVOKABLE bool exportOutputs(const QString& path, bool all = false);
Q_INVOKABLE bool importOutputs(const QString& path);
//! setup auto-refresh interval in seconds
Q_INVOKABLE void setAutoRefreshInterval(int seconds);
//! import a transaction
Q_INVOKABLE bool importTransaction(const QString& txid, const QVector<quint64>& output_indeces, quint64 height, quint64 timestamp, bool miner_tx, bool pool, bool double_spend_seen);
//! return auto-refresh interval in seconds
Q_INVOKABLE int autoRefreshInterval() const;
//! refreshes the wallet
Q_INVOKABLE bool refresh(bool historyAndSubaddresses = false);
// pause/resume refresh
Q_INVOKABLE void startRefresh() const;
Q_INVOKABLE void pauseRefresh() const;
Q_INVOKABLE void startRefresh();
Q_INVOKABLE void pauseRefresh();
//! returns current wallet's block height
//! (can be less than daemon's blockchain height when wallet sync in progress)
@ -225,7 +246,7 @@ public:
//! creates transaction with all outputs
Q_INVOKABLE PendingTransaction * createTransactionAll(const QString &dst_addr, const QString &payment_id,
quint32 mixin_count, PendingTransaction::Priority priority);
quint32 mixin_count, PendingTransaction::Priority priority);
//! creates async transaction with all outputs
Q_INVOKABLE void createTransactionAllAsync(const QString &dst_addr, const QString &payment_id,
@ -248,6 +269,12 @@ public:
//! Sign a transfer from file
Q_INVOKABLE UnsignedTransaction * loadTxFile(const QString &fileName);
//! Load an unsigned transaction from a base64 encoded string
Q_INVOKABLE UnsignedTransaction * loadTxFromBase64Str(const QString &unsigned_tx);
//! Load a signed transaction from file
Q_INVOKABLE PendingTransaction * loadSignedTxFile(const QString &fileName);
//! Submit a transfer from file
Q_INVOKABLE bool submitTxFile(const QString &fileName) const;
@ -327,11 +354,11 @@ public:
Q_INVOKABLE QString getTxKey(const QString &txid) const;
//Q_INVOKABLE void getTxKeyAsync(const QString &txid, const QJSValue &callback);
Q_INVOKABLE QString checkTxKey(const QString &txid, const QString &tx_key, const QString &address);
Q_INVOKABLE QString getTxProof(const QString &txid, const QString &address, const QString &message) const;
Q_INVOKABLE TxProof getTxProof(const QString &txid, const QString &address, const QString &message) const;
// Q_INVOKABLE void getTxProofAsync(const QString &txid, const QString &address, const QString &message, const QJSValue &callback);
//Q_INVOKABLE QString checkTxProof(const QString &txid, const QString &address, const QString &message, const QString &signature);
Q_INVOKABLE TxProofResult checkTxProof(const QString &txid, const QString &address, const QString &message, const QString &signature);
Q_INVOKABLE QString getSpendProof(const QString &txid, const QString &message) const;
Q_INVOKABLE TxProof getSpendProof(const QString &txid, const QString &message) const;
// Q_INVOKABLE void getSpendProofAsync(const QString &txid, const QString &message, const QJSValue &callback);
Q_INVOKABLE QPair<bool, bool> checkSpendProof(const QString &txid, const QString &message, const QString &signature) const;
// Rescan spent outputs
@ -372,6 +399,9 @@ public:
Q_INVOKABLE void onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort=false);
virtual void onWalletPassphraseNeeded(bool on_device) override;
Q_INVOKABLE quint64 getBytesReceived() const;
Q_INVOKABLE quint64 getBytesSent() const;
// TODO: setListenter() when it implemented in API
signals:
// emitted on every event happened with wallet
@ -380,7 +410,7 @@ signals:
// emitted when refresh process finished (could take a long time)
// signalling only after we
void refreshed();
void refreshed(bool success);
void moneySpent(const QString &txId, quint64 amount);
void moneyReceived(const QString &txId, quint64 amount);
@ -402,6 +432,8 @@ signals:
void connectionStatusChanged(int status) const;
void currentSubaddressAccountChanged() const;
void disconnectedChanged() const;
void proxyAddressChanged() const;
void refreshingChanged() const;
private:
Wallet(QObject * parent = nullptr);
@ -409,10 +441,24 @@ private:
~Wallet();
//! initializes wallet
bool init(const QString &daemonAddress, bool trustedDaemon, quint64 upperTransactionLimit, bool isRecovering, bool isRecoveringFromDevice, quint64 restoreHeight);
bool init(
const QString &daemonAddress,
bool trustedDaemon,
quint64 upperTransactionLimit,
bool isRecovering,
bool isRecoveringFromDevice,
quint64 restoreHeight,
const QString& proxyAddress);
bool disconnected() const;
bool refreshing() const;
void refreshingSet(bool value);
void onRefreshed(bool success);
void setConnectionStatus(ConnectionStatus value);
QString getProxyAddress() const;
void setProxyAddress(QString address);
void startRefreshThread();
private:
friend class WalletManager;
@ -445,13 +491,20 @@ private:
mutable SubaddressAccountModel * m_subaddressAccountModel;
Coins * m_coins;
mutable CoinsModel * m_coinsModel;
QMutex m_asyncMutex;
QMutex m_connectionStatusMutex;
bool m_connectionStatusRunning;
QString m_daemonUsername;
QString m_daemonPassword;
QString m_proxyAddress;
mutable QMutex m_proxyMutex;
std::atomic<bool> m_refreshNow;
std::atomic<bool> m_refreshEnabled;
std::atomic<bool> m_refreshing;
WalletListenerImpl *m_walletListener;
FutureScheduler m_scheduler;
QMutex m_storeMutex;
int m_connectionTimeout = 30;
bool m_useSSL;
};

@ -41,10 +41,11 @@ void WalletListenerImpl::updated()
}
// called when wallet refreshed by background thread or explicitly
void WalletListenerImpl::refreshed()
void WalletListenerImpl::refreshed(bool success)
{
qDebug() << __FUNCTION__;
emit m_wallet->refreshed();
m_wallet->onRefreshed(success);
emit m_wallet->refreshed(success);
}
void WalletListenerImpl::onDeviceButtonRequest(uint64_t code)

@ -25,7 +25,7 @@ public:
virtual void updated() override;
// called when wallet refreshed by background thread or explicitly
virtual void refreshed() override;
virtual void refreshed(bool success) override;
virtual void onDeviceButtonRequest(uint64_t code) override;

@ -26,7 +26,7 @@ public:
virtual void unconfirmedMoneyReceived(const std::string &txId, uint64_t amount) override { (void)txId; (void)amount; };
virtual void newBlock(uint64_t height) override { (void) height; };
virtual void updated() override {};
virtual void refreshed() override {};
virtual void refreshed(bool success) override {};
virtual void onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort) override
{
@ -335,7 +335,7 @@ bool WalletManager::isMining() const
{
{
QMutexLocker locker(&m_mutex);
if (m_currentWallet == nullptr || !m_currentWallet->connected())
if (m_currentWallet == nullptr || !m_currentWallet->connectionStatus())
{
return false;
}

@ -22,12 +22,14 @@ int main(int argc, char *argv[])
{
Q_INIT_RESOURCE(assets);
#if defined(Q_OS_MAC) && defined(BUILD_TOR)
Q_INIT_RESOURCE(assets_macos_tor);
#elif defined(Q_OS_LINUX) && defined(BUILD_TOR)
Q_INIT_RESOURCE(assets_linux_tor);
#elif defined(Q_OS_WIN) && defined(BUILD_TOR)
Q_INIT_RESOURCE(assets_windows_tor);
#if defined(Q_OS_MAC) && defined(HAS_TOR)
Q_INIT_RESOURCE(assets_tor_macos);
#elif defined(HAS_TOR)
Q_INIT_RESOURCE(assets_tor);
#endif
#if defined(HAS_XMRIG)
Q_INIT_RESOURCE(assets_mining);
#endif
QStringList argv_;

@ -13,11 +13,17 @@
#include "widgets/ccswidget.h"
#include "widgets/redditwidget.h"
#include "dialog/txconfdialog.h"
#include "dialog/txconfadvdialog.h"
#include "dialog/debuginfodialog.h"
#include "dialog/walletinfodialog.h"
#include "dialog/torinfodialog.h"
#include "dialog/viewonlydialog.h"
#include "dialog/broadcasttxdialog.h"
#include "dialog/tximportdialog.h"
#include "dialog/passworddialog.h"
#include "utils/utils.h"
#include "utils/config.h"
#include "utils/daemonrpc.h"
#include "components.h"
#include "calcwindow.h"
#include "ui_mainwindow.h"
@ -99,8 +105,6 @@ MainWindow::MainWindow(AppContext *ctx, QWidget *parent) :
});
connect(ui->actionShow_debug_info, &QAction::triggered, this, &MainWindow::showDebugInfo);
connect(ui->actionOfficialWebsite, &QAction::triggered, [=] { Utils::externalLinkWarning("https://featherwallet.org"); });
connect(ui->actionAbout_Qt, &QAction::triggered, [=] { QApplication::aboutQt(); });
#if defined(XMRTO)
// xmr.to connects/widget
@ -114,9 +118,12 @@ MainWindow::MainWindow(AppContext *ctx, QWidget *parent) :
connect(m_ctx->XMRTo, &XmrTo::openURL, this, [=](const QString &url){ Utils::externalLinkWarning(url); });
ui->xmrToWidget->setHistoryModel(m_ctx->XMRTo->tableModel);
#else
ui->tabWidget->setTabVisible(5, false);
ui->tabWidget->setTabVisible(Tabs::XMR_TO, false);
#endif
#ifndef HAS_MORPHTOKEN
ui->tabWidget->setTabVisible(Tabs::MORPHTOKEN, false);
#endif
#if defined(Q_OS_LINUX)
// system tray
@ -165,6 +172,24 @@ MainWindow::MainWindow(AppContext *ctx, QWidget *parent) :
connect(m_ctx->nodes, &Nodes::nodeExhausted, this, &MainWindow::showNodeExhaustedMessage);
connect(m_ctx->nodes, &Nodes::WSNodeExhausted, this, &MainWindow::showWSNodeExhaustedMessage);
// XMRig
#ifdef HAS_XMRIG
m_xmrig = new XMRigWidget(m_ctx, this);
ui->xmrRigLayout->addWidget(m_xmrig);
connect(m_ctx->XMRig, &XmRig::output, m_xmrig, &XMRigWidget::onProcessOutput);
connect(m_ctx->XMRig, &XmRig::error, m_xmrig, &XMRigWidget::onProcessError);
connect(m_ctx->XMRig, &XmRig::hashrate, m_xmrig, &XMRigWidget::onHashrate);
connect(m_ctx, &AppContext::walletClosed, m_xmrig, &XMRigWidget::onWalletClosed);
connect(m_ctx, &AppContext::walletOpened, m_xmrig, &XMRigWidget::onWalletOpened);
connect(m_ctx, &AppContext::XMRigDownloads, m_xmrig, &XMRigWidget::onDownloads);
connect(m_xmrig, &XMRigWidget::miningStarted, [=]{ m_ctx->setWindowTitle(true); });
connect(m_xmrig, &XMRigWidget::miningEnded, [=]{ m_ctx->setWindowTitle(false); });
#else
ui->tabWidget->setTabVisible(Tabs::XMRIG, false);
#endif
// CCS/Reddit widget
m_ccsWidget = new CCSWidget(this);
m_redditWidget = new RedditWidget(this);
@ -255,6 +280,7 @@ MainWindow::MainWindow(AppContext *ctx, QWidget *parent) :
connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, tickerWidget, &TickerWidget::init);
connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, m_balanceWidget, &TickerWidget::init);
connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, m_ctx, &AppContext::onPreferredFiatCurrencyChanged);
connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, ui->sendWidget, QOverload<>::of(&SendWidget::onPreferredFiatCurrencyChanged));
// CCS/Reddit widget
connect(m_windowSettings, &Settings::homeWidgetChanged, this, &MainWindow::homeWidgetChanged);
@ -275,13 +301,13 @@ MainWindow::MainWindow(AppContext *ctx, QWidget *parent) :
});
connect(ui->receiveWidget, &ReceiveWidget::showTransactions, [this](const QString &text) {
ui->historyWidget->setSearchText(text);
ui->tabWidget->setCurrentIndex(1); //history
ui->tabWidget->setCurrentIndex(Tabs::HISTORY);
});
// History
connect(ui->historyWidget, &HistoryWidget::spendProof, [&](const QString &txid){
QString spendProof = m_ctx->currentWallet->getSpendProof(txid, "");
Utils::copyToClipboard(spendProof);
TxProof txproof = m_ctx->currentWallet->getSpendProof(txid, "");
Utils::copyToClipboard(txproof.proof);
});
connect(ui->historyWidget, &HistoryWidget::viewOnBlockExplorer, this, &MainWindow::onViewOnBlockExplorer);
@ -323,12 +349,26 @@ MainWindow::MainWindow(AppContext *ctx, QWidget *parent) :
connect(ui->coinsWidget, &CoinsWidget::sweepOutput, m_ctx, &AppContext::onSweepOutput);
connect(m_ctx, &AppContext::walletClosing, [=]{
ui->tabWidget->setCurrentIndex(0);
ui->tabWidget->setCurrentIndex(Tabs::HOME);
});
// window title
connect(m_ctx, &AppContext::setTitle, this, &QMainWindow::setWindowTitle);
// init touchbar
#ifdef Q_OS_MAC
m_touchbar = new KDMacTouchBar(this);
m_touchbarActionWelcome = new QAction(QIcon(":/assets/images/feather.png"), "Welcome to Feather!");
m_touchbarWalletItems = {ui->actionSettings, ui->actionCalculator, ui->actionKeys, ui->actionDonate_to_Feather};
m_touchbarWizardItems = {m_touchbarActionWelcome};
#endif
// setup some UI
this->initMain();
this->initWidgets();
this->initMenu();
connect(&m_updateBytes, &QTimer::timeout, this, &MainWindow::updateNetStats);
}
void MainWindow::initMain() {
@ -356,25 +396,51 @@ void MainWindow::initMain() {
this->show();
m_wizard = this->createWizard(WalletWizard::Page_Menu);
m_wizard->show();
this->touchbarShowWizard();
}
void MainWindow::initMenu() {
// setup shortcuts
ui->actionStore_wallet->setShortcut(QKeySequence("Ctrl+S"));
ui->actionRefresh_tabs->setShortcut(QKeySequence("Ctrl+R"));
ui->actionClose->setShortcut(QKeySequence("Ctrl+W"));
ui->actionShow_debug_info->setShortcut(QKeySequence("Ctrl+D"));
ui->actionSettings->setShortcut(QKeySequence("Ctrl+Alt+S"));
// hide/show tabs
m_tabShowHideSignalMapper = new QSignalMapper(this);
connect(ui->actionShow_Coins, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map));
m_tabShowHideMapper["Coins"] = new ToggleTab(ui->tabCoins, "Coins", "Coins", ui->actionShow_Coins, Config::showTabCoins);
m_tabShowHideSignalMapper->setMapping(ui->actionShow_Coins, "Coins");
connect(ui->actionShow_calc, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map));
m_tabShowHideMapper["Calc"] = new ToggleTab(ui->tabCalc, "Calc", "Calc", ui->actionShow_calc, Config::showTabCalc);
m_tabShowHideSignalMapper->setMapping(ui->actionShow_calc, "Calc");
#if defined(HAS_MORPHTOKEN)
connect(ui->actionShow_MorphToken, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map));
m_tabShowHideMapper["MorphToken"] = new ToggleTab(ui->tabMorphToken, "MorphToken", "MorphToken", ui->actionShow_MorphToken, Config::showTabMorphToken);
m_tabShowHideSignalMapper->setMapping(ui->actionShow_MorphToken, "MorphToken");
#else
ui->actionShow_MorphToken->setVisible(false);
#endif
#if defined(XMRTO)
connect(ui->actionShow_xmr_to, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map));
m_tabShowHideMapper["XMRto"] = new ToggleTab(ui->tabXmrTo, "XMRto", "xmr.to", ui->actionShow_xmr_to, Config::showTabXMRto);
m_tabShowHideMapper["XMRto"] = new ToggleTab(ui->tabXmrTo, "XMRto", "XMR.to", ui->actionShow_xmr_to, Config::showTabXMRto);
m_tabShowHideSignalMapper->setMapping(ui->actionShow_xmr_to, "XMRto");
#else
ui->actionShow_xmr_to->setVisible(false);
#endif
connect(ui->actionShow_calc, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map));
m_tabShowHideMapper["Calc"] = new ToggleTab(ui->tabCalc, "Calc", "Calc", ui->actionShow_calc, Config::showTabCalc);
m_tabShowHideSignalMapper->setMapping(ui->actionShow_calc, "Calc");
#if defined(HAS_XMRIG)
connect(ui->actionShow_XMRig, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map));
m_tabShowHideMapper["XMRig"] = new ToggleTab(ui->tabXmrRig, "XMRig", "XMRig", ui->actionShow_XMRig, Config::showTabXMRig);
m_tabShowHideSignalMapper->setMapping(ui->actionShow_XMRig, "XMRig");
#else
ui->actionShow_XMRig->setVisible(false);
#endif
for (const auto &key: m_tabShowHideMapper.keys()) {
const auto toggleTab = m_tabShowHideMapper.value(key);
@ -389,6 +455,7 @@ void MainWindow::initMenu() {
connect(ui->actionSeed, &QAction::triggered, this, &MainWindow::showSeedDialog);
connect(ui->actionPassword, &QAction::triggered, this, &MainWindow::showPasswordDialog);
connect(ui->actionKeys, &QAction::triggered, this, &MainWindow::showKeysDialog);
connect(ui->actionViewOnly, &QAction::triggered, this, &MainWindow::showViewOnlyDialog);
connect(ui->actionStore_wallet, &QAction::triggered, [&]{
m_ctx->currentWallet->store();
});
@ -398,6 +465,10 @@ void MainWindow::initMenu() {
connect(ui->actionUpdate_balance, &QAction::triggered, [&]{
m_ctx->updateBalance();
});
connect(ui->actionExportKeyImages, &QAction::triggered, this, &MainWindow::exportKeyImages);
connect(ui->actionImportKeyImages, &QAction::triggered, this, &MainWindow::importKeyImages);
connect(ui->actionExportOutputs, &QAction::triggered, this, &MainWindow::exportOutputs);
connect(ui->actionImportOutputs, &QAction::triggered, this, &MainWindow::importOutputs);
// set restore height
connect(ui->actionChange_restore_height, &QAction::triggered, this, &MainWindow::showRestoreHeightDialog);
@ -412,7 +483,8 @@ void MainWindow::initMenu() {
connect(ui->actionExport_CSV, &QAction::triggered, [=]{
if(m_ctx->currentWallet == nullptr) return;
QString fn = QFileDialog::getSaveFileName(this, "Save CSV file", QDir::homePath(), "CSV (*.csv)");
if(!fn.startsWith(".csv")) fn += ".csv";
if(fn.isEmpty()) return;
if(!fn.endsWith(".csv")) fn += ".csv";
m_ctx->currentWallet->history()->writeCSV(fn);
Utils::showMessageBox("CSV export", QString("Transaction history exported to %1").arg(fn), false);
});
@ -435,9 +507,16 @@ void MainWindow::initMenu() {
Utils::showMessageBox("Address book exported", QString("Address book exported to %1").arg(fn), false);
});
connect(ui->actionImportContactsCSV, &QAction::triggered, this, &MainWindow::importContacts);
// Tools
connect(ui->actionSignVerify, &QAction::triggered, this, &MainWindow::menuSignVerifyClicked);
connect(ui->actionVerifyTxProof, &QAction::triggered, this, &MainWindow::menuVerifyTxProof);
connect(ui->actionLoadUnsignedTxFromFile, &QAction::triggered, this, &MainWindow::loadUnsignedTx);
connect(ui->actionLoadUnsignedTxFromClipboard, &QAction::triggered, this, &MainWindow::loadUnsignedTxFromClipboard);
connect(ui->actionLoadSignedTxFromFile, &QAction::triggered, this, &MainWindow::loadSignedTx);
connect(ui->actionLoadSignedTxFromText, &QAction::triggered, this, &MainWindow::loadSignedTxFromText);
connect(ui->actionImport_transaction, &QAction::triggered, this, &MainWindow::importTransaction);
// About screen
connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::menuAboutClicked);
@ -477,7 +556,11 @@ WalletWizard *MainWindow::createWizard(WalletWizard::Page startPage){
void MainWindow::showWizard(WalletWizard::Page startPage) {
this->setEnabled(false);
m_wizard = this->createWizard(startPage);
if (m_wizard == nullptr)
m_wizard = this->createWizard(startPage);
m_wizard->setStartId(startPage);
m_wizard->restart();
m_wizard->setEnabled(true);
m_wizard->show();
}
@ -493,24 +576,41 @@ void MainWindow::onWalletClosed(WalletWizard::Page page) {
this->showWizard(page);
}
void MainWindow::touchbarShowWizard() {
#ifdef Q_OS_MAC
m_touchbar->clear();
for(auto* action: m_touchbarWizardItems) m_touchbar->addAction(action);
#endif
}
void MainWindow::touchbarShowWallet() {
#ifdef Q_OS_MAC
m_touchbar->clear();
for(auto* action: m_touchbarWalletItems) m_touchbar->addAction(action);
#endif
}
void MainWindow::onWalletCreatedError(const QString &err) {
Utils::showMessageBox("Wallet creation error", err, true);
this->showWizard(WalletWizard::Page_CreateWallet);
}
void MainWindow::onWalletOpenPasswordRequired(bool invalidPassword) {
QInputDialog passwordDialog(this);
passwordDialog.setInputMode(QInputDialog::TextInput);
passwordDialog.setTextEchoMode(QLineEdit::Password);
passwordDialog.setWindowTitle("Password required");
passwordDialog.setLabelText("Please enter wallet password.");
passwordDialog.resize(300, 100);
if(!(bool)passwordDialog.exec())
return this->showWizard(WalletWizard::Page_OpenWallet);
void MainWindow::onWalletOpenPasswordRequired(bool invalidPassword, const QString &path) {
QFileInfo fileInfo(path);
auto dialog = new PasswordDialog(this, fileInfo.fileName(), invalidPassword);
switch (dialog->exec()) {
case QDialog::Rejected:
{
this->showWizard(WalletWizard::Page_OpenWallet);
return;
}
}
const auto passwd = passwordDialog.textValue();
m_ctx->walletPassword = passwd;
m_ctx->walletPassword = dialog->password;
m_ctx->onOpenWallet(m_ctx->walletPath, m_ctx->walletPassword);
dialog->deleteLater();
}
void MainWindow::onWalletOpenedError(const QString &err) {
@ -518,6 +618,7 @@ void MainWindow::onWalletOpenedError(const QString &err) {
QMessageBox::warning(this, "Wallet open error", err);
this->setWindowTitle("Feather");
this->showWizard(WalletWizard::Page_OpenWallet);
this->touchbarShowWizard();
}
void MainWindow::onWalletCreated(Wallet *wallet) {
@ -530,8 +631,6 @@ void MainWindow::onWalletOpened() {
qDebug() << Q_FUNC_INFO;
if(m_wizard != nullptr) {
m_wizard->hide();
m_wizard->disconnect();
m_wizard->deleteLater();
}
this->raise();
@ -543,10 +642,6 @@ void MainWindow::onWalletOpened() {
else
m_statusLabelStatus->setText("Wallet opened - Searching for node");
// window title as wallet name
QFileInfo fileInfo(m_ctx->walletPath);
this->setWindowTitle(QString("Feather - [%1]").arg(fileInfo.fileName()));
connect(m_ctx->currentWallet, &Wallet::connectionStatusChanged, this, &MainWindow::onConnectionStatusChanged);
// receive page
@ -563,7 +658,7 @@ void MainWindow::onWalletOpened() {
// history page
m_ctx->currentWallet->history()->refresh(m_ctx->currentWallet->currentSubaddressAccount());
ui->historyWidget->setModel(m_ctx->currentWallet->coins(), m_ctx->currentWallet->historyModel(), m_ctx->currentWallet->history());
ui->historyWidget->setModel(m_ctx->currentWallet->historyModel(), m_ctx->currentWallet);
connect(m_ctx->currentWallet->history(), &TransactionHistory::txNoteChanged, [this]{
m_ctx->storeWallet();
});
@ -583,17 +678,31 @@ void MainWindow::onWalletOpened() {
connect(m_ctx->currentWallet->coins(), &Coins::coinThawed, [this]{
m_ctx->storeWallet();
});
this->touchbarShowWallet();
this->updatePasswordIcon();
m_updateBytes.start(1000);
}
void MainWindow::onBalanceUpdated(double balance, double unlocked, const QString &balance_str, const QString &unlocked_str) {
qDebug() << Q_FUNC_INFO;
bool hide = config()->get(Config::hideBalance).toBool();
auto label_str = QString("Balance: %1 XMR").arg(unlocked_str);
if(balance > unlocked)
label_str += QString(" (+%1 XMR unconfirmed)").arg(QString::number(balance - unlocked, 'f'));
if (hide) {
label_str = "Balance: HIDDEN";
}
m_statusLabelBalance->setText(label_str);
m_balanceWidget->setHidden(hide);
}
void MainWindow::onSynchronized() {
this->updateNetStats();
m_statusLabelStatus->setText("Synchronized");
this->onConnectionStatusChanged(Wallet::ConnectionStatus_Connected);
}
@ -606,11 +715,12 @@ void MainWindow::onBlockchainSync(int height, int target) {
void MainWindow::onRefreshSync(int height, int target) {
QString heightText = QString("Wallet refresh: %1/%2").arg(height).arg(target);
m_statusLabelStatus->setText(heightText);
this->updateNetStats();
}
void MainWindow::onConnectionStatusChanged(int status)
{
qDebug() << "Wallet connection status changed " << status;
qDebug() << "Wallet connection status changed " << Utils::QtEnumToString(static_cast<Wallet::ConnectionStatus>(status));
// Update connection info in status bar.
@ -652,7 +762,7 @@ void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QStrin
auto tx_err = tx->errorString();
qCritical() << tx_err;
if(m_ctx->currentWallet->connected() == Wallet::ConnectionStatus_WrongVersion)
if (m_ctx->currentWallet->connectionStatus() == Wallet::ConnectionStatus_WrongVersion)
err = QString("%1 Wrong daemon version: %2").arg(err).arg(tx_err);
else
err = QString("%1 %2").arg(err).arg(tx_err);
@ -668,15 +778,26 @@ void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QStrin
} else {
const auto &description = m_ctx->tmpTxDescription;
auto *dialog = new TxConfDialog(tx, address, description, mixin, this);
auto *dialog = new TxConfDialog(m_ctx, tx, address, description, mixin, this);
switch (dialog->exec()) {
case QDialog::Rejected:
m_ctx->onCancelTransaction(tx, address);
{
if (!dialog->showAdvanced)
m_ctx->onCancelTransaction(tx, address);
break;
}
case QDialog::Accepted:
m_ctx->currentWallet->commitTransactionAsync(tx);
break;
}
if (dialog->showAdvanced) {
auto *dialog_adv = new TxConfAdvDialog(m_ctx, description, this);
dialog_adv->setTransaction(tx);
dialog_adv->exec();
dialog_adv->deleteLater();
}
dialog->deleteLater();
}
}
@ -721,6 +842,10 @@ void MainWindow::create_status_bar() {
m_statusLabelStatus->setTextInteractionFlags(Qt::TextSelectableByMouse);
this->statusBar()->addWidget(m_statusLabelStatus);
m_statusLabelNetStats = new QLabel("", this);
m_statusLabelNetStats->setTextInteractionFlags(Qt::TextSelectableByMouse);
this->statusBar()->addWidget(m_statusLabelNetStats);
m_statusLabelBalance = new QLabel("Balance: 0.00 XMR", this);
m_statusLabelBalance->setTextInteractionFlags(Qt::TextSelectableByMouse);
this->statusBar()->addPermanentWidget(m_statusLabelBalance);
@ -753,18 +878,13 @@ void MainWindow::showWalletInfoDialog() {
}
void MainWindow::showSeedDialog() {
QString seed = m_ctx->currentWallet->getCacheAttribute("feather.seed");
if (seed.isEmpty()) {
seed = m_ctx->currentWallet->getSeed();
}
auto *dialog = new SeedDialog(seed, this);
auto *dialog = new SeedDialog(m_ctx->currentWallet, this);
dialog->exec();
dialog->deleteLater();
}
void MainWindow::showConnectionStatusDialog() {
auto status = m_ctx->currentWallet->connected(true);
auto status = m_ctx->currentWallet->connectionStatus();
bool synchronized = m_ctx->currentWallet->synchronized();
QString statusMsg;
@ -791,32 +911,22 @@ void MainWindow::showConnectionStatusDialog() {
statusMsg = "Unknown connection status (this should never happen).";
}
statusMsg += QString("\n\nTx: %1, Rx: %2").arg(Utils::formatBytes(m_ctx->currentWallet->getBytesSent()),
Utils::formatBytes(m_ctx->currentWallet->getBytesReceived()));
QMessageBox::information(this, "Connection Status", statusMsg);
}
void MainWindow::showPasswordDialog() {
auto *pdialog = new PasswordChangeDialog(this);
int ret = pdialog->exec();
if (!ret) return;
QApplication::setActiveWindow(this);
QString currentPassword = pdialog->getCurrentPassword();
QString newPassword = pdialog->getNewPassword();
if (currentPassword != m_ctx->walletPassword) {
QMessageBox::warning(this, "Error", "Incorrect password");
return;
}
if (m_ctx->currentWallet->setPassword(newPassword)) {
QMessageBox::information(this, "Information", "Password changed successfully");
}
else {
QMessageBox::warning(this, "Error", QString("Error: %1").arg(m_ctx->currentWallet->errorString()));
}
auto *pdialog = new PasswordChangeDialog(this, m_ctx->currentWallet);
pdialog->exec();
pdialog->deleteLater();
this->updatePasswordIcon();
}
void MainWindow::updatePasswordIcon() {
QIcon icon = m_ctx->currentWallet->getPassword().isEmpty() ? QIcon(":/assets/images/unlock.svg") : QIcon(":/assets/images/lock.svg");
m_statusBtnPassword->setIcon(icon);
}
void MainWindow::showRestoreHeightDialog() {
@ -850,6 +960,12 @@ void MainWindow::showKeysDialog() {
dialog->deleteLater();
}
void MainWindow::showViewOnlyDialog() {
auto *dialog = new ViewOnlyDialog(m_ctx, this);
dialog->exec();
dialog->deleteLater();
}
void MainWindow::menuTorClicked() {
auto *dialog = new TorInfoDialog(m_ctx, this);
@ -864,6 +980,8 @@ void MainWindow::menuNewRestoreClicked() {
}
void MainWindow::menuQuitClicked() {
cleanupBeforeClose();
QCoreApplication::quit();
}
@ -900,7 +1018,9 @@ void MainWindow::menuAboutClicked() {
}
void MainWindow::menuSettingsClicked() {
m_windowSettings->raise();
m_windowSettings->show();
m_windowSettings->activateWindow();
}
void MainWindow::menuSignVerifyClicked() {
@ -937,11 +1057,7 @@ void MainWindow::homeWidgetChanged(const QString &widgetName) {
}
void MainWindow::closeEvent(QCloseEvent *event) {
m_ctx->tor->stop();
this->saveGeo();
if(m_wizard != nullptr)
m_wizard->close();
cleanupBeforeClose();
QWidget::closeEvent(event);
}
@ -952,17 +1068,17 @@ void MainWindow::donateButtonClicked() {
donation = 1.337;
ui->sendWidget->fill(m_ctx->featherDonationAddress, "Donation to the Feather development team", donation);
ui->tabWidget->setCurrentIndex(2);
ui->tabWidget->setCurrentIndex(Tabs::SEND);
}
void MainWindow::showHistoryTab() {
this->raise();
ui->tabWidget->setCurrentIndex(1);
ui->tabWidget->setCurrentIndex(Tabs::HISTORY);
}
void MainWindow::showSendTab() {
this->raise();
ui->tabWidget->setCurrentIndex(2);
ui->tabWidget->setCurrentIndex(Tabs::SEND);
}
void MainWindow::showCalcWindow() {
@ -971,7 +1087,7 @@ void MainWindow::showCalcWindow() {
void MainWindow::showSendScreen(const CCSEntry &entry) {
ui->sendWidget->fill(entry);
ui->tabWidget->setCurrentIndex(2);
ui->tabWidget->setCurrentIndex(Tabs::SEND);
}
void MainWindow::onViewOnBlockExplorer(const QString &txid) {
@ -989,6 +1105,29 @@ void MainWindow::onAddContact(const QString &address, const QString &name) {
}
}
void MainWindow::importContacts() {
const QString targetFile = QFileDialog::getOpenFileName(this, "Import CSV file", QDir::homePath(), "CSV Files (*.csv)");
if(targetFile.isEmpty()) return;
auto *model = m_ctx->currentWallet->addressBookModel();
QMapIterator<QString, QString> i(model->readCSV(targetFile));
int inserts = 0;
while (i.hasNext()) {
i.next();
bool addressValid = WalletManager::addressValid(i.value(), m_ctx->currentWallet->nettype());
if(addressValid) {
m_ctx->currentWallet->addressBook()->addRow(i.value(), "", i.key());
inserts++;
}
}
if(inserts > 0) {
m_ctx->storeWallet();
}
QMessageBox::information(this, "Contacts imported", QString("Total contacts imported: %1").arg(inserts));
}
MainWindow *MainWindow::getInstance() {
return pMainWindow;
}
@ -1060,6 +1199,150 @@ void MainWindow::showWSNodeExhaustedMessage() {
QMessageBox::warning(this, "Could not connect to a node", msg);
}
void MainWindow::exportKeyImages() {
QString fn = QFileDialog::getSaveFileName(this, "Save key images to file", QDir::homePath(), "Key Images (*_keyImages)");
if (fn.isEmpty()) return;
if (!fn.endsWith("_keyImages")) fn += "_keyImages";
m_ctx->currentWallet->exportKeyImages(fn, true);
auto err = m_ctx->currentWallet->errorString();
if (!err.isEmpty()) {
QMessageBox::warning(this, "Key image export", QString("Failed to export key images.\nReason: %1").arg(err));
} else {
QMessageBox::information(this, "Key image export", "Successfully exported key images.");
}
}
void MainWindow::importKeyImages() {
QString fn = QFileDialog::getOpenFileName(this, "Import key image file", QDir::homePath(), "Key Images (*_keyImages)");
if (fn.isEmpty()) return;
m_ctx->currentWallet->importKeyImages(fn);
auto err = m_ctx->currentWallet->errorString();
if (!err.isEmpty()) {
QMessageBox::warning(this, "Key image import", QString("Failed to import key images.\n\n%1").arg(err));
} else {
QMessageBox::information(this, "Key image import", "Successfully imported key images");
m_ctx->refreshModels();
}
}
void MainWindow::exportOutputs() {
QString fn = QFileDialog::getSaveFileName(this, "Save outputs to file", QDir::homePath(), "Outputs (*_outputs)");
if (fn.isEmpty()) return;
if (!fn.endsWith("_outputs")) fn += "_outputs";
m_ctx->currentWallet->exportOutputs(fn, true);
auto err = m_ctx->currentWallet->errorString();
if (!err.isEmpty()) {
QMessageBox::warning(this, "Outputs export", QString("Failed to export outputs.\nReason: %1").arg(err));
} else {
QMessageBox::information(this, "Outputs export", "Successfully exported outputs.");
}
}
void MainWindow::importOutputs() {
QString fn = QFileDialog::getOpenFileName(this, "Import outputs file", QDir::homePath(), "Outputs (*_outputs)");
if (fn.isEmpty()) return;
m_ctx->currentWallet->importOutputs(fn);
auto err = m_ctx->currentWallet->errorString();
if (!err.isEmpty()) {
QMessageBox::warning(this, "Outputs import", QString("Failed to import outputs.\n\n%1").arg(err));
} else {
QMessageBox::information(this, "Outputs import", "Successfully imported outputs");
m_ctx->refreshModels();
}
}
void MainWindow::cleanupBeforeClose() {
m_ctx->walletManager->closeWallet();
m_ctx->tor->stop();
this->saveGeo();
}
void MainWindow::loadUnsignedTx() {
QString fn = QFileDialog::getOpenFileName(this, "Select transaction to load", QDir::homePath(), "Transaction (*unsigned_monero_tx)");
if (fn.isEmpty()) return;
UnsignedTransaction *tx = m_ctx->currentWallet->loadTxFile(fn);
auto err = m_ctx->currentWallet->errorString();
if (!err.isEmpty()) {
QMessageBox::warning(this, "Load transaction from file", QString("Failed to load transaction.\n\n%1").arg(err));
return;
}
this->createUnsignedTxDialog(tx);
}
void MainWindow::loadUnsignedTxFromClipboard() {
QString unsigned_tx = Utils::copyFromClipboard();
if (unsigned_tx.isEmpty()) {
QMessageBox::warning(this, "Load unsigned transaction from clipboard", "Clipboard is empty");
return;
}
UnsignedTransaction *tx = m_ctx->currentWallet->loadTxFromBase64Str(unsigned_tx);
auto err = m_ctx->currentWallet->errorString();
if (!err.isEmpty()) {
QMessageBox::warning(this, "Load unsigned transaction from clipboard", QString("Failed to load transaction.\n\n%1").arg(err));
return;
}
this->createUnsignedTxDialog(tx);
}
void MainWindow::loadSignedTx() {
QString fn = QFileDialog::getOpenFileName(this, "Select transaction to load", QDir::homePath(), "Transaction (*signed_monero_tx)");
if (fn.isEmpty()) return;
PendingTransaction *tx = m_ctx->currentWallet->loadSignedTxFile(fn);
auto err = m_ctx->currentWallet->errorString();
if (!err.isEmpty()) {
QMessageBox::warning(this, "Load signed transaction from file", err);
return;
}
auto *dialog = new TxConfAdvDialog(m_ctx, "", this);
dialog->setTransaction(tx);
dialog->exec();
dialog->deleteLater();
}
void MainWindow::loadSignedTxFromText() {
auto dialog = new BroadcastTxDialog(this, m_ctx);
dialog->exec();
dialog->deleteLater();
}
void MainWindow::createUnsignedTxDialog(UnsignedTransaction *tx) {
auto *dialog = new TxConfAdvDialog(m_ctx, "", this);
dialog->setUnsignedTransaction(tx);
dialog->exec();
dialog->deleteLater();
}
void MainWindow::importTransaction() {
auto result = QMessageBox::warning(this, "Warning", "Using this feature may allow a remote node to associate the transaction with your IP address.\n"
"\n"
"Connect to a trusted node or run Feather over Tor if network level metadata leakage is included in your threat model.",
QMessageBox::Ok | QMessageBox::Cancel);
if (result == QMessageBox::Ok) {
auto *dialog = new TxImportDialog(this, m_ctx);
dialog->exec();
dialog->deleteLater();
}
}
void MainWindow::updateNetStats() {
if (!m_ctx->currentWallet) {
m_statusLabelNetStats->setText("");
return;
}
if (m_ctx->currentWallet->connectionStatus() == Wallet::ConnectionStatus_Connected && m_ctx->currentWallet->synchronized()) {
m_statusLabelNetStats->setText("");
return;
}
m_statusLabelNetStats->setText(QString("(D: %1)").arg(Utils::formatBytes(m_ctx->currentWallet->getBytesReceived())));
}
MainWindow::~MainWindow() {
delete ui;
}

@ -4,6 +4,10 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#ifdef Q_OS_MAC
#include "src/kdmactouchbar.h"
#endif
#include <QMainWindow>
#include <QSystemTrayIcon>
#include <QScreen>
@ -19,6 +23,7 @@
#include "widgets/ccswidget.h"
#include "widgets/redditwidget.h"
#include "widgets/tickerwidget.h"
#include "widgets/xmrigwidget.h"
#include "utils/networking.h"
#include "appcontext.h"
#include "utils/config.h"
@ -64,6 +69,18 @@ public:
qreal screenDpiPhysical;
qreal screenRatio;
enum Tabs {
HOME = 0,
HISTORY,
SEND,
RECEIVE,
COINS,
CALC,
MORPHTOKEN,
XMR_TO,
XMRIG
};
public slots:
void initWidgets();
void initMenu();
@ -79,6 +96,7 @@ public slots:
void showConnectionStatusDialog();
void showPasswordDialog();
void showKeysDialog();
void showViewOnlyDialog();
void donateButtonClicked();
void showCalcWindow();
void showSendTab();
@ -94,10 +112,22 @@ public slots:
void onWalletCreated(Wallet *wallet);
void menuWalletCloseClicked();
void menuWalletOpenClicked();
void onWalletOpenPasswordRequired(bool invalidPassword);
void onWalletOpenPasswordRequired(bool invalidPassword, const QString &path);
void onViewOnBlockExplorer(const QString &txid);
void onAddContact(const QString &address, const QString &name);
void importContacts();
void showRestoreHeightDialog();
void importTransaction();
// offline tx signing
void exportKeyImages();
void importKeyImages();
void exportOutputs();
void importOutputs();
void loadUnsignedTx();
void loadUnsignedTxFromClipboard();
void loadSignedTx();
void loadSignedTxFromText();
// libwalletqt
void onBalanceUpdated(double balance, double unlocked, const QString &balance_str, const QString &unlocked_str);
@ -119,6 +149,7 @@ private:
static MainWindow * pMainWindow;
void closeEvent(QCloseEvent *event) override;
void cleanupBeforeClose();
void create_status_bar();
void initMain();
void loadSkins();
@ -128,6 +159,11 @@ private:
void showDebugInfo();
void showNodeExhaustedMessage();
void showWSNodeExhaustedMessage();
void createUnsignedTxDialog(UnsignedTransaction *tx);
void touchbarShowWizard();
void touchbarShowWallet();
void updatePasswordIcon();
void updateNetStats();
WalletWizard *createWizard(WalletWizard::Page startPage);
@ -137,6 +173,7 @@ private:
SignVerifyDialog *m_windowSignVerify = nullptr;
RestoreDialog *m_restoreDialog = nullptr;
AboutDialog *m_aboutDialog = nullptr;
XMRigWidget *m_xmrig = nullptr;
bool m_windowSpawned = false;
@ -156,6 +193,7 @@ private:
// lower status bar
QLabel *m_statusLabelBalance;
QLabel *m_statusLabelStatus;
QLabel *m_statusLabelNetStats;
StatusBarButton *m_statusBtnConnectionStatusIndicator;
StatusBarButton *m_statusBtnPassword;
StatusBarButton *m_statusBtnPreferences;
@ -165,13 +203,20 @@ private:
SubaddressProxyModel *subaddressProxyModel;
TransactionHistoryModel *txHistModel;
CoinsModel *coinsModel;
#ifdef Q_OS_MAC
QAction *m_touchbarActionWelcome;
KDMacTouchBar *m_touchbar;
QList<QAction *> m_touchbarWalletItems;
QList<QAction *> m_touchbarWizardItems;
#endif
QSignalMapper *m_tabShowHideSignalMapper;
QMap<QString, ToggleTab*> m_tabShowHideMapper;
WalletWizard *m_wizard = nullptr;
QMap<QString, QString> m_skins;
QTimer m_updateBytes;
private slots:
void menuToggleTabVisible(const QString &key);
};

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>894</width>
<width>1156</width>
<height>496</height>
</rect>
</property>
@ -224,13 +224,41 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tabCalc">
<attribute name="icon">
<iconset resource="assets.qrc">
<normaloff>:/assets/images/coldcard.png</normaloff>:/assets/images/coldcard.png</iconset>
</attribute>
<attribute name="title">
<string>Calc</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<widget class="CalcWidget" name="conversionWidget" native="true"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabMorphToken">
<attribute name="icon">
<iconset resource="assets.qrc">
<normaloff>:/assets/images/morphtoken.png</normaloff>:/assets/images/morphtoken.png</iconset>
</attribute>
<attribute name="title">
<string>MorphToken</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="MorphTokenWidget" name="morphtokenWidget" native="true"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabXmrTo">
<attribute name="icon">
<iconset resource="assets.qrc">
<normaloff>:/assets/images/xmrto.png</normaloff>:/assets/images/xmrto.png</iconset>
</attribute>
<attribute name="title">
<string>xmr.to</string>
<string>XMR.to</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="leftMargin">
@ -266,17 +294,17 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tabCalc">
<widget class="QWidget" name="tabXmrRig">
<attribute name="icon">
<iconset resource="assets.qrc">
<normaloff>:/assets/images/coldcard.png</normaloff>:/assets/images/coldcard.png</iconset>
<normaloff>:/assets/images/xmrig.ico</normaloff>:/assets/images/xmrig.ico</iconset>
</attribute>
<attribute name="title">
<string>Calc</string>
<string>XMRig</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_4">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="CalcWidget" name="conversionWidget" native="true"/>
<layout class="QGridLayout" name="xmrRigLayout"/>
</item>
</layout>
</widget>
@ -290,7 +318,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>894</width>
<width>1156</width>
<height>30</height>
</rect>
</property>
@ -319,15 +347,33 @@
<string>Contacts</string>
</property>
<addaction name="actionExportContactsCSV"/>
<addaction name="actionImportContactsCSV"/>
</widget>
<widget class="QMenu" name="menuAdvanced">
<property name="title">
<string>Advanced</string>
</property>
<widget class="QMenu" name="menuExport">
<property name="title">
<string>Export</string>
</property>
<addaction name="actionExportOutputs"/>
<addaction name="actionExportKeyImages"/>
</widget>
<widget class="QMenu" name="menuImport">
<property name="title">
<string>Import</string>
</property>
<addaction name="actionImportOutputs"/>
<addaction name="actionImportKeyImages"/>
</widget>
<addaction name="actionStore_wallet"/>
<addaction name="actionUpdate_balance"/>
<addaction name="actionRefresh_tabs"/>
<addaction name="separator"/>
<addaction name="actionChange_restore_height"/>
<addaction name="menuExport"/>
<addaction name="menuImport"/>
</widget>
<addaction name="actionInformation"/>
<addaction name="menuAdvanced"/>
@ -335,6 +381,7 @@
<addaction name="actionPassword"/>
<addaction name="actionSeed"/>
<addaction name="actionKeys"/>
<addaction name="actionViewOnly"/>
<addaction name="separator"/>
<addaction name="menuHistory"/>
<addaction name="menuContacts"/>
@ -343,9 +390,27 @@
<property name="title">
<string>Tools</string>
</property>
<widget class="QMenu" name="menuLoad_transaction">
<property name="title">
<string>Load unsigned transaction</string>
</property>
<addaction name="actionLoadUnsignedTxFromFile"/>
<addaction name="actionLoadUnsignedTxFromClipboard"/>
</widget>
<widget class="QMenu" name="menuLoad_signed_transaction">
<property name="title">
<string>Broadcast transaction</string>
</property>
<addaction name="actionLoadSignedTxFromFile"/>
<addaction name="actionLoadSignedTxFromText"/>
</widget>
<addaction name="actionSignVerify"/>
<addaction name="actionVerifyTxProof"/>
<addaction name="separator"/>
<addaction name="menuLoad_transaction"/>
<addaction name="menuLoad_signed_transaction"/>
<addaction name="actionImport_transaction"/>
<addaction name="separator"/>
<addaction name="actionCalculator"/>
<addaction name="actionCreateDesktopEntry"/>
</widget>
@ -354,7 +419,6 @@
<string>Help</string>
</property>
<addaction name="actionAbout"/>
<addaction name="actionAbout_Qt"/>
<addaction name="actionOfficialWebsite"/>
<addaction name="actionDonate_to_Feather"/>
<addaction name="separator"/>
@ -366,8 +430,10 @@
<string>View</string>
</property>
<addaction name="actionShow_Coins"/>
<addaction name="actionShow_xmr_to"/>
<addaction name="actionShow_calc"/>
<addaction name="actionShow_MorphToken"/>
<addaction name="actionShow_xmr_to"/>
<addaction name="actionShow_XMRig"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuWallet"/>
@ -455,7 +521,7 @@
</action>
<action name="actionShow_xmr_to">
<property name="text">
<string>Show Xmr.To</string>
<string>Show XMR.to</string>
</property>
</action>
<action name="actionShow_calc">
@ -488,6 +554,11 @@
<string>Refresh models</string>
</property>
</action>
<action name="actionImportContactsCSV">
<property name="text">
<string>Import CSV</string>
</property>
</action>
<action name="actionExportContactsCSV">
<property name="text">
<string>Export CSV</string>
@ -503,11 +574,6 @@
<string>Keys</string>
</property>
</action>
<action name="actionAbout_Qt">
<property name="text">
<string>About Qt</string>
</property>
</action>
<action name="actionCreateDesktopEntry">
<property name="text">
<string>Create desktop entry</string>
@ -518,6 +584,106 @@
<string>Debug info</string>
</property>
</action>
<action name="actionCreate_view_only_details">
<property name="text">
<string>Details</string>
</property>
</action>
<action name="actionCreate_view_only_wallet_file">
<property name="text">
<string>Export wallet file</string>
</property>
</action>
<action name="actionViewOnly">
<property name="text">
<string>View-Only</string>
</property>
</action>
<action name="actionExport_key_images">
<property name="text">
<string>Export key images</string>
</property>
</action>
<action name="actionImport_key_images">
<property name="text">
<string>Import key images</string>
</property>
</action>
<action name="actionExport_outputs">
<property name="text">
<string>Export outputs</string>
</property>
</action>
<action name="actionImport_outputs">
<property name="text">
<string>Import outputs</string>
</property>
</action>
<action name="actionExportKeyImages">
<property name="text">
<string>Key Images</string>
</property>
</action>
<action name="actionExportOutputs">
<property name="text">
<string>Outputs</string>
</property>
</action>
<action name="actionImportKeyImages">
<property name="text">
<string>Key images</string>
</property>
</action>
<action name="actionImportOutputs">
<property name="text">
<string>Outputs</string>
</property>
</action>
<action name="actionShow_XMRig">
<property name="text">
<string>Show XMRig</string>
</property>
</action>
<action name="actionImportTransaction">
<property name="text">
<string>Transaction</string>
</property>
</action>
<action name="actionSubmitTransaction">
<property name="text">
<string>Submit transaction file</string>
</property>
</action>
<action name="actionLoadUnsignedTxFromFile">
<property name="text">
<string>From file</string>
</property>
</action>
<action name="actionLoadSignedTxFromFile">
<property name="text">
<string>From file</string>
</property>
</action>
<action name="actionLoadUnsignedTxFromClipboard">
<property name="text">
<string>From clipboard</string>
</property>
</action>
<action name="actionLoadSignedTxFromText">
<property name="text">
<string>From text</string>
</property>
</action>
<action name="actionImport_transaction">
<property name="text">
<string>Import transaction</string>
</property>
</action>
<action name="actionShow_MorphToken">
<property name="text">
<string>Show MorphToken</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
@ -562,6 +728,12 @@
<header>calcwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>MorphTokenWidget</class>
<extends>QWidget</extends>
<header>MorphTokenWidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="assets.qrc"/>

@ -174,3 +174,26 @@ bool AddressBookModel::writeCSV(const QString &path) {
csv = QString("address,description\n%1").arg(csv);
return Utils::fileWrite(path, csv);
}
QMap<QString, QString> AddressBookModel::readCSV(const QString &path) {
if(!Utils::fileExists(path)) {
return QMap<QString, QString>();
}
QString csv = Utils::barrayToString(Utils::fileOpen(path));
QTextStream stream(&csv);
QMap<QString, QString> map;
while(!stream.atEnd()) {
QStringList line = stream.readLine().split(",");
if(line.length() != 2) {
continue;
}
QString address = line.at(0);
QString description = line.at(1);
description = description.replace("\"", "");
if(!description.isEmpty() && !address.isEmpty()) {
map[description] = address;
}
}
return map;
}

@ -36,6 +36,7 @@ public:
bool isShowFullAddresses() const;
void setShowFullAddresses(bool show);
bool writeCSV(const QString &path);
QMap<QString, QString> readCSV(const QString &path);
public slots:
void startReset();

@ -6,6 +6,7 @@
#include "Coins.h"
#include <wallet/api/wallet2_api.h>
#include "ModelUtils.h"
#include "globals.h"
#include <QDebug>
#include <QHash>
@ -19,6 +20,9 @@ CoinsModel::CoinsModel(QObject *parent, Coins *coins)
{
connect(m_coins, &Coins::refreshStarted, this, &CoinsModel::startReset);
connect(m_coins, &Coins::refreshFinished, this, &CoinsModel::endReset);
m_eye = QIcon(":/assets/images/eye1.png");
m_eyeBlind = QIcon(":/assets/images/eye_blind.png");
}
void CoinsModel::startReset(){
@ -73,7 +77,7 @@ QVariant CoinsModel::data(const QModelIndex &index, int role) const
result = QBrush(QColor(173, 216, 230));
}
else if (!cInfo.unlocked()) {
result = QBrush(QColor("#BFFF00"));
result = QBrush(QColor("#60993E"));
}
}
else if (role == Qt::TextAlignmentRole) {
@ -82,6 +86,19 @@ QVariant CoinsModel::data(const QModelIndex &index, int role) const
result = Qt::AlignRight;
}
}
else if (role == Qt::DecorationRole) {
switch (index.column()) {
case KeyImageKnown:
{
if (cInfo.keyImageKnown()) {
result = QVariant(m_eye);
}
else {
result = QVariant(m_eyeBlind);
}
}
}
}
else if (role == Qt::FontRole) {
switch(index.column()) {
case PubKey:
@ -91,6 +108,16 @@ QVariant CoinsModel::data(const QModelIndex &index, int role) const
}
}
else if (role == Qt::ToolTipRole) {
switch(index.column()) {
case KeyImageKnown:
{
if (cInfo.keyImageKnown()) {
result = "Key image known";
} else {
result = "Key image unknown. Outgoing transactions that include this output will not be detected.";
}
}
}
if (cInfo.frozen()) {
result = "Output is frozen.";
}
@ -142,6 +169,8 @@ QVariant CoinsModel::parseTransactionInfo(const CoinsInfo &cInfo, int column, in
{
switch (column)
{
case KeyImageKnown:
return "";
case PubKey:
return cInfo.pubKey().mid(0,8);
case OutputPoint:
@ -162,10 +191,14 @@ QVariant CoinsModel::parseTransactionInfo(const CoinsInfo &cInfo, int column, in
case SpentHeight:
return cInfo.spentHeight();
case Amount:
return QString::number(cInfo.amount() / 1e12, 'f', 12);
{
if (role == Qt::UserRole) {
return cInfo.amount() / globals::cdiv;
}
return QString::number(cInfo.amount() / globals::cdiv, 'f', 12);
}
case Frozen:
return cInfo.frozen();
default:
{
qCritical() << "Unimplemented role";

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

Loading…
Cancel
Save