{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# OPTIONS_GHC -Wall #-} -- | An interface to bitcoind's available raw transaction-related RPC calls. -- The implementation of these functions can be found at -- . -- -- If any APIs are missing, patches are always welcome. If you look at the -- source of this module, you'll see that the interface code is trivial. -- -- Also, documentation for this module is scarce. I would love the addition -- of more documentation by anyone who knows what these things are. module Network.Bitcoin.RawTransaction ( Client , getClient , RawTransaction , getRawTransaction , TxIn(..) , TxnOutputType(..) , ScriptPubKey(..) , ScriptSig(..) , TxOut(..) , BlockInfo(..) , RawTransactionInfo(..) , getRawTransactionInfo , UnspentTransaction(..) , listUnspent , createRawTransaction , DecodedRawTransaction(..) , decodeRawTransaction , WhoCanPay(..) , RawSignedTransaction(..) , signRawTransaction , sendRawTransaction ) where import Control.Applicative import Control.Monad import Data.Aeson as A import Data.Aeson.Types as AT import Data.Maybe import qualified Data.Vector as V import Network.Bitcoin.Internal -- | Just like most binary data retrieved from bitcoind, a raw transaction is -- represented by a hexstring. -- -- This is a serialized, hex-encoded transaction. type RawTransaction = HexString -- | Get a raw transaction from its unique ID. getRawTransaction :: Client -> TransactionID -> IO RawTransaction getRawTransaction client txid = callApi client "getrawtransaction" [ tj txid, tj verbose ] where verbose = 0 :: Int -- | A transaction into an account. This can either be a coinbase transaction, -- or a standard transaction with another account. data TxIn = TxCoinbase { txCoinbase :: HexString } | TxIn { -- | This transaction's ID. txInId :: TransactionID , numOut :: Integer , scriptSig :: ScriptSig -- | A transaction sequence number. , txSequence :: Integer } deriving ( Show, Read, Ord, Eq ) instance FromJSON TxIn where parseJSON (Object o) = parseCB <|> parseTxIn where parseCB = TxCoinbase <$> o .: "coinbase" parseTxIn = TxIn <$> o .: "txid" <*> o .: "vout" <*> o .: "scriptSig" <*> o .: "sequence" parseJSON _ = mzero -- | The type of a transaction out. -- -- More documentation is needed here. Submit a patch if you know what this is -- about! data TxnOutputType = TxnPubKey -- ^ JSON of "pubkey" received. | TxnPubKeyHash -- ^ JSON of "pubkeyhash" received. | TxnScriptHash -- ^ JSON of "scripthash" received. | TxnMultisig -- ^ JSON of "multisig" received. deriving ( Show, Read, Ord, Eq ) instance FromJSON TxnOutputType where parseJSON (A.String s) | s == "pubkey" = return TxnPubKey | s == "pubkeyhash" = return TxnPubKeyHash | s == "scripthash" = return TxnScriptHash | s == "multisig" = return TxnMultisig | otherwise = mzero parseJSON _ = mzero -- | A transaction out of an account. data TxOut = TxOut { -- | The amount of bitcoin transferred out. txoutVal :: BTC -- | The public key of the account we sent the money to. , scriptPubKey :: ScriptPubKey } deriving ( Show, Read, Ord, Eq ) instance FromJSON TxOut where parseJSON (Object o) = TxOut <$> o .: "value" <*> o .: "scriptPubKey" parseJSON _ = mzero -- * Scripts -- A script is a complex bitcoin construct that provides the creation -- of Contracts. -- See and . -- It consists of two parts - a public key and a signature. -- | A public key of someone we sent money to. data ScriptPubKey = NonStandardScriptPubKey { -- | The JSON "asm" field. nspkAsm :: HexString -- | The JSON "hex" field. , nspkHex :: HexString } | StandardScriptPubKey { -- | The JSON "asm" field. sspkAsm :: HexString -- | The JSON "hex" field. , sspkHex :: HexString -- | The number of required signatures. , requiredSigs :: Integer -- | The type of the transaction. , sspkType :: TxnOutputType -- | The addresses associated with this key. , sspkAddresses :: Vector Address } deriving ( Show, Read, Ord, Eq ) instance FromJSON ScriptPubKey where parseJSON (Object o) = parseStandard <|> parseNonstandard where parseStandard = StandardScriptPubKey <$> o .: "asm" <*> o .: "hex" <*> o .: "reqSigs" <*> o .: "type" <*> o .: "addresses" parseNonstandard = NonStandardScriptPubKey <$> o .: "asm" <*> o .: "hex" parseJSON _ = mzero -- | A script signature. data ScriptSig = ScriptSig { sigAsm :: HexString , sigHex :: HexString } deriving ( Show, Read, Ord, Eq ) instance FromJSON ScriptSig where parseJSON (Object o) = ScriptSig <$> o .: "asm" <*> o .: "hex" parseJSON _ = mzero -- | Information on a single block. data BlockInfo = ConfirmedBlock { -- | The number of confirmations a block has. -- This will always be >= 1. confirmations :: Integer -- The JSON "time" field". , cbTime :: Integer -- | The JSON "blocktime" field. , blockTime :: Integer } | UnconfirmedBlock -- ^ An unconfirmed block is boring, but a possibility. deriving ( Show, Read, Ord, Eq ) instance FromJSON BlockInfo where parseJSON (Object o) = parseConfirmed <|> parseUnconfirmed where parseConfirmed = ConfirmedBlock <$> o .: "confirmations" <*> o .: "time" <*> o .: "blocktime" parseUnconfirmed = do c <- o .: "confirmations" :: AT.Parser Integer guard $ c == 0 return UnconfirmedBlock parseJSON _ = mzero -- | The raw transaction info for a given transaction ID. data RawTransactionInfo = RawTransactionInfo { -- | The raw transaction. raw :: RawTransaction -- | The transaction version number. , txnVersion :: Integer , txnLockTime :: Integer -- | The vector of transactions in. , vin :: Vector TxIn -- | The vector of transactions out. , vout :: Vector TxOut -- | The hash of the block that was used for this -- transaction. , rawTxBlockHash :: HexString -- | The transaction's block's info. , rawBlockInfo :: BlockInfo } deriving ( Show, Read, Ord, Eq ) instance FromJSON RawTransactionInfo where parseJSON v@(Object o) = RawTransactionInfo <$> o .: "hex" <*> o .: "version" <*> o .: "locktime" <*> o .: "vin" <*> o .: "vout" <*> o .: "blockhash" <*> parseJSON v parseJSON _ = mzero -- | Get raw transaction info for a given transaction ID. The data structure -- returned is quite sprawling and undocumented, so any patches to help -- simplify things would be greatly appreciated. getRawTransactionInfo :: Client -> TransactionID -> IO RawTransactionInfo getRawTransactionInfo client txid = callApi client "getrawtransaction" [ tj txid, tj verbose ] where verbose = 1 :: Int data UnspentTransaction = UnspentTransaction { unspentTransactionId :: TransactionID , outIdx :: Integer , unspentAddress :: Address , unspentScriptPubKey :: HexString , redeemScript :: Maybe HexString , unspentAmount :: BTC , usConfirmations :: Integer } deriving ( Show, Eq ) instance FromJSON UnspentTransaction where parseJSON (Object o) = UnspentTransaction <$> o .: "txid" <*> o .: "vout" <*> o .: "address" <*> o .: "scriptPubKey" <*> o .:? "redeemScript" <*> o .: "amount" <*> o .: "confirmations" parseJSON _ = mzero -- Instance used in 'createRawTransaction'. instance ToJSON UnspentTransaction where toJSON (UnspentTransaction{..}) = object [ "txid" .= unspentTransactionId , "vout" .= outIdx ] -- | Returns an array of unspent transaction outputs with between minconf and -- maxconf (inclusive) confirmations. If addresses are given, the result will -- be filtered to include only those addresses. listUnspent :: Client -> Maybe Int -- ^ minconf. Defaults to 1 if 'Nothing'. -> Maybe Int -- ^ maxconf. Defaults to 9999999 if 'Nothing'. -> Vector Address -- ^ Use 'Data.Vector.empty' for no filtering. -> IO (Vector UnspentTransaction) listUnspent client mmin mmax vaddrs = let min' = fromMaybe 1 mmin max' = fromMaybe 9999999 mmax in callApi client "listunspent" [ tj min', tj max', tj vaddrs ] -- | Create a transaction spending given inputs, sending to given addresses. -- -- Note that the transaction's inputs are not signed, and it is not stored -- in the wallet or transmitted to the network. -- -- Also, there is no checking to see if it's possible to send that much to -- the targets specified. In the future, such a scenario might throw an -- exception. createRawTransaction :: Client -> Vector UnspentTransaction -- ^ The unspent transactions we'll be using as our output. -> Vector (Address, BTC) -- ^ The addresses we're sending money to, along with how -- much each of them gets. -> IO HexString createRawTransaction client us tgts = callApi client "createrawtransaction" [ tj us, tj $ AA tgts ] -- | A successfully decoded raw transaction, from a given serialized, -- hex-encoded transaction. data DecodedRawTransaction = DecodedRawTransaction { -- | The raw transaction. decRaw :: RawTransaction -- | The transaction version number. , decTxnVersion :: Integer , decTxnLockTime :: Integer -- | The vector of transactions in. , decVin :: Vector TxIn -- | The vector of transactions out. , decVout :: Vector TxOut } instance FromJSON DecodedRawTransaction where parseJSON (Object o) = DecodedRawTransaction <$> o .: "hex" <*> o .: "version" <*> o .: "locktime" <*> o .: "vin" <*> o .: "vout" parseJSON _ = mzero -- | Decodes a raw transaction into a more accessible data structure. decodeRawTransaction :: Client -> RawTransaction -> IO DecodedRawTransaction decodeRawTransaction client tx = callApi client "decoderawtransaction" [ tj tx ] -- | Used internally to give a new 'ToJSON' instance for 'UnspentTransaction'. newtype UnspentForSigning = UFS UnspentTransaction instance ToJSON UnspentForSigning where toJSON (UFS (UnspentTransaction{..})) | isNothing redeemScript = object [ "txid" .= unspentTransactionId , "vout" .= outIdx , "scriptPubKey" .= unspentScriptPubKey ] | otherwise = object [ "txid" .= unspentTransactionId , "vout" .= outIdx , "scriptPubKey" .= unspentScriptPubKey , "redeemScript" .= fromJust redeemScript ] -- | Who can pay for a given transaction. data WhoCanPay = All | AllOrAnyoneCanPay | None | NoneOrAnyoneCanPay | Single | SingleOrAnyoneCanPay toString :: WhoCanPay -> Text toString All = "ALL" toString AllOrAnyoneCanPay = "ALL|ANYONECANPAY" toString None = "NONE" toString NoneOrAnyoneCanPay = "NONE|ANYONECANPAY" toString Single = "SINGLE" toString SingleOrAnyoneCanPay = "SINGLE|ANYONECANPAY" -- | A raw signed transaction contains the raw, signed hexstring and whether or -- not this transaction has a complete signature set. data RawSignedTransaction = RawSignedTransaction { rawSigned :: HexString , hasCompleteSigSet :: Bool } instance FromJSON RawSignedTransaction where parseJSON (Object o) = RawSignedTransaction <$> o .: "hex" <*> o .: "complete" parseJSON _ = mzero -- | Sign inputs for a raw transaction. signRawTransaction :: Client -> RawTransaction -- ^ The raw transaction whose inputs we're signing. -> Maybe (Vector UnspentTransaction) -- ^ An optional list of previous transaction outputs that -- this transaction depends on but may not yet be in the -- block chain. -> Maybe (Vector HexString) -- ^ An array of base58-encoded private keys that, if given, -- will be the only keys used to sign the transaction. -> Maybe WhoCanPay -- ^ Who can pay for this transaction? 'All' by default. -> IO RawSignedTransaction -- ^ Returns 'Nothing' if the transaction has a complete set -- of signatures, and the raw signed transa signRawTransaction client rt us' privkeys wcp = let us = V.map UFS <$> us' :: Maybe (Vector UnspentForSigning) in callApi client "signrawtransaction" [ tj rt , tj us , tj privkeys , tj . toString $ fromMaybe All wcp ] sendRawTransaction :: Client -> RawTransaction -> IO TransactionID sendRawTransaction client rt = callApi client "sendrawtransaction" [ tj rt ]