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 }