diff --git a/.gitignore b/.gitignore index 6fbacef..425e2d8 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ .project Setup.hs dist/ +dist-newstyle/ cabal-dev/ network-bitcoin-tests cabal.sandbox.config diff --git a/.hgignore b/.hgignore index b2cea56..c499dd6 100644 --- a/.hgignore +++ b/.hgignore @@ -1,6 +1,7 @@ syntax: regexp \#.*\#$ \.\# +\.sw[op] \.DS_Store$ \.cabal-sandbox/ \.vagrant/ @@ -10,6 +11,7 @@ cabal\.sandbox\.config$ \.shake\.database$ \.shake/ dist/ +dist-newstyle/ \.tfstate\.backup$ \.tfstate\.blank$ \.terraform/ diff --git a/README.md b/README.md index 4afddf2..f30b2de 100644 --- a/README.md +++ b/README.md @@ -1 +1,13 @@ -See the [Hackage documentation](http://hackage.haskell.org/package/network-bitcoin) +network-bitcoin +==== + +See the [Hackage documentation](http://hackage.haskell.org/package/network-bitcoin). + +Testing +---- + +The tests expect to run against a `bitcoind` node synced to testnet. Invoke `bitcoind` with: + +```shell +$ bitcoind -rpcuser -rpcpassword -testnet +``` diff --git a/src/Network/Bitcoin/BlockChain.hs b/src/Network/Bitcoin/BlockChain.hs index 756468d..6356d74 100644 --- a/src/Network/Bitcoin/BlockChain.hs +++ b/src/Network/Bitcoin/BlockChain.hs @@ -14,6 +14,7 @@ module Network.Bitcoin.BlockChain ( Client , setTransactionFee , getRawMemoryPool , BlockHash + , BlockHeight , getBlockHash , Block(..) , getBlock diff --git a/src/Network/Bitcoin/Wallet.hs b/src/Network/Bitcoin/Wallet.hs index cc93db6..5bd015f 100644 --- a/src/Network/Bitcoin/Wallet.hs +++ b/src/Network/Bitcoin/Wallet.hs @@ -35,6 +35,8 @@ module Network.Bitcoin.Wallet ( Client , moveBitcoins , sendFromAccount , sendMany + , EstimationMode (..) + , estimateSmartFee -- , createMultiSig , ReceivedByAddress(..) , listReceivedByAddress @@ -63,14 +65,20 @@ module Network.Bitcoin.Wallet ( Client , isAddressValid ) where +import Control.Applicative (liftA2) import Control.Monad import Data.Aeson as A +import Data.Aeson.Types (parseEither) +import Data.Bifunctor (first) +import Data.Bool (bool) import qualified Data.HashMap.Lazy as HM +import qualified Data.List as List import Data.Maybe import Data.Text import Data.Time.Clock.POSIX import Data.Vector as V hiding ((++)) -import Network.Bitcoin.BlockChain (BlockHash) +import Data.Word +import Network.Bitcoin.BlockChain (BlockHash, BlockHeight) import Network.Bitcoin.Internal import Network.Bitcoin.RawTransaction (RawTransaction) @@ -745,3 +753,28 @@ instance FromJSON IsValid where -- | Checks if a given address is a valid one. isAddressValid :: Client -> Address -> IO Bool isAddressValid client addr = getValid <$> callApi client "validateaddress" [ tj addr ] + + +-- | Possible fee estimation modes +data EstimationMode + = Economical + | Conservative + deriving Eq + + +instance ToJSON EstimationMode where + toJSON Economical = toJSON ("ECONOMICAL" :: String) + toJSON Conservative = toJSON ("CONSERVATIVE" :: String) + + +-- | Estimate the fee per kb to send a transaction +estimateSmartFee :: Client -> Word32 -> Maybe EstimationMode -> IO (Either [String] (Double, BlockHeight)) +estimateSmartFee client target mode = + parse <$> callApi client "estimatesmartfee" (catMaybes [ Just $ tj target, tj <$> mode ]) + where + parse = join . first pure . parseEither parseResp + parseResp = withObject "estimatesmartfee response" $ \obj -> do + merrs <- obj .:? "errors" + flip (maybe (parseVals obj)) merrs $ \errs -> + bool (pure $ Left errs) (parseVals obj) . List.null $ errs + parseVals = fmap Right . (liftA2 (,) <$> (.: "feerate") <*> (.: "blocks"))