Skip to content

Commit

Permalink
server: Add endpoint "GET /addresses/:address/utxos"
Browse files Browse the repository at this point in the history
This is a part for #23, it allows to retrieve the unspent
outputs for the given address.
  • Loading branch information
AlexITC committed Jun 18, 2018
1 parent 41abe0e commit 5a7fd8a
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 8 deletions.
23 changes: 17 additions & 6 deletions server/app/com/xsn/explorer/services/AddressService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,38 @@ package com.xsn.explorer.services

import javax.inject.Inject

import com.alexitc.playsonify.core.FutureApplicationResult
import com.alexitc.playsonify.core.FutureOr.Implicits.{FutureOps, OrOps}
import com.alexitc.playsonify.core.{ApplicationResult, FutureApplicationResult}
import com.xsn.explorer.errors.AddressFormatError
import com.xsn.explorer.models.{Address, AddressDetails}
import org.scalactic.{One, Or}
import play.api.libs.json.JsValue

import scala.concurrent.ExecutionContext

class AddressService @Inject() (xsnService: XSNService)(implicit ec: ExecutionContext) {

def getDetails(addressString: String): FutureApplicationResult[AddressDetails] = {
val result = for {
address <- {
val maybe = Address.from(addressString)
Or.from(maybe, One(AddressFormatError)).toFutureOr
}

address <- getAddress(addressString).toFutureOr
balance <- xsnService.getAddressBalance(address).toFutureOr
transactions <- xsnService.getTransactions(address).toFutureOr
} yield AddressDetails(balance, transactions)

result.toFuture
}

def getUnspentOutputs(addressString: String): FutureApplicationResult[JsValue] = {
val result = for {
address <- getAddress(addressString).toFutureOr
outputs <- xsnService.getUnspentOutputs(address).toFutureOr
} yield outputs

result.toFuture
}

private def getAddress(addressString: String): ApplicationResult[Address] = {
val maybe = Address.from(addressString)
Or.from(maybe, One(AddressFormatError))
}
}
4 changes: 4 additions & 0 deletions server/app/controllers/AddressesController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@ class AddressesController @Inject() (

transactionService.getTransactions(address, paginatedQuery)
}

def getUnspentOutputs(address: String) = publicNoInput { _ =>
addressService.getUnspentOutputs(address)
}
}
1 change: 1 addition & 0 deletions server/conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ GET /transactions/:txid/raw controllers.TransactionsController.getRawTransa

GET /addresses/:address controllers.AddressesController.getDetails(address: String)
GET /addresses/:address/transactions controllers.AddressesController.getTransactions(address: String, offset: Int ?= 0, limit: Int ?= 10)
GET /addresses/:address/utxos controllers.AddressesController.getUnspentOutputs(address: String)

GET /blocks controllers.BlocksController.getLatestBlocks()
GET /blocks/:query controllers.BlocksController.getDetails(query: String)
Expand Down
47 changes: 45 additions & 2 deletions server/test/controllers/AddressesControllerSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import com.xsn.explorer.models._
import com.xsn.explorer.models.rpc.AddressBalance
import com.xsn.explorer.services.XSNService
import controllers.common.MyAPISpec
import org.scalactic.{One, Or}
import org.scalactic.{Good, One, Or}
import play.api.inject.bind
import play.api.libs.json.JsValue
import play.api.libs.json.{JsValue, Json}
import play.api.test.Helpers._

import scala.concurrent.Future
Expand All @@ -29,6 +29,29 @@ class AddressesControllerSpec extends MyAPISpec {
)
)

val addressForUtxos = DataHelper.createAddress("XeNEPsgeWqNbrEGEN5vqv4wYcC3qQrqNyp")
val utxosResponse =
"""
|[
| {
| "address": "XeNEPsgeWqNbrEGEN5vqv4wYcC3qQrqNyp",
| "height": 22451,
| "outputIndex": 0,
| "satoshis": 1500000000000,
| "script": "76a914285b6f1ccacea0059ff5393cb4eb2f0569e2b3e988ac",
| "txid": "ea837f2011974b6a1a2fa077dc33684932c514a4ec6febc10e1a19ebe1336539"
| },
| {
| "address": "XeNEPsgeWqNbrEGEN5vqv4wYcC3qQrqNyp",
| "height": 25093,
| "outputIndex": 3,
| "satoshis": 2250000000,
| "script": "76a914285b6f1ccacea0059ff5393cb4eb2f0569e2b3e988ac",
| "txid": "96a06b802d1c15818a42aa9b46dd2e236cde746000d35f74d3eb940ab9d5694d"
| }
|]
""".stripMargin

val customXSNService = new DummyXSNService {
val map = Map(
"Xi3sQfMQsy2CzMZTrnKW6HFGp1VqFThdLw" -> addressEmpty,
Expand All @@ -46,6 +69,15 @@ class AddressesControllerSpec extends MyAPISpec {
val result = Or.from(maybe, One(AddressFormatError))
Future.successful(result)
}

override def getUnspentOutputs(address: Address): FutureApplicationResult[JsValue] = {
if (address == addressForUtxos) {
val result = Good(Json.parse(utxosResponse))
Future.successful(result)
} else {
super.getUnspentOutputs(address)
}
}
}

override val application = guiceApplicationBuilder
Expand Down Expand Up @@ -82,4 +114,15 @@ class AddressesControllerSpec extends MyAPISpec {
(error \ "field").as[String] mustEqual "address"
}
}

"GET /addresses/:address/utxos" should {
def url(address: String) = s"/addresses/$address/utxos"

"return an array with the result" in {
val response = GET(url(addressForUtxos.string))

status(response) mustEqual OK
contentAsJson(response) mustEqual Json.parse(utxosResponse)
}
}
}

0 comments on commit 5a7fd8a

Please sign in to comment.