Compare commits
3 commits
9ab31a6d9b
...
3da6a57d50
Author | SHA1 | Date | |
---|---|---|---|
3da6a57d50 | |||
149d74d4e2 | |||
de3bc48c38 |
4 changed files with 173 additions and 85 deletions
|
@ -33,7 +33,6 @@ import Database.Persist
|
||||||
import Lens.Micro ((&), (+~), (.~), (?~), (^.), set)
|
import Lens.Micro ((&), (+~), (.~), (?~), (^.), set)
|
||||||
import Lens.Micro.TH
|
import Lens.Micro.TH
|
||||||
import Monomer
|
import Monomer
|
||||||
|
|
||||||
import qualified Monomer.Lens as L
|
import qualified Monomer.Lens as L
|
||||||
import System.Directory (getHomeDirectory)
|
import System.Directory (getHomeDirectory)
|
||||||
import System.FilePath ((</>))
|
import System.FilePath ((</>))
|
||||||
|
@ -69,15 +68,16 @@ import Zenith.Types hiding (ZcashAddress(..))
|
||||||
import Zenith.Utils
|
import Zenith.Utils
|
||||||
( displayAmount
|
( displayAmount
|
||||||
, getChainTip
|
, getChainTip
|
||||||
, getZcashPrice
|
|
||||||
, isRecipientValidGUI
|
, isRecipientValidGUI
|
||||||
, isValidString
|
, isValidString
|
||||||
, isZecAddressValid
|
, isZecAddressValid
|
||||||
, jsonNumber
|
, jsonNumber
|
||||||
, padWithZero
|
, padWithZero
|
||||||
, parseZcashPayment
|
|
||||||
, showAddress
|
, showAddress
|
||||||
, validBarValue
|
, validBarValue
|
||||||
|
, parseZcashPayment
|
||||||
|
, getZcashPrice
|
||||||
|
, createZip321
|
||||||
)
|
)
|
||||||
|
|
||||||
data VkTypeDef
|
data VkTypeDef
|
||||||
|
@ -96,7 +96,6 @@ data AppEvent
|
||||||
| AccountClicked
|
| AccountClicked
|
||||||
| MenuClicked
|
| MenuClicked
|
||||||
| NewClicked
|
| NewClicked
|
||||||
| ViewingKeysClicked
|
|
||||||
| NewAddress !(Maybe (Entity ZcashAccount))
|
| NewAddress !(Maybe (Entity ZcashAccount))
|
||||||
| NewInternalAddress !(Maybe (Entity ZcashAccount))
|
| NewInternalAddress !(Maybe (Entity ZcashAccount))
|
||||||
| NewAccount !(Maybe (Entity ZcashWallet))
|
| NewAccount !(Maybe (Entity ZcashWallet))
|
||||||
|
@ -162,15 +161,21 @@ data AppEvent
|
||||||
| ShowFIATBalance
|
| ShowFIATBalance
|
||||||
| DisplayFIATBalance Double Double
|
| DisplayFIATBalance Double Double
|
||||||
| CloseFIATBalance
|
| CloseFIATBalance
|
||||||
|
| ViewingKeysClicked
|
||||||
| PrepareViewingKey !VkTypeDef !(Maybe (Entity ZcashAccount))
|
| PrepareViewingKey !VkTypeDef !(Maybe (Entity ZcashAccount))
|
||||||
| ShowViewingKey !VkTypeDef !T.Text
|
| ShowViewingKey !VkTypeDef !T.Text
|
||||||
| CopyViewingKey !T.Text !T.Text
|
| CopyViewingKey !T.Text !T.Text
|
||||||
| CloseShowVK
|
| CloseShowVK
|
||||||
| DisplayPaymentURI
|
| DisplayPaymentURIForm !T.Text
|
||||||
| ClosePaymentURI
|
| ClosePaymentURIForm
|
||||||
|
| PrepareURIString
|
||||||
|
| CloseShowURIOverlay
|
||||||
|
| ShowURIOverlay !T.Text
|
||||||
|
| CopyURIString !T.Text
|
||||||
| DisplayPayUsingURI
|
| DisplayPayUsingURI
|
||||||
| ClosePayUsingURI
|
| ClosePayUsingURI
|
||||||
| ProcIfValidURI
|
| ProcIfValidURI
|
||||||
|
| PreparePaymentURIForm
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
data AppModel = AppModel
|
data AppModel = AppModel
|
||||||
|
@ -238,8 +243,10 @@ data AppModel = AppModel
|
||||||
, _vkTypeName :: !T.Text
|
, _vkTypeName :: !T.Text
|
||||||
, _vkData :: !T.Text
|
, _vkData :: !T.Text
|
||||||
, _paymentURIDisplay :: !Bool
|
, _paymentURIDisplay :: !Bool
|
||||||
|
, _showURIDisplay :: !Bool
|
||||||
, _usepmtURIOverlay :: !Bool
|
, _usepmtURIOverlay :: !Bool
|
||||||
, _uriString :: !T.Text
|
, _uriString :: !T.Text
|
||||||
|
, _uriAddr :: !T.Text
|
||||||
} deriving (Eq, Show)
|
} deriving (Eq, Show)
|
||||||
|
|
||||||
makeLenses ''AppModel
|
makeLenses ''AppModel
|
||||||
|
@ -283,18 +290,19 @@ buildUI wenv model = widgetTree
|
||||||
, modalOverlay `nodeVisible` isJust (model ^. modalMsg)
|
, modalOverlay `nodeVisible` isJust (model ^. modalMsg)
|
||||||
, adrbookOverlay `nodeVisible` model ^. showAdrBook
|
, adrbookOverlay `nodeVisible` model ^. showAdrBook
|
||||||
, newAdrBkOverlay `nodeVisible` model ^. newAdrBkEntry
|
, newAdrBkOverlay `nodeVisible` model ^. newAdrBkEntry
|
||||||
, dfBalOverlay `nodeVisible` model ^. displayFIATBalance
|
|
||||||
, showABAddressOverlay (model ^. abdescrip) (model ^. abaddress) `nodeVisible`
|
, showABAddressOverlay (model ^. abdescrip) (model ^. abaddress) `nodeVisible`
|
||||||
model ^.
|
model ^.
|
||||||
showABAddress
|
showABAddress
|
||||||
, updateABAddressOverlay (model ^. abdescrip) (model ^. abaddress) `nodeVisible`
|
, updateABAddressOverlay (model ^. abdescrip) (model ^. abaddress) `nodeVisible`
|
||||||
model ^.
|
model ^.
|
||||||
updateABAddress
|
updateABAddress
|
||||||
, showVKOverlay `nodeVisible` model ^. viewingKeyDisplay
|
|
||||||
, paymentURIOverlay `nodeVisible` model ^. paymentURIDisplay
|
|
||||||
, pmtUsingURIOverlay `nodeVisible` model ^. usepmtURIOverlay
|
|
||||||
, shieldOverlay `nodeVisible` model ^. shieldZec
|
, shieldOverlay `nodeVisible` model ^. shieldZec
|
||||||
, deShieldOverlay `nodeVisible` model ^. deShieldZec
|
, deShieldOverlay `nodeVisible` model ^. deShieldZec
|
||||||
|
, dfBalOverlay `nodeVisible` model ^. displayFIATBalance
|
||||||
|
, showVKOverlay `nodeVisible` model ^. viewingKeyDisplay
|
||||||
|
, paymentURIOverlay `nodeVisible` model ^. paymentURIDisplay
|
||||||
|
, showURIOverlay `nodeVisible` model ^. showURIDisplay
|
||||||
|
, pmtUsingURIOverlay `nodeVisible` model ^. usepmtURIOverlay
|
||||||
, msgAdrBookOverlay `nodeVisible` isJust (model ^. msgAB)
|
, msgAdrBookOverlay `nodeVisible` isJust (model ^. msgAB)
|
||||||
]
|
]
|
||||||
mainWindow =
|
mainWindow =
|
||||||
|
@ -387,7 +395,7 @@ buildUI wenv model = widgetTree
|
||||||
("Balance in " <>
|
("Balance in " <>
|
||||||
T.toUpper (c_currencyCode (model ^. configuration)))) `styleBasic`
|
T.toUpper (c_currencyCode (model ^. configuration)))) `styleBasic`
|
||||||
[bgColor white, borderB 1 gray, padding 3]
|
[bgColor white, borderB 1 gray, padding 3]
|
||||||
, box_ [alignLeft, onClick DisplayPaymentURI] (label "Create URI") `styleBasic`
|
, box_ [alignLeft, onClick PreparePaymentURIForm] (label "Create URI") `styleBasic`
|
||||||
[bgColor white, borderB 1 gray, padding 3]
|
[bgColor white, borderB 1 gray, padding 3]
|
||||||
, box_
|
, box_
|
||||||
[alignLeft, onClick DisplayPayUsingURI]
|
[alignLeft, onClick DisplayPayUsingURI]
|
||||||
|
@ -1241,66 +1249,68 @@ buildUI wenv model = widgetTree
|
||||||
, filler
|
, filler
|
||||||
]) `styleBasic`
|
]) `styleBasic`
|
||||||
[bgColor (white & L.a .~ 0.5)]
|
[bgColor (white & L.a .~ 0.5)]
|
||||||
|
--
|
||||||
paymentURIOverlay =
|
paymentURIOverlay =
|
||||||
box
|
alert ClosePaymentURIForm $
|
||||||
(vstack
|
vstack
|
||||||
[ filler
|
|
||||||
, hstack
|
|
||||||
[ filler
|
|
||||||
, box_
|
|
||||||
[]
|
|
||||||
(vstack
|
|
||||||
[ box_
|
[ box_
|
||||||
[alignMiddle]
|
[]
|
||||||
(label "Create URI" `styleBasic`
|
(label "Create a Payment URI" `styleBasic`
|
||||||
[textColor white, textFont "Bold", textSize 12]) `styleBasic`
|
[textFont "Bold", textColor white, textSize 10, padding 3]) `styleBasic`
|
||||||
[bgColor btnColor]
|
[bgColor btnColor, radius 2, padding 3]
|
||||||
, separatorLine `styleBasic` [fgColor btnColor]
|
|
||||||
, spacer
|
, spacer
|
||||||
, hstack
|
, hstack
|
||||||
[ label "Amount:" `styleBasic`
|
[ filler
|
||||||
[width 50, textFont "Bold"]
|
, label "Current Address:" `styleBasic` [textFont "Bold"]
|
||||||
, spacer
|
, spacer
|
||||||
, numericField_
|
, label_ (txtWrapN (model ^. uriAddr) 64) [multiline]
|
||||||
sendAmount
|
, filler
|
||||||
[ decimals 8
|
]
|
||||||
, minValue 0.0
|
, spacer
|
||||||
, maxValue
|
, hstack
|
||||||
(fromIntegral (model ^. balance) / 100000000.0)
|
[ label "Amount : " `styleBasic` [textFont "Bold"]
|
||||||
, validInput amountValid
|
, numericField_ sendAmount
|
||||||
, onChange CheckAmount
|
[ decimals 8 ]
|
||||||
] `styleBasic`
|
`nodeKey` "floatInput"
|
||||||
|
`styleBasic`
|
||||||
[ width 150
|
[ width 150
|
||||||
, styleIf
|
, styleIf (model ^. sendAmount <= 0.0) (textColor red)
|
||||||
(not $ model ^. amountValid)
|
|
||||||
(textColor red)
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
, spacer
|
||||||
, hstack
|
, hstack
|
||||||
[ label "Memo:" `styleBasic`
|
[ label "Memo: " `styleBasic` [textFont "Bold"]
|
||||||
[width 50, textFont "Bold"]
|
|
||||||
, spacer
|
, spacer
|
||||||
, textArea sendMemo `styleBasic`
|
, textField_ sendMemo [] `styleBasic` [width 300]
|
||||||
[width 150, height 40]
|
|
||||||
]
|
]
|
||||||
, spacer
|
, spacer
|
||||||
, box_
|
, hstack
|
||||||
[alignMiddle]
|
[ filler
|
||||||
(hstack
|
, mainButton "Create URI" PrepareURIString `nodeEnabled`
|
||||||
[ spacer
|
(model ^. sendAmount > 0.0)
|
||||||
, mainButton "Create URI" NotImplemented `nodeEnabled`
|
|
||||||
True
|
|
||||||
, spacer
|
|
||||||
, button "Cancel" ClosePaymentURI
|
|
||||||
, 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
|
, filler
|
||||||
]) `styleBasic`
|
]
|
||||||
[bgColor (white & L.a .~ 0.5)]
|
]
|
||||||
pmtUsingURIOverlay =
|
pmtUsingURIOverlay =
|
||||||
box
|
box
|
||||||
(vstack
|
(vstack
|
||||||
|
@ -1694,6 +1704,7 @@ handleEvent wenv node model evt =
|
||||||
model & amountValid .~
|
model & amountValid .~
|
||||||
(i < (fromIntegral (model ^. balance) / 100000000.0))
|
(i < (fromIntegral (model ^. balance) / 100000000.0))
|
||||||
]
|
]
|
||||||
|
--
|
||||||
ShowTxId tx -> [Model $ model & showId ?~ tx & modalMsg .~ Nothing]
|
ShowTxId tx -> [Model $ model & showId ?~ tx & modalMsg .~ Nothing]
|
||||||
-- |
|
-- |
|
||||||
-- | Address Book Events
|
-- | Address Book Events
|
||||||
|
@ -1752,6 +1763,11 @@ handleEvent wenv node model evt =
|
||||||
, setClipboardData $ ClipboardText v
|
, setClipboardData $ ClipboardText v
|
||||||
, Event $ ShowMessage (t <> " viewing key copied!!")
|
, Event $ ShowMessage (t <> " viewing key copied!!")
|
||||||
]
|
]
|
||||||
|
CopyURIString u ->
|
||||||
|
[ setClipboardData ClipboardEmpty
|
||||||
|
, setClipboardData $ ClipboardText u
|
||||||
|
, Event $ ShowMessage "URI string copied to clipboard!!"
|
||||||
|
]
|
||||||
DeleteABEntry a ->
|
DeleteABEntry a ->
|
||||||
[ Task $ deleteAdrBook (model ^. configuration) a
|
[ Task $ deleteAdrBook (model ^. configuration) a
|
||||||
, Model $
|
, Model $
|
||||||
|
@ -1814,11 +1830,31 @@ handleEvent wenv node model evt =
|
||||||
--
|
--
|
||||||
-- Display PaymentURI Form
|
-- Display PaymentURI Form
|
||||||
--
|
--
|
||||||
DisplayPaymentURI ->
|
PreparePaymentURIForm ->
|
||||||
|
[ Task $ getCurrentAddress currentAddress ]
|
||||||
|
--
|
||||||
|
DisplayPaymentURIForm ua->
|
||||||
[ Model $
|
[ Model $
|
||||||
model & paymentURIDisplay .~ True & uriString .~ "" & menuPopup .~ False
|
model & uriString .~ ""
|
||||||
|
& uriAddr .~ ua
|
||||||
|
& amountValid .~ False
|
||||||
|
& sendAmount .~ 0.0
|
||||||
|
& sendMemo .~ ""
|
||||||
|
& paymentURIDisplay .~ True
|
||||||
|
& menuPopup .~ False
|
||||||
]
|
]
|
||||||
ClosePaymentURI -> [Model $ model & paymentURIDisplay .~ False]
|
ClosePaymentURIForm -> [Model $ model & paymentURIDisplay .~ False]
|
||||||
|
--
|
||||||
|
-- Generate URI
|
||||||
|
--
|
||||||
|
PrepareURIString -> [ Task $ genURIString (model ^. uriAddr) (model ^. sendAmount) (model ^. sendMemo) ]
|
||||||
|
ShowURIOverlay uStr ->
|
||||||
|
[ Model $
|
||||||
|
model & uriString .~ uStr
|
||||||
|
& paymentURIDisplay .~ False
|
||||||
|
& showURIDisplay .~ True
|
||||||
|
]
|
||||||
|
CloseShowURIOverlay -> [ Model $ model & showURIDisplay .~ False & uriString .~ "" ]
|
||||||
--
|
--
|
||||||
-- Display Pay using URI Form
|
-- Display Pay using URI Form
|
||||||
--
|
--
|
||||||
|
@ -1841,9 +1877,8 @@ handleEvent wenv node model evt =
|
||||||
T.pack (uriAddress p) &
|
T.pack (uriAddress p) &
|
||||||
sendAmount .~
|
sendAmount .~
|
||||||
realToFrac a &
|
realToFrac a &
|
||||||
sendMemo .~
|
sendMemo .~ (uriMemo p)
|
||||||
(uriMemo p)
|
, Event $ ClosePaymentURIForm
|
||||||
, Event $ ClosePaymentURI
|
|
||||||
]
|
]
|
||||||
Nothing ->
|
Nothing ->
|
||||||
[ Model $
|
[ Model $
|
||||||
|
@ -2062,6 +2097,28 @@ handleEvent wenv node model evt =
|
||||||
let tsk = getTranSK $ zcashAccountTPrivateKey $ entityVal acc
|
let tsk = getTranSK $ zcashAccountTPrivateKey $ entityVal acc
|
||||||
ivk <- deriveUivk n osk ssk tsk
|
ivk <- deriveUivk n osk ssk tsk
|
||||||
return $ ShowViewingKey VkIncoming ivk
|
return $ ShowViewingKey VkIncoming ivk
|
||||||
|
--
|
||||||
|
-- Get curret zcash address
|
||||||
|
--
|
||||||
|
getCurrentAddress :: Maybe (Entity WalletAddress) -> IO AppEvent
|
||||||
|
getCurrentAddress a = do
|
||||||
|
let ua = case model ^. selPool of
|
||||||
|
OrchardPool -> maybe "None" (getUA . walletAddressUAddress . entityVal) a
|
||||||
|
SaplingPool -> fromMaybe "None" $ (getSaplingFromUA . E.encodeUtf8 . getUA . walletAddressUAddress . entityVal) =<< a
|
||||||
|
SproutPool -> "None"
|
||||||
|
TransparentPool -> maybe "None" (encodeTransparentReceiver (model ^. network)) $
|
||||||
|
t_rec =<< (isValidUnifiedAddress . E.encodeUtf8 . getUA . walletAddressUAddress . entityVal) =<< a
|
||||||
|
return $ DisplayPaymentURIForm ua
|
||||||
|
--
|
||||||
|
-- Gen URI String
|
||||||
|
--
|
||||||
|
genURIString :: T.Text -> Float -> T.Text -> IO AppEvent
|
||||||
|
genURIString addr mAmt mMemo = do
|
||||||
|
let mM = case mMemo of
|
||||||
|
"" -> Nothing
|
||||||
|
_ -> Just (T.unpack mMemo)
|
||||||
|
let uriSt = createZip321 (T.unpack addr) (Just (realToFrac mAmt)) mM
|
||||||
|
return $ ShowURIOverlay (T.pack uriSt)
|
||||||
|
|
||||||
scanZebra ::
|
scanZebra ::
|
||||||
T.Text
|
T.Text
|
||||||
|
@ -2415,6 +2472,8 @@ runZenithGUI config = do
|
||||||
""
|
""
|
||||||
False
|
False
|
||||||
False
|
False
|
||||||
|
False
|
||||||
|
""
|
||||||
""
|
""
|
||||||
startApp model handleEvent buildUI (params hD)
|
startApp model handleEvent buildUI (params hD)
|
||||||
Left _e -> print "Zebra not available"
|
Left _e -> print "Zebra not available"
|
||||||
|
|
|
@ -22,6 +22,7 @@ import qualified Data.Text as T
|
||||||
import qualified Data.Text.Encoding as E
|
import qualified Data.Text.Encoding as E
|
||||||
import qualified Data.Text.Encoding as TE
|
import qualified Data.Text.Encoding as TE
|
||||||
import Network.HTTP.Simple
|
import Network.HTTP.Simple
|
||||||
|
import Network.URI (escapeURIString, isUnreserved)
|
||||||
import System.Directory
|
import System.Directory
|
||||||
import System.Process (createProcess_, shell)
|
import System.Process (createProcess_, shell)
|
||||||
import Text.Printf (printf)
|
import Text.Printf (printf)
|
||||||
|
@ -334,3 +335,22 @@ padBase64 bs = bs <> BC.replicate paddingLength '='
|
||||||
-- Function to decode a base64 un-padded string
|
-- Function to decode a base64 un-padded string
|
||||||
decodeBase64Unpadded :: BC.ByteString -> Either String BC.ByteString
|
decodeBase64Unpadded :: BC.ByteString -> Either String BC.ByteString
|
||||||
decodeBase64Unpadded = B64.decode . padBase64
|
decodeBase64Unpadded = B64.decode . padBase64
|
||||||
|
|
||||||
|
-- Function to encode memo as un-padded Base64
|
||||||
|
encodeBase64Memo :: String -> String
|
||||||
|
encodeBase64Memo = BC.unpack . BC.takeWhile (/= '=') . B64.encode . BC.pack
|
||||||
|
|
||||||
|
-- Function to drop trailing zeros
|
||||||
|
dropTrailingZeros :: String -> String
|
||||||
|
dropTrailingZeros str =
|
||||||
|
let withoutZeros = reverse (dropWhile (== '0') (reverse str))
|
||||||
|
in if last withoutZeros == '.'
|
||||||
|
then withoutZeros ++ "0" -- Ensure at least one decimal place
|
||||||
|
else withoutZeros
|
||||||
|
|
||||||
|
-- Function to create a ZIP-321 URI
|
||||||
|
createZip321 :: String -> Maybe Double -> Maybe String -> String
|
||||||
|
createZip321 address mAmount mMemo =
|
||||||
|
"zcash:" ++ address
|
||||||
|
++ maybe "" (\amount -> "?amount=" ++ dropTrailingZeros (printf "%.8f" amount) ) mAmount
|
||||||
|
++ maybe "" (\memo -> "&memo=" ++ escapeURIString isUnreserved (encodeBase64Memo memo)) mMemo
|
||||||
|
|
|
@ -1138,3 +1138,11 @@ main = do
|
||||||
print p
|
print p
|
||||||
(uriAmount p) `shouldBe` Just 100.0
|
(uriAmount p) `shouldBe` Just 100.0
|
||||||
Left e -> assertFailure $ "Error: " ++ e
|
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"
|
||||||
|
|
|
@ -97,6 +97,7 @@ library
|
||||||
, word-wrap
|
, word-wrap
|
||||||
, zcash-haskell
|
, zcash-haskell
|
||||||
, unordered-containers
|
, unordered-containers
|
||||||
|
, network-uri
|
||||||
--pkgconfig-depends: rustzcash_wrapper
|
--pkgconfig-depends: rustzcash_wrapper
|
||||||
default-language: Haskell2010
|
default-language: Haskell2010
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue