easy-bitcoin-0.0.0.2: Network/EasyBitcoin.hs
{-|
Module : EasyBitcoin
Description : Libray to parse and compose bitcoin transactions, keys, addresses and escrows.
Copyright : (c) Alejandro Durán Pallarés, 2015
License : BSD3
Maintainer : vwwv@correo.ugr.es
Stability : experimental
EasyBitcoin is a simple haskell library providing types and class-instances for bitcoin related code;
it also include an small set of functions to handle addresses, transactions and escrows.
Some small examples can be found <https://github.com/vwwv/easy-bitcoin/tree/master/Examples here> .
-}
{-# LANGUAGE DataKinds, GADTs #-}
module Network.EasyBitcoin( -- * Usage Example:
-- $example
-- * Addresses:
Address()
, Addressable(..)
, isPay2SH
, isPay2PKH
, addressFromUncompressed
-- * Keys:
, Key()
, Visibility(..)
, derive
, derivePublic
, deriveHardened
, deriveRoot
, showAsCompressedSingletonKey
, showAsUncompressedSingletonKey
, serializeCompressedSingleton
, serializeUncompressedSingleton
, (===)
-- * Transactions:
, Outpoint (..)
, Txid()
, txid
, Tx ()
, transaction
, unsignedTransaction
, txOutputs
, txInputs
-- * Escrows and Signatures:
, RedeemScript(..)
, ScriptSig()
, SigHash(..)
, TxSignature()
, sigHashType
, signTxAt
, scriptSig
, escrowSignatures
, escrowSignaturesFor
, simpleSignature
, checkInput
, checkSignatureAt
, createSignature
, createSignatureAs
, BTC()
, btc
, mBTC
, satoshis
, asBtc
, asMbtc
, asSatoshis
, showAsBtc
, showAsMbtc
, showAsSatoshis
-- * Network Parameters:
, ProdNet
, TestNet
, BlockNetwork(..)
, Params(..)
) where
import Network.EasyBitcoin.Address ( Address
, Addressable(..)
, isPay2SH
, isPay2PKH
, addressFromUncompressed
)
import Network.EasyBitcoin.Script ( RedeemScript(..)
)
import Network.EasyBitcoin.NetworkParams( ProdNet
, TestNet
, BlockNetwork(..)
, Params(..)
)
import Network.EasyBitcoin.Internal.Transaction
( Outpoint (..)
, Txid
, txid
, Tx()
, sigHashType
)
import Network.EasyBitcoin.Transaction ( transaction
, txOutputs
, txInputs
, unsignedTransaction
, ScriptSig()
, TxSignature()
, signTxAt
, scriptSig
, escrowSignatures
, escrowSignaturesFor
, simpleSignature
, checkInput
, checkSignatureAt
, createSignature
, createSignatureAs
, SigHash(..)
)
import Network.EasyBitcoin.Keys ( Key()
, Visibility(..)
, derive
, derivePublic
, deriveHardened
, deriveRoot
, (===)
, showAsCompressedSingletonKey
, showAsUncompressedSingletonKey
, serializeCompressedSingleton
, serializeUncompressedSingleton
)
import Network.EasyBitcoin.BitcoinUnits( BTC()
, btc
, mBTC
, satoshis
, asBtc
, asMbtc
, asSatoshis
, showAsBtc
, showAsMbtc
, showAsSatoshis
)
{- $example
As a toy example, let's imagine the following scenario:
* On a blog, there's a donation address @mm8LjcoUYdPNKgWshGs7dueFu33aK56ckb@ (private key @__91vaDsoxZACAZeGM89Y7dBnbTB7wrvtBeEkMTpL2sCgEtHf4RBn__@).
* The blog is written by blogger A, who wrote __70%__ of the posts, and blogger B, who wrote the remaining __30%__.
* They want split the donation proportionally to the number of posts they have written.
* Blogger A has as personal address @__miHWju2dzq9RcUPESzYBgVWa3W3swTXtLo__@ .
* Blogger B has as personal address @__mvsXpubWQSw2dK2L85iYFppnNjGm439aWK__@ .
As this is an example, we won't use real bitcoin, but testnet bitcoin, also, we'll use Coinbase's
public bitcoin client, Toshi, so we don't have to install anything in our computer:
@
{-# LANGUAGE DataKinds, OverloadedStrings #-}
import Network.EasyBitcoin
import Control.Monad(forever)
import Network.HTTP.Client(HttpException(StatusCodeException))
import Network.Wreq(get,post,statusCode,responseBody)
import Control.Exception(handleJust)
import Control.Lens
import Data.Aeson.Lens
import Data.Aeson
import Safe
import Control.Applicative
import Control.Monad
import Control.Concurrent
----------------------------------------------------------------------------------------------
incoming = read "91vaDsoxZACAZeGM89Y7dBnbTB7wrvtBeEkMTpL2sCgEtHf4RBn" :: Key Private TestNet
outgoingA = read "miHWju2dzq9RcUPESzYBgVWa3W3swTXtLo" :: Address TestNet
outgoingB = read "mvsXpubWQSw2dK2L85iYFppnNjGm439aWK" :: Address TestNet
----------------------------------------------------------------------------------------------
fee = btc 0.0001 -- the miner fee to use.
threshold = btc 0.2 -- won't send any transaction till reach this amount. This is important
-- to avoid creating "dust" transactions.
server = "https://testnet3.toshi.io/" -- The Coinbase Toshi client testnet url.
secondsToPool = 20
----------------------------------------------------------------------------------------------
-- General workflow:
-- each 20 seconds:
-- - read from Toshi all unspent outpoints.
-- - if not enough funds holds on the unspent outpoints:
-- continue next iteration.
-- - else:
-- combining all available outpoints into a transaction to miHWju2dzq9RcUPESzYBgVWa3W3swTXtLo and mvsXpubWQSw2dK2L85iYFppnNjGm439aWK
-- send this transaction to Toshi to be broad-casted into the network.
main::IO ()
main = do putStrLn $ "Rebrodcasting from " ++ show (address incoming) ++ " to "++ show outgoingA ++ " and " ++ show outgoingB
forever $ do readToshi >>= maybe (return ()) sendToshi . createTransaction
threadDelay (secondsToPool*1000000)
-- If not enough funds, returns Nothing, otherwise, returns the transaction to be sent.
createTransaction :: [(Outpoint, BTC TestNet)] -> Maybe (Tx TestNet)
createTransaction inputs = if amount > threshold then Just txToSend
else Nothing
where
amount = sum (fmap snd inputs) - fee
amountToA = btc (asBtc amount * 0.7)
amountToB = amount - amountToA
txToSend = transaction [ (outpoint,incoming) | (outpoint, _ ) <- inputs]
(outgoingA,amountToA) [(outgoingB,amountToB)]
sendToshi :: Tx TestNet -> IO ()
sendToshi tx = do putStrLn $ "Sending tx: " ++ show (txid tx)
post (server ++ "/api/v0/transactions") (toJSON$show tx)
return ()
-- Querying and parsing the Toshi client about the unspent_outputs holds on the address defined by the private key
-- 91vaDsoxZACAZeGM89Y7dBnbTB7wrvtBeEkMTpL2sCgEtHf4RBn (that is mm8LjcoUYdPNKgWshGs7dueFu33aK56ckb).
readToshi :: IO [(Outpoint, BTC TestNet)]
readToshi = handleJust isNotFound (const$ return [])
$ do body <- get $ server ++ "api/v0/addresses/"++ show (address incoming) ++ "/unspent_outputs"
return $ body ^.. responseBody . values . to parseOutpoint . _Just
where
-- Toshi returns 404 if the address has never received any tx
isNotFound ex = case ex of
StatusCodeException s _ _
| s ^. statusCode == 404 -> Just ()
_ -> Nothing
parseOutpoint val = do vout <- (val ^? key "output_index" ._JSON)
txid <- (val ^? key "transaction_hash" ._JSON. to readMay._Just)
amount <- (val ^? key "amount" ._JSON. to satoshis)
Just (Outpoint txid vout, amount)
@
-}