diff --git a/.gitignore b/.gitignore index 1c231fa..00967d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ .stack-work/ *~ dist-newstyle/ +zenith.db +zenith.log +zenith.db-shm +zenith.db-wal diff --git a/CHANGELOG.md b/CHANGELOG.md index a865617..a4a73d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.5.3.1-beta] + +### Added + +- Docker image + +## [0.5.3.0-beta] + +### Added + +- Address Book functionality. Allows users to store frequently used zcash addresses and + generate transactions using them. + +### Changed + +- Improved formatting of sync progress + +### Fixed + +- Wallet sync when no new block has been detected on-chain. + ## [0.5.2.0-beta] ### Changed diff --git a/README.md b/README.md index efabca0..bce5523 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Zenith is a wallet for the [Zebra](https://zfnd.org/zebra/) Zcash node . It has - Listing transactions for specific addresses, decoding memos for easy reading. - Copying addresses to the clipboard. - Sending transactions with shielded memo support. +- Address Book for storing frequently used zcash addresses ## Installation diff --git a/app/Main.hs b/app/Main.hs index 5911cfc..269ec1b 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -220,7 +220,7 @@ main = do " ______ _ _ _ \n |___ / (_) | | | \n / / ___ _ __ _| |_| |__ \n / / / _ \\ '_ \\| | __| '_ \\ \n / /_| __/ | | | | |_| | | |\n /_____\\___|_| |_|_|\\__|_| |_|\n Zcash Full Node CLI v0.4.0" } (root nodeUser nodePwd) - "cli" -> runZenithCLI myConfig + "tui" -> runZenithTUI myConfig "rescan" -> clearSync myConfig _ -> printUsage else printUsage @@ -230,5 +230,5 @@ printUsage = do putStrLn "zenith [command] [parameters]\n" putStrLn "Available commands:" putStrLn "legacy\tLegacy CLI for zcashd" - putStrLn "cli\tCLI for zebrad" + putStrLn "tui\tTUI for zebrad" putStrLn "rescan\tRescan the existing wallet(s)" diff --git a/cabal.project.freeze b/cabal.project.freeze new file mode 100644 index 0000000..3b9c8d2 --- /dev/null +++ b/cabal.project.freeze @@ -0,0 +1,285 @@ +active-repositories: hackage.haskell.org:merge +constraints: any.Cabal ==3.8.1.0, + any.Cabal-syntax ==3.8.1.0, + any.Clipboard ==2.3.2.0, + any.HUnit ==1.6.2.0, + any.Hclip ==3.0.0.4, + any.OneTuple ==0.4.1.1, + any.QuickCheck ==2.14.3, + QuickCheck -old-random +templatehaskell, + any.StateVar ==1.2.2, + any.X11 ==1.10.3, + X11 -pedantic, + any.aeson ==2.2.1.0, + aeson +ordered-keymap, + any.alex ==3.5.1.0, + any.ansi-terminal ==1.1, + ansi-terminal -example, + any.ansi-terminal-types ==1.1, + any.appar ==0.1.8, + any.array ==0.5.4.0, + any.ascii-progress ==0.3.3.0, + ascii-progress -examples, + any.asn1-encoding ==0.9.6, + any.asn1-parse ==0.9.5, + any.asn1-types ==0.3.4, + any.assoc ==1.1, + assoc +tagged, + any.async ==2.2.5, + async -bench, + any.attoparsec ==0.14.4, + attoparsec -developer, + any.attoparsec-aeson ==2.2.0.1, + any.auto-update ==0.1.6, + any.base ==4.17.2.1, + any.base-orphans ==0.9.1, + any.base16 ==1.0, + any.base16-bytestring ==1.0.2.0, + any.base58-bytestring ==0.1.0, + any.base64-bytestring ==1.2.1.0, + any.basement ==0.0.16, + any.bifunctors ==5.6.2, + bifunctors +tagged, + any.bimap ==0.5.0, + any.binary ==0.8.9.1, + any.binary-orphans ==1.0.4.1, + any.bitvec ==1.1.5.0, + bitvec +simd, + any.blaze-builder ==0.4.2.3, + any.blaze-html ==0.9.2.0, + any.blaze-markup ==0.8.3.0, + any.borsh ==0.3.0, + any.brick ==2.3.1, + brick -demos, + any.byteorder ==1.0.4, + any.bytes ==0.17.3, + any.bytestring ==0.11.5.3, + any.c2hs ==0.28.8, + c2hs +base3 -regression, + any.call-stack ==0.4.0, + any.case-insensitive ==1.2.1.0, + any.cborg ==0.2.10.0, + cborg +optimize-gmp, + any.cereal ==0.5.8.3, + cereal -bytestring-builder, + any.colour ==2.3.6, + any.comonad ==5.0.8, + comonad +containers +distributive +indexed-traversable, + any.concurrent-output ==1.10.20, + any.conduit ==1.3.5, + any.conduit-extra ==1.3.6, + any.config-ini ==0.2.7.0, + config-ini -enable-doctests, + any.configurator ==0.3.0.0, + configurator -developer, + any.containers ==0.6.7, + any.contravariant ==1.5.5, + contravariant +semigroups +statevar +tagged, + any.cookie ==0.4.6, + any.crypto-api ==0.13.3, + crypto-api -all_cpolys, + any.crypton ==0.34, + crypton -check_alignment +integer-gmp -old_toolchain_inliner +support_aesni +support_deepseq +support_pclmuldq +support_rdrand -support_sse +use_target_attributes, + any.crypton-connection ==0.3.2, + any.crypton-x509 ==1.7.6, + any.crypton-x509-store ==1.6.9, + any.crypton-x509-system ==1.6.7, + any.crypton-x509-validation ==1.6.12, + any.cryptonite ==0.30, + cryptonite -check_alignment +integer-gmp -old_toolchain_inliner +support_aesni +support_deepseq -support_pclmuldq +support_rdrand -support_sse +use_target_attributes, + any.data-clist ==0.2, + any.data-default ==0.7.1.1, + any.data-default-class ==0.1.2.0, + any.data-default-instances-containers ==0.0.1, + any.data-default-instances-dlist ==0.0.1, + any.data-default-instances-old-locale ==0.0.1, + any.data-fix ==0.3.2, + any.deepseq ==1.4.8.0, + any.directory ==1.3.7.1, + any.distributive ==0.6.2.1, + distributive +semigroups +tagged, + any.dlist ==1.0, + dlist -werror, + any.easy-file ==0.2.5, + any.entropy ==0.4.1.10, + entropy -donotgetentropy, + any.envy ==2.1.3.0, + any.esqueleto ==3.5.11.2, + any.exceptions ==0.10.5, + any.fast-logger ==3.2.2, + any.filepath ==1.4.2.2, + any.foldable1-classes-compat ==0.1, + foldable1-classes-compat +tagged, + any.foreign-rust ==0.1.0, + any.generically ==0.1.1, + any.generics-sop ==0.5.1.4, + any.ghc ==9.4.8, + any.ghc-bignum ==1.3, + any.ghc-boot ==9.4.8, + any.ghc-boot-th ==9.4.8, + any.ghc-heap ==9.4.8, + any.ghc-prim ==0.9.1, + any.ghci ==9.4.8, + any.half ==0.3.1, + any.happy ==1.20.1.1, + any.hashable ==1.4.4.0, + hashable +integer-gmp -random-initial-seed, + any.haskeline ==0.8.2, + any.haskell-lexer ==1.1.1, + any.haskoin-core ==1.1.0, + any.hexstring ==0.12.1.0, + any.hourglass ==0.2.12, + any.hpc ==0.6.1.0, + any.hsc2hs ==0.68.10, + hsc2hs -in-ghc-tree, + any.hspec ==2.11.7, + any.hspec-core ==2.11.7, + any.hspec-discover ==2.11.7, + any.hspec-expectations ==0.8.4, + any.http-api-data ==0.6, + http-api-data -use-text-show, + any.http-client ==0.7.17, + http-client +network-uri, + any.http-client-tls ==0.3.6.3, + any.http-conduit ==2.3.8.3, + http-conduit +aeson, + any.http-types ==0.12.4, + any.indexed-traversable ==0.1.3, + any.indexed-traversable-instances ==0.1.1.2, + any.integer-conversion ==0.1.0.1, + any.integer-gmp ==1.1, + any.integer-logarithms ==1.0.3.1, + integer-logarithms -check-bounds +integer-gmp, + any.iproute ==1.7.12, + any.language-c ==0.9.3, + language-c -allwarnings +iecfpextension +usebytestrings, + any.lift-type ==0.1.1.1, + any.lifted-base ==0.2.3.12, + any.megaparsec ==9.6.1, + megaparsec -dev, + any.memory ==0.18.0, + memory +support_bytestring +support_deepseq, + any.microlens ==0.4.13.1, + any.microlens-mtl ==0.2.0.3, + any.microlens-th ==0.4.3.14, + any.mime-types ==0.1.2.0, + any.monad-control ==1.0.3.1, + any.monad-logger ==0.3.40, + monad-logger +template_haskell, + any.monad-loops ==0.4.3, + monad-loops +base4, + any.mono-traversable ==1.0.17.0, + any.mtl ==2.2.2, + any.murmur3 ==1.0.5, + any.network ==3.1.4.0, + network -devel, + any.network-uri ==2.6.4.2, + any.old-locale ==1.0.0.7, + any.old-time ==1.1.0.4, + any.os-string ==2.0.2, + any.parsec ==3.1.16.1, + any.parser-combinators ==1.3.0, + parser-combinators -dev, + any.path-pieces ==0.2.1, + any.pem ==0.2.4, + any.persistent ==2.14.6.1, + any.persistent-sqlite ==2.13.3.0, + persistent-sqlite -build-sanity-exe +full-text-search +have-usleep +json1 -systemlib +uri-filenames -use-pkgconfig -use-stat3 +use-stat4, + any.persistent-template ==2.12.0.0, + any.pretty ==1.1.3.6, + any.primitive ==0.9.0.0, + any.process ==1.6.18.0, + any.pureMD5 ==2.1.4, + pureMD5 -test, + any.quickcheck-io ==0.2.0, + any.quickcheck-transformer ==0.3.1.2, + any.random ==1.2.1.2, + any.regex-base ==0.94.0.2, + any.regex-compat ==0.95.2.1, + any.regex-posix ==0.96.0.1, + regex-posix -_regex-posix-clib, + any.resource-pool ==0.4.0.0, + any.resourcet ==1.3.0, + any.rts ==1.0.2, + any.safe ==0.3.21, + any.safe-exceptions ==0.1.7.4, + any.scientific ==0.3.7.0, + scientific -bytestring-builder -integer-simple, + any.secp256k1-haskell ==1.2.0, + any.semialign ==1.3, + semialign +semigroupoids, + any.semigroupoids ==6.0.0.1, + semigroupoids +comonad +containers +contravariant +distributive +tagged +unordered-containers, + any.serialise ==0.2.6.1, + serialise +newtime15, + any.silently ==1.2.5.3, + any.socks ==0.6.1, + any.sop-core ==0.5.0.2, + any.sort ==1.0.0.0, + any.split ==0.2.5, + any.splitmix ==0.1.0.5, + splitmix -optimised-mixer, + any.stm ==2.5.1.0, + any.stm-chans ==3.0.0.9, + any.streaming-commons ==0.2.2.6, + streaming-commons -use-bytestring-builder, + any.strict ==0.5, + any.string-conversions ==0.4.0.1, + any.structured-cli ==2.7.0.1, + structured-cli -debug, + any.tagged ==0.8.8, + tagged +deepseq +transformers, + any.template-haskell ==2.19.0.0, + any.terminal-size ==0.3.4, + any.terminfo ==0.4.1.5, + any.text ==2.0.2, + any.text-iso8601 ==0.1, + any.text-short ==0.1.5, + text-short -asserts, + any.text-zipper ==0.13, + any.tf-random ==0.5, + any.th-abstraction ==0.6.0.0, + any.th-compat ==0.1.5, + any.th-lift ==0.8.4, + any.th-lift-instances ==0.1.20, + any.these ==1.2, + any.time ==1.12.2, + any.time-compat ==1.9.6.1, + time-compat -old-locale, + any.tls ==2.0.2, + tls -devel, + any.transformers ==0.5.6.2, + any.transformers-base ==0.4.6, + transformers-base +orphaninstances, + any.transformers-compat ==0.7.2, + transformers-compat -five +five-three -four +generic-deriving +mtl -three -two, + any.typed-process ==0.2.11.1, + any.unix ==2.7.3, + any.unix-compat ==0.7.1, + unix-compat -old-time, + any.unix-time ==0.4.12, + any.unliftio ==0.2.25.0, + any.unliftio-core ==0.2.1.0, + any.unordered-containers ==0.2.20, + unordered-containers -debug, + any.utf8-string ==1.0.2, + any.uuid-types ==1.0.5.1, + any.vault ==0.3.1.5, + vault +useghc, + any.vector ==0.13.1.0, + vector +boundschecks -internalchecks -unsafechecks -wall, + any.vector-algorithms ==0.9.0.1, + vector-algorithms +bench +boundschecks -internalchecks -llvm +properties -unsafechecks, + any.vector-stream ==0.1.0.1, + any.void ==0.7.3, + void -safe, + any.vty ==6.2, + any.vty-crossplatform ==0.4.0.0, + vty-crossplatform -demos, + any.vty-unix ==0.2.0.0, + any.wide-word ==0.1.6.0, + any.witherable ==0.4.2, + any.word-wrap ==0.5, + any.zlib ==0.7.0.0, + zlib -bundled-c-zlib +non-blocking-ffi +pkg-config +index-state: hackage.haskell.org 2024-04-07T10:14:52Z diff --git a/src/Zenith/CLI.hs b/src/Zenith/CLI.hs index b46d04b..830a1d7 100644 --- a/src/Zenith/CLI.hs +++ b/src/Zenith/CLI.hs @@ -10,8 +10,10 @@ import qualified Brick.BChan as BC import qualified Brick.Focus as F import Brick.Forms ( Form(..) + , FormFieldState , (@@=) , allFieldsValid + , editShowableField , editShowableFieldWithValidate , editTextField , focusedFormInputAttr @@ -40,6 +42,9 @@ import Brick.Widgets.Core , joinBorders , padAll , padBottom + , padLeft + , padTop + , setAvailableSize , str , strWrap , strWrapWith @@ -49,6 +54,7 @@ import Brick.Widgets.Core , updateAttrMap , vBox , vLimit + , viewport , withAttr , withBorderStyle ) @@ -62,7 +68,7 @@ import Control.Monad (forever, void) import Control.Monad.IO.Class (liftIO) import Control.Monad.Logger (LoggingT, runFileLoggingT, runNoLoggingT) import Data.Aeson -import Data.HexString (toText) +import Data.HexString (HexString(..), toText) import Data.Maybe import qualified Data.Text as T import qualified Data.Text.Encoding as E @@ -76,6 +82,7 @@ import Lens.Micro ((&), (.~), (^.), set) import Lens.Micro.Mtl import Lens.Micro.TH import System.Hclip +import Text.Printf import Text.Wrap (FillScope(..), FillStrategy(..), WrapSettings(..), wrapText) import ZcashHaskell.Keys (generateWalletSeedPhrase, getWalletSeed) import ZcashHaskell.Orchard (getSaplingFromUA, isValidUnifiedAddress) @@ -108,6 +115,10 @@ data Name | RecField | AmtField | MemoField + | ABViewport + | ABList + | DescripField + | AddressField deriving (Eq, Show, Ord) data DialogInput = DialogInput @@ -124,6 +135,13 @@ data SendInput = SendInput makeLenses ''SendInput +data AdrBookEntry = AdrBookEntry + { _descrip :: !T.Text + , _address :: !T.Text + } deriving (Show) + +makeLenses ''AdrBookEntry + data DialogType = WName | AName @@ -132,19 +150,26 @@ data DialogType | ASelect | SendTx | Blank + | AdrBook + | AdrBookForm + | AdrBookUpdForm + | AdrBookDelForm data DisplayType = AddrDisplay | MsgDisplay | PhraseDisplay | TxDisplay + | TxIdDisplay | SyncDisplay | SendDisplay + | AdrBookEntryDisplay | BlankDisplay data Tick = TickVal !Float | TickMsg !String + | TickTx !HexString data State = State { _network :: !ZcashNet @@ -169,6 +194,10 @@ data State = State , _eventDispatch :: !(BC.BChan Tick) , _timer :: !Int , _txForm :: !(Form SendInput () Name) + , _abAddresses :: !(L.List Name (Entity AddressBook)) + , _abForm :: !(Form AdrBookEntry () Name) + , _abCurAdrs :: !T.Text -- used for address book CRUD operations + , _sentTx :: !(Maybe HexString) } makeLenses ''State @@ -182,14 +211,15 @@ drawUI s = [splashDialog s, helpDialog s, displayDialog s, inputDialog s, ui s] withBorderStyle unicode $ B.borderWithLabel (str - ("Zenith - " <> + (" Zenith - " <> show (st ^. network) <> " - " <> - T.unpack - (maybe - "(None)" - (\(_, w) -> zcashWalletName $ entityVal w) - (L.listSelectedElement (st ^. wallets))))) + (T.unpack + (maybe + "(None)" + (\(_, w) -> zcashWalletName $ entityVal w) + (L.listSelectedElement (st ^. wallets)))) ++ + " ")) (C.hCenter (str ("Account: " ++ @@ -204,17 +234,20 @@ drawUI s = [splashDialog s, helpDialog s, displayDialog s, inputDialog s, ui s] if st ^. network == MainNet then displayZec (st ^. balance) else displayTaz (st ^. balance))) <=> - listAddressBox "Addresses" (st ^. addresses) <+> + listAddressBox " Addresses " (st ^. addresses) <+> B.vBorder <+> - (C.hCenter (str ("Last block seen: " ++ show (st ^. syncBlock))) <=> - listTxBox "Transactions" (st ^. network) (st ^. transactions))) <=> + (C.hCenter + (str ("Last block seen: " ++ show (st ^. syncBlock) ++ "\n")) <=> + listTxBox " Transactions " (st ^. network) (st ^. transactions))) <=> C.hCenter (hBox [ capCommand "W" "allets" , capCommand "A" "ccounts" , capCommand "V" "iew address" , capCommand "S" "end Tx" + , capCommand2 "Address " "B" "ook" , capCommand "Q" "uit" + , capCommand "?" " Help" , str $ show (st ^. timer) ]) listBox :: Show e => String -> L.List Name e -> Widget Name @@ -253,7 +286,7 @@ drawUI s = [splashDialog s, helpDialog s, displayDialog s, inputDialog s, ui s] (hBox [ capCommand "↑↓ " "move" , capCommand "↲ " "select" - , capCommand "Tab " "->" + , capCommand3 "" "Tab" " ->" ]) ] listTxBox :: @@ -269,19 +302,20 @@ drawUI s = [splashDialog s, helpDialog s, displayDialog s, inputDialog s, ui s] (hBox [ capCommand "↑↓ " "move" , capCommand "T" "x Display" - , capCommand "Tab " "<-" + , capCommand3 "" "Tab" " <-" ]) ] helpDialog :: State -> Widget Name helpDialog st = if st ^. helpBox then D.renderDialog - (D.dialog (Just (str "Commands")) Nothing 55) + (D.dialog (Just (str " Commands ")) Nothing 55) (vBox ([C.hCenter $ str "Key", B.hBorder] <> keyList) <+> vBox ([str "Actions", B.hBorder] <> actionList)) else emptyWidget where - keyList = map (C.hCenter . str) ["?", "Esc", "w", "a", "v", "q"] + keyList = + map (C.hCenter . str) ["?", "Esc", "w", "a", "v", "s", "b", "q"] actionList = map (hLimit 40 . str) @@ -290,6 +324,8 @@ drawUI s = [splashDialog s, helpDialog s, displayDialog s, inputDialog s, ui s] , "Switch wallets" , "Switch accounts" , "View address" + , "Send Tx" + , "Address Book" , "Quit" ] inputDialog :: State -> Widget Name @@ -297,20 +333,20 @@ drawUI s = [splashDialog s, helpDialog s, displayDialog s, inputDialog s, ui s] case st ^. dialogBox of WName -> D.renderDialog - (D.dialog (Just (str "Create Wallet")) Nothing 50) + (D.dialog (Just (str " Create Wallet ")) Nothing 50) (renderForm $ st ^. inputForm) AName -> D.renderDialog - (D.dialog (Just (str "Create Account")) Nothing 50) + (D.dialog (Just (str " Create Account ")) Nothing 50) (renderForm $ st ^. inputForm) AdName -> D.renderDialog - (D.dialog (Just (str "Create Address")) Nothing 50) + (D.dialog (Just (str " Create Address ")) Nothing 50) (renderForm $ st ^. inputForm) WSelect -> D.renderDialog - (D.dialog (Just (str "Select Wallet")) Nothing 50) - (selectListBox "Wallets" (st ^. wallets) listDrawWallet <=> + (D.dialog (Just (str " Select Wallet ")) Nothing 50) + (selectListBox " Wallets " (st ^. wallets) listDrawWallet <=> C.hCenter (hBox [ capCommand "↑↓ " "move" @@ -321,8 +357,8 @@ drawUI s = [splashDialog s, helpDialog s, displayDialog s, inputDialog s, ui s] ])) ASelect -> D.renderDialog - (D.dialog (Just (str "Select Account")) Nothing 50) - (selectListBox "Accounts" (st ^. accounts) listDrawAccount <=> + (D.dialog (Just (str " Select Account ")) Nothing 50) + (selectListBox " Accounts " (st ^. accounts) listDrawAccount <=> C.hCenter (hBox [ capCommand "↑↓ " "move" @@ -332,11 +368,63 @@ drawUI s = [splashDialog s, helpDialog s, displayDialog s, inputDialog s, ui s] ])) SendTx -> D.renderDialog - (D.dialog (Just (str "Send Transaction")) Nothing 50) + (D.dialog (Just (str " Send Transaction ")) Nothing 50) (renderForm (st ^. txForm) <=> C.hCenter (hBox [capCommand "↲ " "Send", capCommand " " "Cancel"])) Blank -> emptyWidget + -- Address Book List + AdrBook -> + D.renderDialog + (D.dialog (Just $ str " Address Book ") Nothing 60) + (withAttr abDefAttr $ + setAvailableSize (50, 20) $ + viewport ABViewport BT.Vertical $ + vLimit 20 $ + hLimit 50 $ + vBox + [ vLimit 16 $ + hLimit 50 $ + vBox $ [L.renderList listDrawAB True (s ^. abAddresses)] + , padTop Max $ + vLimit 4 $ + hLimit 50 $ + withAttr abMBarAttr $ + vBox $ + [ C.hCenter $ + (capCommand "N" "ew Address" <+> + capCommand "E" "dit Address" <+> + capCommand3 "" "C" "opy Address") + , C.hCenter $ + (capCommand "D" "elete Address" <+> + capCommand "S" "end Zcash" <+> capCommand3 "E" "x" "it") + ] + ]) + -- Address Book new entry form + AdrBookForm -> + D.renderDialog + (D.dialog (Just $ str " New Address Book Entry ") Nothing 50) + (renderForm (st ^. abForm) <=> + C.hCenter + (hBox [capCommand "↲" " Save", capCommand3 "" "" " Cancel"])) + -- Address Book edit/update entry form + AdrBookUpdForm -> + D.renderDialog + (D.dialog (Just $ str " Edit Address Book Entry ") Nothing 50) + (renderForm (st ^. abForm) <=> + C.hCenter + (hBox [capCommand "↲" " Save", capCommand3 "" "" " Cancel"])) + -- Address Book edit/update entry form + AdrBookDelForm -> + D.renderDialog + (D.dialog (Just $ str " Delete Address Book Entry ") Nothing 50) + (renderForm (st ^. abForm) <=> + C.hCenter + (hBox + [ capCommand "C" "onfirm delete" + , capCommand3 "" "" " Cancel" + ])) + -- splashDialog :: State -> Widget Name splashDialog st = if st ^. splashBox @@ -348,9 +436,14 @@ drawUI s = [splashDialog s, helpDialog s, displayDialog s, inputDialog s, ui s] (str " _____ _ _ _ \n|__ /___ _ __ (_) |_| |__\n / // _ \\ '_ \\| | __| '_ \\\n / /| __/ | | | | |_| | | |\n/____\\___|_| |_|_|\\__|_| |_|") <=> C.hCenter - (withAttr titleAttr (str "Zcash Wallet v0.5.2.0-beta")) <=> + (withAttr titleAttr (str "Zcash Wallet v0.5.3.1-beta")) <=> C.hCenter (withAttr blinkAttr $ str "Press any key...")) else emptyWidget + capCommand3 :: String -> String -> String -> Widget Name + capCommand3 l h e = hBox [str l, withAttr titleAttr (str h), str e] + capCommand2 :: String -> String -> String -> Widget Name + capCommand2 l h e = + hBox [str l, withAttr titleAttr (str h), str e, str " | "] capCommand :: String -> String -> Widget Name capCommand k comm = hBox [withAttr titleAttr (str k), str comm, str " | "] xCommand :: Widget Name @@ -401,7 +494,7 @@ drawUI s = [splashDialog s, helpDialog s, displayDialog s, inputDialog s, ui s] Just (_, w) -> withBorderStyle unicodeBold $ D.renderDialog - (D.dialog (Just $ txt "Seed Phrase") Nothing 50) + (D.dialog (Just $ txt " Seed Phrase ") Nothing 50) (padAll 1 $ txtWrap $ E.decodeUtf8Lenient $ @@ -410,15 +503,25 @@ drawUI s = [splashDialog s, helpDialog s, displayDialog s, inputDialog s, ui s] MsgDisplay -> withBorderStyle unicodeBold $ D.renderDialog - (D.dialog (Just $ txt "Message") Nothing 50) + (D.dialog (Just $ txt " Message ") Nothing 50) (padAll 1 $ strWrap $ st ^. msg) + TxIdDisplay -> + withBorderStyle unicodeBold $ + D.renderDialog + (D.dialog (Just $ txt " Success ") Nothing 50) + (padAll 1 $ + (txt "Tx ID: " <+> + txtWrapWith + (WrapSettings False True NoFill FillAfterFirst) + (maybe "None" toText (st ^. sentTx))) <=> + C.hCenter (hBox [capCommand "C" "opy", xCommand])) TxDisplay -> case L.listSelectedElement $ st ^. transactions of Nothing -> emptyWidget Just (_, tx) -> withBorderStyle unicodeBold $ D.renderDialog - (D.dialog (Just $ txt "Transaction") Nothing 50) + (D.dialog (Just $ txt " Transaction ") Nothing 50) (padAll 1 (str @@ -444,7 +547,7 @@ drawUI s = [splashDialog s, helpDialog s, displayDialog s, inputDialog s, ui s] SyncDisplay -> withBorderStyle unicodeBold $ D.renderDialog - (D.dialog (Just $ txt "Sync") Nothing 50) + (D.dialog (Just $ txt " Sync ") Nothing 50) (padAll 1 (updateAttrMap @@ -453,17 +556,33 @@ drawUI s = [splashDialog s, helpDialog s, displayDialog s, inputDialog s, ui s] , (barToDoAttr, P.progressIncompleteAttr) ]) (P.progressBar - (Just $ show (st ^. barValue * 100)) + (Just $ printf "%.2f%%" (st ^. barValue * 100)) (_barValue st)))) SendDisplay -> withBorderStyle unicodeBold $ D.renderDialog - (D.dialog (Just $ txt "Sending Transaction") Nothing 50) + (D.dialog (Just $ txt " Sending Transaction ") Nothing 50) (padAll 1 (strWrapWith (WrapSettings False True NoFill FillAfterFirst) (st ^. msg))) + AdrBookEntryDisplay -> do + case L.listSelectedElement $ st ^. abAddresses of + Just (_, a) -> do + let abentry = + T.pack $ + " Descr: " ++ + T.unpack (addressBookAbdescrip (entityVal a)) ++ + "\n Address: " ++ + T.unpack (addressBookAbaddress (entityVal a)) + withBorderStyle unicodeBold $ + D.renderDialog + (D.dialog (Just $ txt " Address Book Entry ") Nothing 60) + (padAll 1 $ + txtWrapWith (WrapSettings False True NoFill FillAfterFirst) $ + abentry) + _ -> emptyWidget BlankDisplay -> emptyWidget mkInputForm :: DialogInput -> Form DialogInput e Name @@ -484,10 +603,20 @@ mkSendForm bal = ] where isAmountValid :: Integer -> Float -> Bool - isAmountValid b i = (fromIntegral b * 100000000.0) >= i + isAmountValid b i = (fromIntegral b * 100000000.0) >= i && i > 0 label s w = padBottom (Pad 1) $ vLimit 1 (hLimit 15 $ str s <+> fill ' ') <+> w +mkNewABForm :: AdrBookEntry -> Form AdrBookEntry e Name +mkNewABForm = + newForm + [ label "Descrip: " @@= editTextField descrip DescripField (Just 1) + , label "Address: " @@= editTextField address AddressField (Just 1) + ] + where + label s w = + padBottom (Pad 1) $ vLimit 1 (hLimit 10 $ str s <+> fill ' ') <+> w + isRecipientValid :: T.Text -> Bool isRecipientValid a = case isValidUnifiedAddress (E.encodeUtf8 a) of @@ -557,6 +686,14 @@ listDrawTx znet sel tx = then withAttr customAttr (txt $ "> " <> s) else txt $ " " <> s +listDrawAB :: Bool -> Entity AddressBook -> Widget Name +listDrawAB sel ab = + let selStr s = + if sel + then withAttr abSelAttr (txt $ " " <> s) + else txt $ " " <> s + in selStr $ addressBookAbdescrip (entityVal ab) + customAttr :: A.AttrName customAttr = L.listSelectedAttr <> A.attrName "custom" @@ -575,6 +712,15 @@ barDoneAttr = A.attrName "done" barToDoAttr :: A.AttrName barToDoAttr = A.attrName "remaining" +abDefAttr :: A.AttrName +abDefAttr = A.attrName "abdefault" + +abSelAttr :: A.AttrName +abSelAttr = A.attrName "abselected" + +abMBarAttr :: A.AttrName +abMBarAttr = A.attrName "menubar" + validBarValue :: Float -> Float validBarValue = clamp 0 1 @@ -586,12 +732,16 @@ scanZebra dbP zHost zPort b eChan = do dbBlock <- runNoLoggingT $ getMaxBlock pool let sb = max dbBlock b if sb > zgb_blocks bStatus || sb < 1 - then do - liftIO $ BC.writeBChan eChan $ TickMsg "Invalid starting block for scan" + then liftIO $ + BC.writeBChan eChan $ TickMsg "Invalid starting block for scan" else do let bList = [(sb + 1) .. (zgb_blocks bStatus)] - let step = (1.0 :: Float) / fromIntegral (zgb_blocks bStatus - (sb + 1)) - mapM_ (processBlock pool step) bList + if not (null bList) + then do + let step = + (1.0 :: Float) / fromIntegral (zgb_blocks bStatus - (sb + 1)) + mapM_ (processBlock pool step) bList + else liftIO $ BC.writeBChan eChan $ TickVal 1.0 where processBlock :: ConnectionPool -> Float -> Int -> IO () processBlock pool step bl = do @@ -603,8 +753,7 @@ scanZebra dbP zHost zPort b eChan = do "getblock" [Data.Aeson.String $ T.pack $ show bl, jsonNumber 1] case r of - Left e1 -> do - liftIO $ BC.writeBChan eChan $ TickMsg e1 + Left e1 -> liftIO $ BC.writeBChan eChan $ TickMsg e1 Right blk -> do r2 <- liftIO $ @@ -614,8 +763,7 @@ scanZebra dbP zHost zPort b eChan = do "getblock" [Data.Aeson.String $ T.pack $ show bl, jsonNumber 0] case r2 of - Left e2 -> do - liftIO $ BC.writeBChan eChan $ TickMsg e2 + Left e2 -> liftIO $ BC.writeBChan eChan $ TickMsg e2 Right hb -> do let blockTime = getBlockTime hb mapM_ (runNoLoggingT . processTx zHost zPort blockTime pool) $ @@ -640,17 +788,23 @@ appEvent (BT.AppEvent t) = do MsgDisplay -> return () PhraseDisplay -> return () TxDisplay -> return () + TxIdDisplay -> return () SyncDisplay -> return () - SendDisplay -> do - BT.modify $ set msg m + SendDisplay -> BT.modify $ set msg m + AdrBookEntryDisplay -> return () BlankDisplay -> return () + TickTx txid -> do + BT.modify $ set sentTx (Just txid) + BT.modify $ set displayBox TxIdDisplay TickVal v -> do case s ^. displayBox of AddrDisplay -> return () MsgDisplay -> return () PhraseDisplay -> return () TxDisplay -> return () + TxIdDisplay -> return () SendDisplay -> return () + AdrBookEntryDisplay -> return () SyncDisplay -> do if s ^. barValue == 1.0 then do @@ -683,6 +837,10 @@ appEvent (BT.AppEvent t) = do WSelect -> return () ASelect -> return () SendTx -> return () + AdrBook -> return () + AdrBookForm -> return () + AdrBookUpdForm -> return () + AdrBookDelForm -> return () Blank -> do if s ^. timer == 90 then do @@ -700,8 +858,7 @@ appEvent (BT.AppEvent t) = do (s ^. eventDispatch) BT.modify $ set timer 0 return () - else do - BT.modify $ set timer $ 1 + s ^. timer + else BT.modify $ set timer $ 1 + s ^. timer appEvent (BT.VtyEvent e) = do r <- F.focusGetCurrent <$> use focusRing s <- BT.get @@ -710,8 +867,7 @@ appEvent (BT.VtyEvent e) = do else if s ^. helpBox then do case e of - V.EvKey V.KEsc [] -> do - BT.modify $ set helpBox False + V.EvKey V.KEsc [] -> BT.modify $ set helpBox False _ev -> return () else do case s ^. displayBox of @@ -770,8 +926,19 @@ appEvent (BT.VtyEvent e) = do MsgDisplay -> BT.modify $ set displayBox BlankDisplay PhraseDisplay -> BT.modify $ set displayBox BlankDisplay TxDisplay -> BT.modify $ set displayBox BlankDisplay + TxIdDisplay -> do + case e of + V.EvKey (V.KChar 'x') [] -> + BT.modify $ set displayBox BlankDisplay + V.EvKey (V.KChar 'c') [] -> do + liftIO $ + setClipboard $ + T.unpack $ maybe "None" toText (s ^. sentTx) + BT.modify $ set msg "Copied transaction ID!" + _ev -> return () SendDisplay -> BT.modify $ set displayBox BlankDisplay SyncDisplay -> BT.modify $ set displayBox BlankDisplay + AdrBookEntryDisplay -> BT.modify $ set displayBox BlankDisplay BlankDisplay -> do case s ^. dialogBox of WName -> do @@ -834,7 +1001,7 @@ appEvent (BT.VtyEvent e) = do V.EvKey (V.KChar 'n') [] -> do BT.modify $ set inputForm $ - updateFormState (DialogInput "New Wallet") $ + updateFormState (DialogInput " New Wallet ") $ s ^. inputForm BT.modify $ set dialogBox WName V.EvKey (V.KChar 's') [] -> @@ -851,7 +1018,7 @@ appEvent (BT.VtyEvent e) = do V.EvKey (V.KChar 'n') [] -> do BT.modify $ set inputForm $ - updateFormState (DialogInput "New Account") $ + updateFormState (DialogInput " New Account ") $ s ^. inputForm BT.modify $ set dialogBox AName ev -> BT.zoom accounts $ L.handleListEvent ev @@ -912,7 +1079,7 @@ appEvent (BT.VtyEvent e) = do BT.modify $ set msg "Invalid inputs" BT.modify $ set displayBox MsgDisplay BT.modify $ set dialogBox Blank - ev -> do + ev -> BT.zoom txForm $ do handleFormEvent (BT.VtyEvent ev) fs <- BT.gets formState @@ -920,6 +1087,189 @@ appEvent (BT.VtyEvent e) = do setFieldValid (isRecipientValid (fs ^. sendTo)) RecField + AdrBook -> do + case e of + V.EvKey (V.KChar 'x') [] -> + BT.modify $ set dialogBox Blank + V.EvKey (V.KChar 'c') [] + -- Copy Address to Clipboard + -> do + case L.listSelectedElement $ s ^. abAddresses of + Just (_, a) -> do + liftIO $ + setClipboard $ + T.unpack $ addressBookAbaddress (entityVal a) + BT.modify $ + set msg $ + "Address copied to Clipboard from >>\n" ++ + T.unpack (addressBookAbdescrip (entityVal a)) + BT.modify $ set displayBox MsgDisplay + _ -> do + BT.modify $ + set msg "Error while copying the address!!" + BT.modify $ set displayBox MsgDisplay + -- Send Zcash transaction + V.EvKey (V.KChar 's') [] -> do + case L.listSelectedElement $ s ^. abAddresses of + Just (_, a) -> do + BT.modify $ + set txForm $ + mkSendForm + (s ^. balance) + (SendInput + (addressBookAbaddress (entityVal a)) + 0.0 + "") + BT.modify $ set dialogBox SendTx + _ -> do + BT.modify $ + set msg "No receiver address available!!" + BT.modify $ set displayBox MsgDisplay + -- Edit an entry in Address Book + V.EvKey (V.KChar 'e') [] -> do + case L.listSelectedElement $ s ^. abAddresses of + Just (_, a) -> do + BT.modify $ + set + abCurAdrs + (addressBookAbaddress (entityVal a)) + BT.modify $ + set abForm $ + mkNewABForm + (AdrBookEntry + (addressBookAbdescrip (entityVal a)) + (addressBookAbaddress (entityVal a))) + BT.modify $ set dialogBox AdrBookUpdForm + _ -> do + BT.modify $ set dialogBox Blank + -- Delete an entry from Address Book + V.EvKey (V.KChar 'd') [] -> do + case L.listSelectedElement $ s ^. abAddresses of + Just (_, a) -> do + BT.modify $ + set + abCurAdrs + (addressBookAbaddress (entityVal a)) + BT.modify $ + set abForm $ + mkNewABForm + (AdrBookEntry + (addressBookAbdescrip (entityVal a)) + (addressBookAbaddress (entityVal a))) + BT.modify $ set dialogBox AdrBookDelForm + _ -> do + BT.modify $ set dialogBox Blank + -- Create a new entry in Address Book + V.EvKey (V.KChar 'n') [] -> do + BT.modify $ + set abForm $ mkNewABForm (AdrBookEntry "" "") + BT.modify $ set dialogBox AdrBookForm + -- Show AddressBook entry data + V.EvKey V.KEnter [] -> do + BT.modify $ set displayBox AdrBookEntryDisplay + -- Process any other event + ev -> BT.zoom abAddresses $ L.handleListEvent ev + -- Process new address book entry + AdrBookForm -> do + case e of + V.EvKey V.KEsc [] -> BT.modify $ set dialogBox AdrBook + V.EvKey V.KEnter [] -> do + pool <- liftIO $ runNoLoggingT $ initPool $ s ^. dbPath + fs <- BT.zoom abForm $ BT.gets formState + let idescr = T.unpack $ T.strip (fs ^. descrip) + let iabadr = fs ^. address + if not (null idescr) && isRecipientValid iabadr + then do + res <- + liftIO $ + saveAdrsInAdrBook pool $ + AddressBook + (ZcashNetDB (s ^. network)) + (fs ^. descrip) + (fs ^. address) + case res of + Nothing -> do + BT.modify $ + set + msg + ("AddressBook Entry already exists: " ++ + T.unpack (fs ^. address)) + BT.modify $ set displayBox MsgDisplay + Just _ -> do + BT.modify $ + set + msg + ("New AddressBook entry created!!\n" ++ + T.unpack (fs ^. address)) + BT.modify $ set displayBox MsgDisplay + -- case end + s' <- liftIO $ refreshAddressBook s + BT.put s' + BT.modify $ set dialogBox AdrBook + else do + BT.modify $ set msg "Invalid or missing data!!: " + BT.modify $ set displayBox MsgDisplay + BT.modify $ set dialogBox AdrBookForm + ev -> + BT.zoom abForm $ do + handleFormEvent (BT.VtyEvent ev) + fs <- BT.gets formState + BT.modify $ + setFieldValid + (isRecipientValid (fs ^. address)) + AddressField + AdrBookUpdForm -> do + case e of + V.EvKey V.KEsc [] -> BT.modify $ set dialogBox AdrBook + V.EvKey V.KEnter [] -> do + pool <- liftIO $ runNoLoggingT $ initPool $ s ^. dbPath + fs <- BT.zoom abForm $ BT.gets formState + let idescr = T.unpack $ T.strip (fs ^. descrip) + let iabadr = fs ^. address + if not (null idescr) && isRecipientValid iabadr + then do + res <- + liftIO $ + updateAdrsInAdrBook + pool + (fs ^. descrip) + (fs ^. address) + (s ^. abCurAdrs) + BT.modify $ + set + msg + ("AddressBook entry modified!!\n" ++ + T.unpack (fs ^. address)) + BT.modify $ set displayBox MsgDisplay + -- case end + s' <- liftIO $ refreshAddressBook s + BT.put s' + BT.modify $ set dialogBox AdrBook + else do + BT.modify $ set msg "Invalid or missing data!!: " + BT.modify $ set displayBox MsgDisplay + BT.modify $ set dialogBox AdrBookForm + ev -> + BT.zoom abForm $ do + handleFormEvent (BT.VtyEvent ev) + fs <- BT.gets formState + BT.modify $ + setFieldValid + (isRecipientValid (fs ^. address)) + AddressField + -- Process delete AddresBook entry + AdrBookDelForm -> do + case e of + V.EvKey V.KEsc [] -> BT.modify $ set dialogBox AdrBook + V.EvKey (V.KChar 'c') [] -> do + pool <- liftIO $ runNoLoggingT $ initPool $ s ^. dbPath + fs <- BT.zoom abForm $ BT.gets formState + res <- liftIO $ deleteAdrsFromAB pool (fs ^. address) + s' <- liftIO $ refreshAddressBook s + BT.put s' + BT.modify $ set dialogBox AdrBook + ev -> BT.modify $ set dialogBox AdrBookDelForm + -- Process any other event Blank -> do case e of V.EvKey (V.KChar '\t') [] -> focusRing %= F.focusNext @@ -943,12 +1293,16 @@ appEvent (BT.VtyEvent e) = do set txForm $ mkSendForm (s ^. balance) (SendInput "" 0.0 "") BT.modify $ set dialogBox SendTx + V.EvKey (V.KChar 'b') [] -> + BT.modify $ set dialogBox AdrBook ev -> case r of Just AList -> BT.zoom addresses $ L.handleListEvent ev Just TList -> BT.zoom transactions $ L.handleListEvent ev + Just ABList -> + BT.zoom abAddresses $ L.handleListEvent ev _anyName -> return () where printMsg :: String -> BT.EventM Name State () @@ -968,11 +1322,14 @@ theMap = , (blinkAttr, style V.blink) , (focusedFormInputAttr, V.white `on` V.blue) , (invalidFormInputAttr, V.red `on` V.black) - , (E.editAttr, V.white `on` V.blue) - , (E.editFocusedAttr, V.blue `on` V.white) + , (E.editAttr, V.white `on` V.black) + , (E.editFocusedAttr, V.black `on` V.white) , (baseAttr, bg V.brightBlack) , (barDoneAttr, V.white `on` V.blue) , (barToDoAttr, V.white `on` V.black) + , (abDefAttr, V.white `on` V.blue) + , (abSelAttr, V.black `on` V.white) + , (abMBarAttr, V.white `on` V.black) ] theApp :: M.App State Tick Name @@ -985,8 +1342,8 @@ theApp = , M.appAttrMap = const theMap } -runZenithCLI :: Config -> IO () -runZenithCLI config = do +runZenithTUI :: Config -> IO () +runZenithTUI config = do let host = c_zebraHost config let port = c_zebraPort config let dbFilePath = c_dbPath config @@ -1018,6 +1375,7 @@ runZenithCLI config = do if not (null walList) then zcashWalletLastSync $ entityVal $ head walList else 0 + abookList <- getAdrBook pool $ zgb_net chainInfo bal <- if not (null accList) then getBalance pool $ entityKey $ head accList @@ -1058,6 +1416,10 @@ runZenithCLI config = do eventChan 0 (mkSendForm 0 $ SendInput "" 0.0 "") + (L.list ABList (Vec.fromList abookList) 1) + (mkNewABForm (AdrBookEntry "" "")) + "" + Nothing Left e -> do print $ "No Zebra node available on port " <> @@ -1112,14 +1474,13 @@ addNewWallet n s = do let netName = s ^. network r <- saveWallet pool $ ZcashWallet n (ZcashNetDB netName) (PhraseDB sP) bH 0 case r of - Nothing -> do - return $ s & msg .~ ("Wallet already exists: " ++ T.unpack n) + Nothing -> return $ s & msg .~ ("Wallet already exists: " ++ T.unpack n) Just _ -> do wL <- getWallets pool netName let aL = L.listFindBy (\x -> zcashWalletName (entityVal x) == n) $ L.listReplace (Vec.fromList wL) (Just 0) (s ^. wallets) - return $ (s & wallets .~ aL) & msg .~ "Created new wallet: " ++ T.unpack n + return $ s & wallets .~ aL & msg .~ "Created new wallet: " ++ T.unpack n addNewAccount :: T.Text -> State -> IO State addNewAccount n s = do @@ -1138,19 +1499,18 @@ addNewAccount n s = do try $ createZcashAccount n (aL' + 1) selWallet :: IO (Either IOError ZcashAccount) case zA of - Left e -> return $ s & msg .~ ("Error: " ++ show e) + Left e -> return $ s & msg .~ "Error: " ++ show e Right zA' -> do r <- saveAccount pool zA' case r of - Nothing -> - return $ s & msg .~ ("Account already exists: " ++ T.unpack n) + Nothing -> return $ s & msg .~ "Account already exists: " ++ T.unpack n Just x -> do aL <- runNoLoggingT $ getAccounts pool (entityKey selWallet) let nL = L.listMoveToElement x $ L.listReplace (Vec.fromList aL) (Just 0) (s ^. accounts) return $ - (s & accounts .~ nL) & msg .~ "Created new account: " ++ T.unpack n + s & accounts .~ nL & msg .~ "Created new account: " ++ T.unpack n refreshAccount :: State -> IO State refreshAccount s = do @@ -1203,6 +1563,21 @@ refreshTxs s = do let tL' = L.listReplace (Vec.fromList tList) (Just 0) (s ^. transactions) return $ s & transactions .~ tL' +refreshAddressBook :: State -> IO State +refreshAddressBook s = do + pool <- runNoLoggingT $ initPool $ s ^. dbPath + selAddress <- + do case L.listSelectedElement $ s ^. abAddresses of + Nothing -> do + let fAdd = + L.listSelectedElement $ + L.listMoveToBeginning $ s ^. abAddresses + return fAdd + Just a2 -> return $ Just a2 + abookList <- getAdrBook pool (s ^. network) + let tL' = L.listReplace (Vec.fromList abookList) (Just 0) (s ^. abAddresses) + return $ s & abAddresses .~ tL' + addNewAddress :: T.Text -> Scope -> State -> IO State addNewAddress n scope s = do pool <- runNoLoggingT $ initPool $ s ^. dbPath @@ -1220,19 +1595,18 @@ addNewAddress n scope s = do try $ createWalletAddress n (maxAddr + 1) (s ^. network) scope selAccount :: IO (Either IOError WalletAddress) case uA of - Left e -> return $ s & msg .~ ("Error: " ++ show e) + Left e -> return $ s & msg .~ "Error: " ++ show e Right uA' -> do nAddr <- saveAddress pool uA' case nAddr of - Nothing -> - return $ s & msg .~ ("Address already exists: " ++ T.unpack n) + Nothing -> return $ s & msg .~ "Address already exists: " ++ T.unpack n Just x -> do addrL <- runNoLoggingT $ getAddresses pool (entityKey selAccount) let nL = L.listMoveToElement x $ L.listReplace (Vec.fromList addrL) (Just 0) (s ^. addresses) return $ - (s & addresses .~ nL) & msg .~ "Created new address: " ++ + s & addresses .~ nL & msg .~ "Created new address: " ++ T.unpack n ++ "(" ++ T.unpack (showAddress $ walletAddressUAddress $ entityVal x) ++ ")" @@ -1267,7 +1641,7 @@ sendTransaction pool chan zHost zPort znet accId bl amt ua memo = do [Data.Aeson.String $ toText rawTx] case resp of Left e1 -> BC.writeBChan chan $ TickMsg $ "Zebra error: " ++ show e1 - Right txId -> BC.writeBChan chan $ TickMsg $ "Tx ID: " ++ txId + Right txId -> BC.writeBChan chan $ TickTx txId where parseAddress :: T.Text -> IO UnifiedAddress parseAddress a = diff --git a/src/Zenith/Core.hs b/src/Zenith/Core.hs index a8dc6f2..abfb476 100644 --- a/src/Zenith/Core.hs +++ b/src/Zenith/Core.hs @@ -24,7 +24,7 @@ import Data.Binary.Get hiding (getBytes) import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as LBS import Data.Digest.Pure.MD5 -import Data.HexString (HexString, hexString, toBytes) +import Data.HexString (HexString, hexString, toBytes, toText) import Data.List import Data.Maybe (fromJust) import Data.Pool (Pool) @@ -574,6 +574,7 @@ prepareTx pool zebraHost zebraPort zn za bh amt ua memo = do zn (bh + 3) True + logDebugN $ T.pack $ show tx return tx where makeOutgoing :: diff --git a/src/Zenith/DB.hs b/src/Zenith/DB.hs index a48151d..8f9eef1 100644 --- a/src/Zenith/DB.hs +++ b/src/Zenith/DB.hs @@ -246,6 +246,12 @@ share position Int UniqueSSPos tx position deriving Show Eq + AddressBook + network ZcashNetDB + abdescrip T.Text + abaddress T.Text + UniqueABA abaddress + deriving Show Eq |] -- * Database functions @@ -1467,5 +1473,56 @@ readUnifiedAddressDB :: WalletAddress -> Maybe UnifiedAddress readUnifiedAddressDB = isValidUnifiedAddress . TE.encodeUtf8 . getUA . walletAddressUAddress +-- | Get list of external zcash addresses from database +getAdrBook :: ConnectionPool -> ZcashNet -> IO [Entity AddressBook] +getAdrBook pool n = + runNoLoggingT $ + PS.retryOnBusy $ + flip PS.runSqlPool pool $ do + select $ do + adrbook <- from $ table @AddressBook + where_ (adrbook ^. AddressBookNetwork ==. val (ZcashNetDB n)) + pure adrbook + +-- | Save a new address into AddressBook +saveAdrsInAdrBook :: + ConnectionPool -- ^ The database path to use + -> AddressBook -- ^ The address to add to the database + -> IO (Maybe (Entity AddressBook)) +saveAdrsInAdrBook pool a = + runNoLoggingT $ + PS.retryOnBusy $ flip PS.runSqlPool pool $ insertUniqueEntity a + +-- | Update an existing address into AddressBook +updateAdrsInAdrBook :: ConnectionPool -> T.Text -> T.Text -> T.Text -> IO () +updateAdrsInAdrBook pool d a ia = do + runNoLoggingT $ + PS.retryOnBusy $ + flip PS.runSqlPool pool $ do + update $ \ab -> do + set ab [AddressBookAbdescrip =. val d, AddressBookAbaddress =. val a] + where_ $ ab ^. AddressBookAbaddress ==. val ia + +-- | Get one AddrssBook record using the Address as a key +-- getABookRec :: ConnectionPool -> T.Tex t -> IO (Maybe (Entity AddressBook)) +-- getABookRec pool a = do +-- runNoLoggingT $ +-- PS.retryOnBusy $ +-- flip PS.runSqlPool pool $ +-- select $ do +-- adrbook <- from $ table @AddressBook +-- where_ ((adrbook ^. AddressBookAbaddress) ==. val a) +-- return adrbook + +-- | delete an existing address from AddressBook +deleteAdrsFromAB :: ConnectionPool -> T.Text -> IO () +deleteAdrsFromAB pool ia = do + runNoLoggingT $ + PS.retryOnBusy $ + flip PS.runSqlPool pool $ do + delete $ do + ab <- from $ table @AddressBook + where_ (ab ^. AddressBookAbaddress ==. val ia) + rmdups :: Ord a => [a] -> [a] rmdups = map head . group . sort diff --git a/src/Zenith/Utils.hs b/src/Zenith/Utils.hs index 96ca8dd..0f73fc9 100644 --- a/src/Zenith/Utils.hs +++ b/src/Zenith/Utils.hs @@ -26,18 +26,18 @@ jsonNumber i = Number $ scientific (fromIntegral i) 0 -- | Helper function to display small amounts of ZEC displayZec :: Integer -> String displayZec s - | abs s < 100 = show s ++ " zats " - | abs s < 100000 = show (fromIntegral s / 100) ++ " μZEC " - | abs s < 100000000 = show (fromIntegral s / 100000) ++ " mZEC " + | abs s < 100 = show s ++ " zats" + | abs s < 100000 = show (fromIntegral s / 100) ++ " μZEC" + | abs s < 100000000 = show (fromIntegral s / 100000) ++ " mZEC" | otherwise = show (fromIntegral s / 100000000) ++ " ZEC " -- | Helper function to display small amounts of ZEC displayTaz :: Integer -> String displayTaz s - | abs s < 100 = show s ++ " tazs " - | abs s < 100000 = show (fromIntegral s / 100) ++ " μTAZ " - | abs s < 100000000 = show (fromIntegral s / 100000) ++ " mTAZ " - | otherwise = show (fromIntegral s / 100000000) ++ " TAZ " + | abs s < 100 = show s ++ " tazs" + | abs s < 100000 = show (fromIntegral s / 100) ++ " μTAZ" + | abs s < 100000000 = show (fromIntegral s / 100000) ++ " mTAZ" + | otherwise = show (fromIntegral s / 100000000) ++ " TAZ" -- | Helper function to display abbreviated Unified Address showAddress :: UnifiedAddressDB -> T.Text diff --git a/zenith.cabal b/zenith.cabal index 09a664c..c54b192 100644 --- a/zenith.cabal +++ b/zenith.cabal @@ -1,6 +1,6 @@ cabal-version: 3.0 name: zenith -version: 0.5.2.0-beta +version: 0.5.3.1-beta license: MIT license-file: LICENSE author: Rene Vergara diff --git a/zenith_er.bmp b/zenith_er.bmp new file mode 100644 index 0000000..d248b8f Binary files /dev/null and b/zenith_er.bmp differ diff --git a/zenith_er.png b/zenith_er.png new file mode 100644 index 0000000..5d6d21c Binary files /dev/null and b/zenith_er.png differ