From de3bc48c38d0c32f6b0f115d3876de707282b177 Mon Sep 17 00:00:00 2001 From: "Rene V. Vergara" Date: Sat, 11 Jan 2025 13:22:30 -0500 Subject: [PATCH] rvv001 - URI string generation in progress --- src/Zenith/GUI.hs | 165 ++++++++++++++++++++++++++++++++++++++++---- src/Zenith/Utils.hs | 12 ++++ test/Spec.hs | 8 +++ zenith.cabal | 1 + 4 files changed, 172 insertions(+), 14 deletions(-) diff --git a/src/Zenith/GUI.hs b/src/Zenith/GUI.hs index d780bac..fe4aca6 100644 --- a/src/Zenith/GUI.hs +++ b/src/Zenith/GUI.hs @@ -166,8 +166,12 @@ data AppEvent | ShowViewingKey !VkTypeDef !T.Text | CopyViewingKey !T.Text !T.Text | CloseShowVK - | DisplayPaymentURI - | ClosePaymentURI + | DisplayPaymentURIForm + | ClosePaymentURIForm + | GenURIString + | CheckAmountURI !Float + | CloseShowURIOverlay + | CopyURIString !T.Text | DisplayPayUsingURI | ClosePayUsingURI | ProcIfValidURI @@ -238,6 +242,7 @@ data AppModel = AppModel , _vkTypeName :: !T.Text , _vkData :: !T.Text , _paymentURIDisplay :: !Bool + , _showURIDisplay :: !Bool , _usepmtURIOverlay :: !Bool , _uriString :: !T.Text } deriving (Eq, Show) @@ -292,6 +297,7 @@ buildUI wenv model = widgetTree updateABAddress , showVKOverlay `nodeVisible` model ^. viewingKeyDisplay , paymentURIOverlay `nodeVisible` model ^. paymentURIDisplay + , showURIOverlay `nodeVisible` model ^. showURIDisplay , pmtUsingURIOverlay `nodeVisible` model ^. usepmtURIOverlay , shieldOverlay `nodeVisible` model ^. shieldZec , deShieldOverlay `nodeVisible` model ^. deShieldZec @@ -387,7 +393,7 @@ buildUI wenv model = widgetTree ("Balance in " <> T.toUpper (c_currencyCode (model ^. configuration)))) `styleBasic` [bgColor white, borderB 1 gray, padding 3] - , box_ [alignLeft, onClick DisplayPaymentURI] (label "Create URI") `styleBasic` + , box_ [alignLeft, onClick DisplayPaymentURIForm] (label "Create URI") `styleBasic` [bgColor white, borderB 1 gray, padding 3] , box_ [alignLeft, onClick DisplayPayUsingURI] @@ -1253,10 +1259,40 @@ buildUI wenv model = widgetTree [ box_ [alignMiddle] (label "Create URI" `styleBasic` - [textColor white, textFont "Bold", textSize 12]) `styleBasic` - [bgColor btnColor] + [textFont "Bold", textSize 12]) , separatorLine `styleBasic` [fgColor btnColor] , spacer + , hstack + [ label "Privacy Level:" `styleBasic` + [width 70, textFont "Bold"] + , spacer + , label "Full " `styleBasic` [width 40] + , radio Full privacyChoice + , spacer + , label "Medium " `styleBasic` [width 40] + , radio Medium privacyChoice + ] + , hstack + [ label " " `styleBasic` + [width 70, textFont "Bold"] + , spacer + , label "Low " `styleBasic` [width 40] + , radio Low privacyChoice + , spacer + , label "None " `styleBasic` [width 40] + , radio None privacyChoice + ] + , spacer + , hstack + [ label "To:" `styleBasic` [width 50, textFont "Bold"] + , spacer + , textField_ sendRecipient [onChange CheckRecipient] `styleBasic` + [ width 150 + , styleIf + (not $ model ^. recipientValid) + (textColor red) + ] + ] , hstack [ label "Amount:" `styleBasic` [width 50, textFont "Bold"] @@ -1284,14 +1320,14 @@ buildUI wenv model = widgetTree [width 150, height 40] ] , spacer + -- Radio button group for privacy level , box_ [alignMiddle] (hstack [ spacer - , mainButton "Create URI" NotImplemented `nodeEnabled` - True + , button "Cancel" ClosePaymentURIForm , spacer - , button "Cancel" ClosePaymentURI + , mainButton "Send" SendTx `nodeEnabled` False , spacer ]) ]) `styleBasic` @@ -1301,6 +1337,82 @@ buildUI wenv model = widgetTree , filler ]) `styleBasic` [bgColor (white & L.a .~ 0.5)] + -- box + -- (vstack + -- [ filler + -- , hstack + -- [ filler + -- , box_ + -- [] + -- (vstack + -- [ box_ + -- [alignMiddle] + -- (label "Create URI" `styleBasic` + -- [textColor white, textFont "Bold", textSize 12]) `styleBasic` + -- [bgColor btnColor] + -- , separatorLine `styleBasic` [fgColor btnColor] + -- , spacer + -- , hstack + -- [ label "Amount:" `styleBasic` + -- [width 50, textFont "Bold"] + -- , spacer + -- , numericField_ + -- sendAmount + -- [ decimals 8 + -- , minValue 0.0 + -- , maxValue (fromIntegral (model ^. balance) / 100000000.0) + -- , validInput amountValid + -- , onChange CheckAmountURI + -- ] `styleBasic` + -- [ width 150 + -- , styleIf + -- (not $ model ^. amountValid) + -- (textColor red) + -- ] + -- ] + -- -- , hstack + -- -- [ label "Memo:" `styleBasic` + -- -- [width 50, textFont "Bold"] + -- -- , spacer + -- -- , textArea sendMemo `styleBasic` + -- -- [width 150, height 40] + -- -- ] + -- , spacer + -- , box_ + -- [alignMiddle] + -- (hstack + -- [ spacer + -- -- , mainButton "Create URI" NotImplemented `nodeEnabled` True + -- -- , spacer + -- , button "Cancel" ClosePaymentURIForm + -- , spacer + -- ]) + -- ]) `styleBasic` + -- [radius 4, border 2 btnColor, bgColor white, padding 4] + -- , filler + -- ] + -- , filler + -- ]) `styleBasic` + -- [bgColor (white & L.a .~ 0.5)] + showURIOverlay = + alert CloseShowURIOverlay $ + vstack + [ box_ + [] + (label "Payment URI" `styleBasic` + [textFont "Bold", textColor white, textSize 12, padding 3]) `styleBasic` + [bgColor btnColor, radius 2, padding 3] + , spacer + , hstack + [filler, label_ (txtWrapN (model ^. uriString ) 64) [multiline], filler] + , spacer + , hstack + [ filler + , button "Copy to Clipboard" $ + CopyURIString (model ^. uriString) + , filler + ] + ] pmtUsingURIOverlay = box (vstack @@ -1694,6 +1806,10 @@ handleEvent wenv node model evt = model & amountValid .~ (i < (fromIntegral (model ^. balance) / 100000000.0)) ] + CheckAmountURI i -> + [ Model $ + model & amountValid .~ (i < (fromIntegral (model ^. balance) / 100000000.0)) + ] ShowTxId tx -> [Model $ model & showId ?~ tx & modalMsg .~ Nothing] -- | -- | Address Book Events @@ -1752,6 +1868,11 @@ handleEvent wenv node model evt = , setClipboardData $ ClipboardText v , Event $ ShowMessage (t <> " viewing key copied!!") ] + CopyURIString u -> + [ setClipboardData ClipboardEmpty + , setClipboardData $ ClipboardText u + , Event $ ShowMessage "URI string copied to clipboard!!" + ] DeleteABEntry a -> [ Task $ deleteAdrBook (model ^. configuration) a , Model $ @@ -1814,11 +1935,21 @@ handleEvent wenv node model evt = -- -- Display PaymentURI Form -- - DisplayPaymentURI -> + DisplayPaymentURIForm -> [ Model $ - model & paymentURIDisplay .~ True & uriString .~ "" & menuPopup .~ False + model & paymentURIDisplay .~ True + & uriString .~ "" + & amountValid .~ False + & sendAmount .~ 0.0 + & sendMemo .~ "" + & menuPopup .~ False ] - ClosePaymentURI -> [Model $ model & paymentURIDisplay .~ False] + ClosePaymentURIForm -> [Model $ model & paymentURIDisplay .~ False] + -- + -- Generate URI + -- + GenURIString -> [ Task $ genURIString (model ^. sendAmount) (model ^. sendMemo) ] + CloseShowURIOverlay -> [ Model $ model & showURIDisplay .~ False & uriString .~ "" ] -- -- Display Pay using URI Form -- @@ -1841,9 +1972,8 @@ handleEvent wenv node model evt = T.pack (uriAddress p) & sendAmount .~ realToFrac a & - sendMemo .~ - (uriMemo p) - , Event $ ClosePaymentURI + sendMemo .~ (uriMemo p) + , Event $ ClosePaymentURIForm ] Nothing -> [ Model $ @@ -2062,6 +2192,12 @@ handleEvent wenv node model evt = let tsk = getTranSK $ zcashAccountTPrivateKey $ entityVal acc ivk <- deriveUivk n osk ssk tsk return $ ShowViewingKey VkIncoming ivk + -- + -- Gen URI String + -- + genURIString :: Float -> T.Text -> IO AppEvent + genURIString mAmt mMemo = do + return $ ShowMessage "Prueba de URI" scanZebra :: T.Text @@ -2415,6 +2551,7 @@ runZenithGUI config = do "" False False + False "" startApp model handleEvent buildUI (params hD) Left _e -> print "Zebra not available" diff --git a/src/Zenith/Utils.hs b/src/Zenith/Utils.hs index a3a91d7..29b9aaa 100644 --- a/src/Zenith/Utils.hs +++ b/src/Zenith/Utils.hs @@ -22,6 +22,7 @@ import qualified Data.Text as T import qualified Data.Text.Encoding as E import qualified Data.Text.Encoding as TE import Network.HTTP.Simple +import Network.URI (escapeURIString, isUnreserved) import System.Directory import System.Process (createProcess_, shell) import Text.Printf (printf) @@ -334,3 +335,14 @@ padBase64 bs = bs <> BC.replicate paddingLength '=' -- Function to decode a base64 un-padded string decodeBase64Unpadded :: BC.ByteString -> Either String BC.ByteString decodeBase64Unpadded = B64.decode . padBase64 + +-- Function to encode memo as un-padded Base64 +encodeMemo :: String -> String +encodeMemo = BC.unpack . BC.takeWhile (/= '=') . B64.encode . BC.pack + +-- Function to create a ZIP-321 URI +createZip321 :: String -> Maybe Double -> Maybe String -> String +createZip321 address mAmount mMemo = + "zcash:" ++ address + ++ maybe "" (\amount -> "?amount=" ++ show amount) mAmount + ++ maybe "" (\memo -> "&memo=" ++ escapeURIString isUnreserved (encodeMemo memo)) mMemo diff --git a/test/Spec.hs b/test/Spec.hs index 699fb25..a3e5fd8 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -1138,3 +1138,11 @@ main = do print p (uriAmount p) `shouldBe` Just 100.0 Left e -> assertFailure $ "Error: " ++ e + describe "Create a ZIP-321 URI payment string " $ do + it "Creating an URI using a valid Zcash address, an amount, and a memo " $ do + let address = "ztestsapling10yy2ex5dcqkclhc7z7yrnjq2z6feyjad56ptwlfgmy77dmaqqrl9gyhprdx59qgmsnyfska2kez" + let amount = Just 1.2345 + let memo = Just "This is a simple memo." + let uriString = createZip321 address amount memo + print uriString + uriString `shouldBe` "zcash:ztestsapling10yy2ex5dcqkclhc7z7yrnjq2z6feyjad56ptwlfgmy77dmaqqrl9gyhprdx59qgmsnyfska2kez?amount=1.2345&memo=VGhpcyBpcyBhIHNpbXBsZSBtZW1vLg" diff --git a/zenith.cabal b/zenith.cabal index 0b1e34a..642e6d3 100644 --- a/zenith.cabal +++ b/zenith.cabal @@ -97,6 +97,7 @@ library , word-wrap , zcash-haskell , unordered-containers + , network-uri --pkgconfig-depends: rustzcash_wrapper default-language: Haskell2010