From d476183a1d3e42637cc6215a8c3657bee890424f Mon Sep 17 00:00:00 2001 From: "Rene V. Vergara" Date: Mon, 30 Dec 2024 21:00:57 -0500 Subject: [PATCH] rvv001 - Issue 085 - [Zenith GUI] Read a payment URI New type to support URI data structure created (Types.hs) Function to parse an URI string created (in Utils.hs) Test case added to Benchmark Suite --- .gitignore | 4 ++++ src/Zenith/Types.hs | 9 +++++++++ src/Zenith/Utils.hs | 45 +++++++++++++++++++++++++++++++++++++++++++++ test/ServerSpec.hs | 3 ++- test/Spec.hs | 8 ++++++++ 5 files changed, 68 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 00967d7..938bae6 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,7 @@ zenith.db zenith.log zenith.db-shm zenith.db-wal +test.db +test.db-shm +test.db-wal + diff --git a/src/Zenith/Types.hs b/src/Zenith/Types.hs index e20f5a0..b94c78d 100644 --- a/src/Zenith/Types.hs +++ b/src/Zenith/Types.hs @@ -508,3 +508,12 @@ encodeHexText' t = if T.length t > 0 then C.unpack . B64.encode $ E.encodeUtf8 t else C.unpack . B64.encode $ E.encodeUtf8 "Sent from Zenith" + +-- | Define a data structure for the parsed components +data ZcashPaymentURI = ZcashPaymentURI + { uriAddress :: String + , uriAmount :: Maybe Double + , uriMemo :: C.ByteString + , uriLabel :: Maybe String + , uriMessage :: Maybe String + } deriving (Show, Eq) diff --git a/src/Zenith/Utils.hs b/src/Zenith/Utils.hs index e3c2dd6..835099a 100644 --- a/src/Zenith/Utils.hs +++ b/src/Zenith/Utils.hs @@ -15,12 +15,16 @@ import qualified Data.Text as T import qualified Data.Text.Encoding as E import Control.Exception (try, SomeException) import Control.Monad (when) +import qualified Data.ByteString.Base64 as B64 +import qualified Data.ByteString.Char8 as BC import qualified Data.ByteString.Lazy as B import qualified Data.ByteString.Lazy.Char8 as BL import System.Directory import System.Process (createProcess_, shell) import Text.Regex.Posix +import Text.Read (readMaybe) import Text.Printf (printf) +import qualified Data.Text.Encoding as TE import ZcashHaskell.Orchard ( encodeUnifiedAddress , isValidUnifiedAddress @@ -46,6 +50,7 @@ import Zenith.Types , UnifiedAddressDB(..) , ZcashAddress(..) , ZcashPool(..) + , ZcashPaymentURI (..) ) import Network.HTTP.Simple import Data.Scientific (Scientific, toRealFloat) @@ -276,4 +281,44 @@ getZcashPrice currency = do _ -> return Nothing _ -> return Nothing _ -> return Nothing + +-- Parse memo result to convert it to a ByteString +processEither :: Either String BC.ByteString -> BC.ByteString +processEither (Right bs) = bs +processEither (Left e) = BC.pack e -- Returns the error + +-- Parse the query string into key-value pairs +parseQuery :: String -> [(String, String)] +parseQuery query = map (breakOn '=') (splitOn '&' query) + where + splitOn :: Char -> String -> [String] + splitOn _ [] = [""] + splitOn delim (c:cs) + | c == delim = "" : rest + | otherwise = (c : head rest) : tail rest + where + rest = splitOn delim cs + + breakOn :: Char -> String -> (String, String) + breakOn delim str = (key, drop 1 value) + where (key, value) = span (/= delim) str + +-- Parse a ZIP-321 encoded string into a ZcashPayment structure +parseZcashPayment :: String -> Either String ZcashPaymentURI +parseZcashPayment input + | not (T.isPrefixOf "zcash:" (T.pack input)) = Left "Invalid scheme: must start with 'zcash:'" + | otherwise = + let (addrPart, queryPart) = break (== '?') (drop 6 input) + queryParams = parseQuery (drop 1 queryPart) + in Right ZcashPaymentURI + { uriAddress = addrPart + , uriAmount = lookup "amount" queryParams >>= readMaybe + , uriMemo = case lookup "memo" queryParams of + Just m -> processEither $ B64.decode $ BC.pack m + _ -> "" + , uriLabel = lookup "label" queryParams + , uriMessage = lookup "message" queryParams + } + + \ No newline at end of file diff --git a/test/ServerSpec.hs b/test/ServerSpec.hs index 882b5e0..65fa87e 100644 --- a/test/ServerSpec.hs +++ b/test/ServerSpec.hs @@ -58,7 +58,8 @@ main = do zebraPort <- require config "zebraPort" zebraHost <- require config "zebraHost" nodePort <- require config "nodePort" - let myConfig = Config dbFilePath zebraHost zebraPort nodeUser nodePwd nodePort + currencyCode <- require config "currencyCode" + let myConfig = Config dbFilePath zebraHost zebraPort nodeUser nodePwd nodePort currencyCode hspec $ do describe "RPC methods" $ do beforeAll_ (startAPI myConfig) $ do diff --git a/test/Spec.hs b/test/Spec.hs index dfabe0c..3938dd8 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -1111,3 +1111,11 @@ main = do case price of Just p -> p `shouldNotBe` 0.0 Nothing -> assertFailure "Failed to get ZEC price" + describe "Parse an URI payment string" $ do + it ("Parsing URI -> " ++ "zcash:ztestsapling10yy2ex5....") $ do + let zcashURI2 = "zcash:ztestsapling10yy2ex5dcqkclhc7z7yrnjq2z6feyjad56ptwlfgmy77dmaqqrl9gyhprdx59qgmsnyfska2kez?amount=100&memo=SGVsbG8sIFdvcmxkIQ==&message=Test" + case parseZcashPayment zcashURI2 of + Right p -> do + print p + (uriAmount p) `shouldBe` Just 100.0 + Left e -> assertFailure $ "Error: " ++ e