bitcoin-client

bitcoin client library for price ticker and wallet

git clone https://9o.is/git/bitcoin-client.git

Wallet.scala

(7398B)


      1 package inc.pyc.bitcoin
      2 
      3 import service._
      4 import JsonRPC._
      5 import BitcoinJsonRPC._
      6 import net.liftweb.json._
      7 import net.liftweb.json.JsonAST.JValue
      8 import scala.collection._
      9 import scala.concurrent._
     10 import duration._
     11 import akka.util.Timeout
     12 import com.typesafe.config.Config
     13 import akka.actor._
     14 import akka.util.Timeout.durationToTimeout
     15 import dispatch._
     16 import scala.util.Try
     17 
     18 /**
     19  * Actor to handle bitcoin wallet communications with JSON-RPC.
     20  */
     21 sealed trait Wallet {
     22   this: Actor with ActorLogging =>
     23 
     24   /** Wallet configuration */
     25   protected val config: Config
     26   protected val walletUri: String
     27   protected val rpcUser: String
     28   protected val rpcPass: String
     29   protected val walletPass: String
     30 
     31   /**
     32    * The wait time for a response. This timeout is also used to set
     33    * the timeout for walletpassphrase command.
     34    */
     35   protected implicit val timeout: Timeout = 5 seconds
     36 
     37   /**
     38    * Checks wallet json response. Throws exception if
     39    * response is invalid, else it logs the response.
     40    */
     41   protected def checkWalletResponse(json: JsonResponse, method: String = "") {
     42     json.either.left.map {
     43       case err =>
     44         new RuntimeException("Wallet Command '" + method + "' Failed: " + err)
     45     }
     46 
     47     json.either.right.map {
     48       case r =>
     49         implicit val formats = DefaultFormats
     50         log.debug("\nWallet Command '{}' Success:\n{}", method, pretty(render(Extraction.decompose(r))))
     51     }
     52   }
     53 }
     54 
     55 /**
     56  * Actor to handle bitcoin wallet communications with JSON-RPC
     57  * over Http.
     58  */
     59 private[bitcoin] trait HttpWallet extends HttpService with Wallet {
     60   this: Actor with ActorLogging =>
     61     
     62   import dispatch._
     63     
     64   val bitcoinWallet: Receive = {
     65     case CreateRawTransaction(inputs, receivers) =>
     66       val json = JsonMessage.createRawTransaction(inputs, receivers)
     67       sender ! requestExtract(json, _.extract[String])
     68       
     69     case GetBalance =>
     70       val json = JsonMessage.getBalance
     71       sender ! requestExtract(json, _.extract[String])
     72       
     73     case GetNewAddress =>
     74       val json = JsonMessage.getNewAddress
     75       sender ! requestExtract(json, _.extract[String])
     76     
     77     case GetRawTransaction(transactionHash) =>
     78       val json = JsonMessage.getRawTransaction(transactionHash)
     79       sender ! requestExtract(json, _.extract[RawTransaction])
     80     
     81     case ListUnspentTransactions(minConfirmations, maxConfirmations) =>
     82       val json = JsonMessage.listUnspentTransactions(minConfirmations, maxConfirmations)
     83       sender ! requestExtract(json, _.extract[List[UnspentTransaction]])
     84     
     85     case SendRawTransaction(signedTransaction) =>
     86       val json = JsonMessage.sendRawTransaction(signedTransaction)
     87       sender ! requestExtract(json, _.extract[String])
     88     
     89     case SignRawTransaction(transaction) =>
     90       val json = JsonMessage.signRawTransaction(transaction)
     91       sender ! requestExtract(json, _.extract[SignedTransaction])
     92     
     93     case WalletPassPhrase(walletPass, timeout) =>
     94       val json = JsonMessage.walletPassPhrase(walletPass, timeout)
     95       request(post(json)) // fire & forget
     96     
     97     case ValidateAddress(address) =>
     98       val json = JsonMessage.validateAddress(address)
     99       sender ! requestExtract(json, _.extract[AddressValidation])
    100   }
    101 
    102   private def post(msg: JValue): Req =
    103     url(walletUri) <:< Map("Content-type" -> "application/json-rpc") <<
    104       compact(render(msg)) as_! (rpcUser, rpcPass)
    105 
    106   /* All-in-one func: makes request, extracts response, extracts data. */
    107   private def requestExtract[T](req: JsonRequest, extractor: JValue => T): T = {
    108     val json = request(post(req)).extract[JsonResponse]
    109     checkWalletResponse(json, req.method)
    110     val result = json.result.getOrElse(JNull)
    111     extractor(result)
    112   }
    113 
    114 }
    115 
    116 /**
    117  * Actor to handle bitcoin wallet communications with JSON-RPC
    118  * over WebSocket.
    119  */
    120 private[bitcoin] trait WsWallet extends WsService with Wallet {
    121   this: Actor with ActorLogging =>
    122 
    123   /**
    124    * Handles notifications incoming from server.
    125    */
    126   protected def handleNotification: Receive
    127 
    128   override val api = new java.net.URI(walletUri)
    129 
    130   val bitcoinWallet: Receive = {
    131     case CreateRawTransaction(inputs, receivers) =>
    132       val json = JsonMessage.createRawTransaction(inputs, receivers)
    133       requestExtract(json.id, json, _.extract[String], "CreateRawTransaction" :: Nil)
    134       
    135     case GetBalance =>
    136       val json = JsonMessage.getBalance
    137       requestExtract(json.id, json, _.extract[String], "GetBalance" :: Nil)
    138     
    139     case GetNewAddress =>
    140       val json = JsonMessage.getNewAddress
    141       requestExtract(json.id, json, _.extract[String], "GetNewAddress" :: Nil)
    142     
    143     case GetRawTransaction(transactionHash) =>
    144       val json = JsonMessage.getRawTransaction(transactionHash)
    145       requestExtract(json.id, json, _.extract[RawTransaction], "GetRawTransaction" :: Nil)
    146     
    147     case ListUnspentTransactions(minConfirmations, maxConfirmations) =>
    148       val json = JsonMessage.listUnspentTransactions(minConfirmations, maxConfirmations)
    149       requestExtract(json.id, json, _.extract[List[UnspentTransaction]], "ListUnspentTransactions" :: Nil)
    150     
    151     case SendRawTransaction(signedTransaction) =>
    152       val json = JsonMessage.sendRawTransaction(signedTransaction)
    153       requestExtract(json.id, json, _.extract[String], "SendRawTransaction" :: Nil)
    154     
    155     case SignRawTransaction(transaction) =>
    156       val json = JsonMessage.signRawTransaction(transaction)
    157       requestExtract(json.id, json, _.extract[SignedTransaction], "SignRawTransaction" :: Nil)
    158     
    159     case WalletPassPhrase(walletPass, timeout) =>
    160       val json = JsonMessage.walletPassPhrase(walletPass, timeout)
    161       requestForget(json)
    162     
    163     case ValidateAddress(address) =>
    164       val json = JsonMessage.validateAddress(address)
    165       requestExtract(json.id, json, _.extract[AddressValidation], "ValidateAddress" :: Nil)
    166       
    167     case json @ JsonResponse(jsonrpc, id, errorOption, resultOption) =>
    168       requests.remove(id).foreach(req => {
    169         val (p, extract, info) = req
    170         checkWalletResponse(json, info.head)
    171         json.either.left.map(p tryFailure new RuntimeException(_))        
    172         json.either.right.map(p trySuccess extract(_))
    173       })
    174       
    175     case n: NotificationMessage =>
    176       handleNotification.applyOrElse(n, unhandled)
    177   }
    178 
    179   /*
    180    * Handles notifications incoming from server.
    181    */
    182   private def handleResponseNotification: PartialFunction[JsonNotification, Unit] = {
    183 
    184     /* New Transaction */
    185     case JsonNotification(_, "newtx", params) =>
    186       Try(params(1).extract[TransactionNotification]).filter(_.category == "receive").
    187         foreach(tx => self ! ReceivedPayment(tx.txid, tx.address, tx.amount, tx.confirmations))
    188 
    189     case _ => // ignore
    190   }
    191 
    192   override def onMessage(msg: JValue) = () => {
    193 
    194     // messages should be either JsonNotification or JsonResponse
    195     // only notifications have jsonrpc field.
    196     def isNotification: Boolean =
    197       (msg find {
    198         case JField("jsonrpc", _) => true
    199         case _ => false
    200       }).isDefined
    201 
    202     if (isNotification)
    203       Try(msg.extract[JsonNotification]).foreach(
    204         handleResponseNotification.applyOrElse(_, unhandled))
    205     else
    206       Try(msg.extract[JsonResponse]).foreach(self ! _)
    207   }
    208 }
    209 
    210 /**
    211  * Actor to handle bitcoin wallet communications with JSON-RPC
    212  * over Secure WebSocket.
    213  */
    214 private[bitcoin] trait WssWallet extends WssService with WsWallet {
    215   this: Actor with ActorLogging =>
    216 }