Eclair RPC 0.4.1 (#1627)

* Eclair RPC 0.4.1

* channelstats

* remove the launch script editing code

* getinfo

* sendonchain, onchainbalance, onchaintransactions

* cleanup

* repond to the comments
This commit is contained in:
rorp 2020-07-07 13:22:44 -07:00 committed by GitHub
parent 194370622d
commit 43b6349758
7 changed files with 325 additions and 110 deletions

View File

@ -5,11 +5,12 @@ import java.time.Instant
import java.util.UUID import java.util.UUID
import org.bitcoins.commons.serializers.JsonReaders._ import org.bitcoins.commons.serializers.JsonReaders._
import org.bitcoins.core.config.BitcoinNetwork
import org.bitcoins.core.currency.Satoshis import org.bitcoins.core.currency.Satoshis
import org.bitcoins.core.protocol.ln.channel.{ChannelState, FundedChannelId} import org.bitcoins.core.protocol.ln.channel.{ChannelState, FundedChannelId}
import org.bitcoins.core.protocol.ln.currency.MilliSatoshis import org.bitcoins.core.protocol.ln.currency.MilliSatoshis
import org.bitcoins.core.protocol.ln.fee.FeeProportionalMillionths import org.bitcoins.core.protocol.ln.fee.FeeProportionalMillionths
import org.bitcoins.core.protocol.ln.node.NodeId import org.bitcoins.core.protocol.ln.node.{Feature, FeatureSupport, NodeId}
import org.bitcoins.core.protocol.ln.{ import org.bitcoins.core.protocol.ln.{
LnHumanReadablePart, LnHumanReadablePart,
PaymentPreimage, PaymentPreimage,
@ -28,11 +29,15 @@ import scala.concurrent.duration.FiniteDuration
sealed abstract class EclairModels sealed abstract class EclairModels
case class GetInfoResult( case class GetInfoResult(
version: String,
nodeId: NodeId, nodeId: NodeId,
alias: String, alias: String,
features: Features,
chainHash: DoubleSha256Digest, chainHash: DoubleSha256Digest,
network: BitcoinNetwork,
blockHeight: Long, blockHeight: Long,
publicAddresses: Seq[InetSocketAddress]) publicAddresses: Seq[InetSocketAddress],
instanceId: UUID)
case class PeerInfo( case class PeerInfo(
nodeId: NodeId, nodeId: NodeId,
@ -40,6 +45,31 @@ case class PeerInfo(
address: Option[String], address: Option[String],
channels: Int) channels: Int)
case class ChannelCommandResult(
results: scala.collection.Map[
Either[ShortChannelId, FundedChannelId],
ChannelCommandResult.State]
)
object ChannelCommandResult {
sealed trait State
case object OK extends State
case object ChannelOpened extends State
case object ChannelClosed extends State
case class Error(message: String) extends State
def fromString(s: String): State =
if (s == "ok") {
ChannelCommandResult.OK
} else if (s.startsWith("created channel ")) {
ChannelCommandResult.ChannelOpened
} else if (s.startsWith("closed channel ")) {
ChannelCommandResult.ChannelClosed
} else {
ChannelCommandResult.Error(s)
}
}
/** /**
* This is the data model returned by the RPC call * This is the data model returned by the RPC call
* `channels nodeId`. The content of the objects * `channels nodeId`. The content of the objects
@ -82,14 +112,23 @@ case class OpenChannelInfo(
state: ChannelState.NORMAL.type state: ChannelState.NORMAL.type
) extends ChannelInfo ) extends ChannelInfo
case class ActivatedFeature(feature: Feature, support: FeatureSupport)
case class UnknownFeature(bitIndex: Int)
case class Features(
activated: Set[ActivatedFeature],
unknown: Set[UnknownFeature])
case class NodeInfo( case class NodeInfo(
signature: ECDigitalSignature, signature: ECDigitalSignature,
features: String, features: Features,
timestamp: Instant, timestamp: Instant,
nodeId: NodeId, nodeId: NodeId,
rgbColor: String, rgbColor: String,
alias: String, alias: String,
addresses: Vector[InetSocketAddress]) addresses: Vector[InetSocketAddress],
unknownFields: String)
case class ChannelDesc(shortChannelId: ShortChannelId, a: NodeId, b: NodeId) case class ChannelDesc(shortChannelId: ShortChannelId, a: NodeId, b: NodeId)
@ -110,12 +149,31 @@ case class NetworkFeesResult(
case class ChannelStats( case class ChannelStats(
channelId: FundedChannelId, channelId: FundedChannelId,
direction: ChannelStats.Direction,
avgPaymentAmount: Satoshis, avgPaymentAmount: Satoshis,
paymentCount: Long, paymentCount: Long,
relayFee: Satoshis, relayFee: Satoshis,
networkFee: Satoshis networkFee: Satoshis
) )
object ChannelStats {
sealed trait Direction
case object In extends Direction
case object Out extends Direction
object Direction {
def fromString(s: String): Direction =
if (s.toUpperCase == "IN") {
ChannelStats.In
} else if (s.toUpperCase == "OUT") {
ChannelStats.Out
} else {
throw new RuntimeException(s"Unknown payment direction: `$s`")
}
}
}
case class UsableBalancesResult( case class UsableBalancesResult(
remoteNodeId: NodeId, remoteNodeId: NodeId,
shortChannelId: ShortChannelId, shortChannelId: ShortChannelId,
@ -362,3 +420,14 @@ object WebSocketEvent {
) extends WebSocketEvent ) extends WebSocketEvent
} }
case class OnChainBalance(confirmed: Satoshis, unconfirmed: Satoshis)
case class WalletTransaction(
address: String,
amount: Satoshis,
fees: Satoshis,
blockHash: DoubleSha256DigestBE,
confirmations: Long,
txid: DoubleSha256DigestBE,
timestamp: Long)

View File

@ -19,7 +19,7 @@ import org.bitcoins.core.protocol.ln._
import org.bitcoins.core.protocol.ln.channel._ import org.bitcoins.core.protocol.ln.channel._
import org.bitcoins.core.protocol.ln.currency._ import org.bitcoins.core.protocol.ln.currency._
import org.bitcoins.core.protocol.ln.fee.FeeProportionalMillionths import org.bitcoins.core.protocol.ln.fee.FeeProportionalMillionths
import org.bitcoins.core.protocol.ln.node.NodeId import org.bitcoins.core.protocol.ln.node.{Feature, FeatureSupport, NodeId}
import org.bitcoins.core.protocol.script.{ import org.bitcoins.core.protocol.script.{
ScriptPubKey, ScriptPubKey,
ScriptSignature, ScriptSignature,
@ -36,16 +36,7 @@ import org.bitcoins.core.protocol.{
import org.bitcoins.core.script.ScriptType import org.bitcoins.core.script.ScriptType
import org.bitcoins.core.script.crypto.HashType import org.bitcoins.core.script.crypto.HashType
import org.bitcoins.core.wallet.fee.{BitcoinFeeUnit, SatoshisPerByte} import org.bitcoins.core.wallet.fee.{BitcoinFeeUnit, SatoshisPerByte}
import org.bitcoins.crypto.{ import org.bitcoins.crypto._
DoubleSha256Digest,
DoubleSha256DigestBE,
ECDigitalSignature,
ECPublicKey,
RipeMd160Digest,
RipeMd160DigestBE,
Sha256Digest,
Sha256Hash160Digest
}
import play.api.libs.json._ import play.api.libs.json._
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -257,6 +248,13 @@ object JsonReaders {
} }
} }
implicit object BitcoinNetworkReads extends Reads[BitcoinNetwork] {
override def reads(json: JsValue): JsResult[BitcoinNetwork] =
SerializerUtil.processJsString(BitcoinNetworks.fromString)(json)
}
// Errors for Unit return types are caught in RpcClient::checkUnit // Errors for Unit return types are caught in RpcClient::checkUnit
implicit object UnitReads extends Reads[Unit] { implicit object UnitReads extends Reads[Unit] {
override def reads(json: JsValue): JsResult[Unit] = JsSuccess(()) override def reads(json: JsValue): JsResult[Unit] = JsSuccess(())
@ -718,6 +716,41 @@ object JsonReaders {
} }
} }
implicit val featureSupportReads: Reads[FeatureSupport] =
Reads { jsValue =>
SerializerUtil.processJsString {
case "mandatory" => FeatureSupport.Mandatory
case "optional" => FeatureSupport.Optional
case err: String =>
throw new RuntimeException(s"Invalid feature support value: `$err`")
}(jsValue)
}
lazy val featuresByName: Map[String, Feature] =
Feature.knownFeatures.map(f => (f.rfcName, f)).toMap
implicit val featureReads: Reads[Feature] =
Reads { jsValue =>
SerializerUtil.processJsString(featuresByName)(jsValue)
}
implicit val unknownFeatureReads: Reads[UnknownFeature] =
Reads { jsValue =>
SerializerUtil.processJsString(s => UnknownFeature(s.toInt))(jsValue)
}
implicit val activatedFeatureReads: Reads[ActivatedFeature] =
Reads { jsValue =>
for {
feature <- (jsValue \ "name").validate[Feature]
support <- (jsValue \ "support").validate[FeatureSupport]
} yield ActivatedFeature(feature, support)
}
implicit val featuresReads: Reads[Features] = {
Json.reads[Features]
}
implicit val getInfoResultReads: Reads[GetInfoResult] = { implicit val getInfoResultReads: Reads[GetInfoResult] = {
Json.reads[GetInfoResult] Json.reads[GetInfoResult]
} }
@ -737,20 +770,22 @@ object JsonReaders {
Reads { jsValue => Reads { jsValue =>
for { for {
signature <- (jsValue \ "signature").validate[ECDigitalSignature] signature <- (jsValue \ "signature").validate[ECDigitalSignature]
features <- (jsValue \ "features").validate[String] features <- (jsValue \ "features").validate[Features]
timestamp <- (jsValue \ "timestamp") timestamp <- (jsValue \ "timestamp")
.validate[Instant](instantReadsSeconds) .validate[Instant](instantReadsSeconds)
nodeId <- (jsValue \ "nodeId").validate[NodeId] nodeId <- (jsValue \ "nodeId").validate[NodeId]
rgbColor <- (jsValue \ "rgbColor").validate[String] rgbColor <- (jsValue \ "rgbColor").validate[String]
alias <- (jsValue \ "alias").validate[String] alias <- (jsValue \ "alias").validate[String]
addresses <- (jsValue \ "addresses").validate[Vector[InetSocketAddress]] addresses <- (jsValue \ "addresses").validate[Vector[InetSocketAddress]]
unknownFields <- (jsValue \ "unknownFields").validate[String]
} yield NodeInfo(signature, } yield NodeInfo(signature,
features, features,
timestamp, timestamp,
nodeId, nodeId,
rgbColor, rgbColor,
alias, alias,
addresses) addresses,
unknownFields)
} }
} }
@ -843,6 +878,25 @@ object JsonReaders {
} }
} }
implicit val channelCommandResultStateReads: Reads[
ChannelCommandResult.State] = Reads { jsValue =>
SerializerUtil.processJsString(ChannelCommandResult.fromString)(jsValue)
}
implicit val channelCommandResultReads: Reads[ChannelCommandResult] = Reads {
case obj: JsObject =>
JsSuccess(ChannelCommandResult(obj.value.map { x =>
val channelId = Try(FundedChannelId.fromHex(x._1)) match {
case Success(id) => Right(id)
case Failure(_) => Left(ShortChannelId.fromHumanReadableString(x._1))
}
(channelId, x._2.validate[ChannelCommandResult.State].get)
}))
case err @ (JsNull | _: JsBoolean | _: JsString | _: JsArray |
_: JsNumber) =>
SerializerUtil.buildJsErrorMsg("jsobject", err)
}
implicit val channelUpdateReads: Reads[ChannelUpdate] = { implicit val channelUpdateReads: Reads[ChannelUpdate] = {
Reads { jsValue => Reads { jsValue =>
for { for {
@ -1129,6 +1183,11 @@ object JsonReaders {
timestamp) timestamp)
} }
implicit val channelStatsDirectionReads: Reads[ChannelStats.Direction] =
Reads { json =>
SerializerUtil.processJsString(ChannelStats.Direction.fromString)(json)
}
implicit val channelStatsReads: Reads[ChannelStats] = implicit val channelStatsReads: Reads[ChannelStats] =
Json.reads[ChannelStats] Json.reads[ChannelStats]
@ -1243,4 +1302,10 @@ object JsonReaders {
} }
} }
implicit val onChainBalanceReads: Reads[OnChainBalance] =
Json.reads[OnChainBalance]
implicit val walletTransactionReads: Reads[WalletTransaction] =
Json.reads[WalletTransaction]
} }

View File

@ -0,0 +1,95 @@
package org.bitcoins.core.protocol.ln.node
sealed trait FeatureSupport
object FeatureSupport {
case object Mandatory extends FeatureSupport {
override def toString: String = "mandatory"
}
case object Optional extends FeatureSupport {
override def toString: String = "optional"
}
}
sealed trait Feature {
def rfcName: String
def mandatory: Int
def optional: Int = mandatory + 1
def supportBit(support: FeatureSupport): Int =
support match {
case FeatureSupport.Mandatory => mandatory
case FeatureSupport.Optional => optional
}
override def toString = rfcName
}
object Feature {
case object OptionDataLossProtect extends Feature {
val rfcName = "option_data_loss_protect"
val mandatory = 0
}
case object InitialRoutingSync extends Feature {
val rfcName = "initial_routing_sync"
// reserved but not used as per lightningnetwork/lightning-rfc/pull/178
val mandatory = 2
}
case object ChannelRangeQueries extends Feature {
val rfcName = "gossip_queries"
val mandatory = 6
}
case object VariableLengthOnion extends Feature {
val rfcName = "var_onion_optin"
val mandatory = 8
}
case object ChannelRangeQueriesExtended extends Feature {
val rfcName = "gossip_queries_ex"
val mandatory = 10
}
case object StaticRemoteKey extends Feature {
val rfcName = "option_static_remotekey"
val mandatory = 12
}
case object PaymentSecret extends Feature {
val rfcName = "payment_secret"
val mandatory = 14
}
case object BasicMultiPartPayment extends Feature {
val rfcName = "basic_mpp"
val mandatory = 16
}
case object Wumbo extends Feature {
val rfcName = "option_support_large_channel"
val mandatory = 18
}
case object TrampolinePayment extends Feature {
val rfcName = "trampoline_payment"
val mandatory = 50
}
val knownFeatures: Set[Feature] = Set(
OptionDataLossProtect,
InitialRoutingSync,
ChannelRangeQueries,
VariableLengthOnion,
ChannelRangeQueriesExtended,
PaymentSecret,
BasicMultiPartPayment,
Wumbo,
TrampolinePayment,
StaticRemoteKey
)
}

View File

@ -3,16 +3,9 @@ package org.bitcoins.eclair.rpc
import java.nio.file.Files import java.nio.file.Files
import java.time.Instant import java.time.Instant
import org.bitcoins.commons.jsonmodels.eclair.{ import org.bitcoins.commons.jsonmodels.eclair._
ChannelResult, import org.bitcoins.core.config.RegTest
ChannelUpdate, import org.bitcoins.core.currency.{CurrencyUnits, Satoshis}
IncomingPaymentStatus,
InvoiceResult,
OpenChannelInfo,
OutgoingPaymentStatus,
WebSocketEvent
}
import org.bitcoins.core.currency.{CurrencyUnit, CurrencyUnits, Satoshis}
import org.bitcoins.core.number.UInt64 import org.bitcoins.core.number.UInt64
import org.bitcoins.core.protocol.BitcoinAddress import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.ln.LnParams.LnBitcoinRegTest import org.bitcoins.core.protocol.ln.LnParams.LnBitcoinRegTest
@ -149,6 +142,23 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
executeWithClientOtherClient(f) executeWithClientOtherClient(f)
} }
it should "perform on-chain operations" in {
for {
c <- clientF
address <- c.getNewAddress()
balance <- c.onChainBalance()
txid <- c.sendOnChain(address, Satoshis(5000), 1)
balance1 <- c.onChainBalance()
transactions <- c.onChainTransactions()
} yield {
assert(balance.confirmed > Satoshis(0))
assert(balance.unconfirmed == Satoshis(0))
// we sent 5000 sats to ourselves and paid some sats in fee
assert(balance1.confirmed < balance.confirmed)
assert(transactions.exists(_.txid == txid))
}
}
/** /**
* Please keep this test the very first. All other tests rely on the propagated gossip messages. * Please keep this test the very first. All other tests rely on the propagated gossip messages.
*/ */
@ -1189,15 +1199,6 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
} }
} }
it should "get new address" in {
for {
c <- clientF
res <- c.getNewAddress()
} yield {
assert(res.toString.nonEmpty)
}
}
it should "disconnect node" in { it should "disconnect node" in {
for { for {
c1 <- clientF c1 <- clientF
@ -1205,6 +1206,8 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
nodeInfo2 <- c2.getInfo nodeInfo2 <- c2.getInfo
_ <- c1.disconnect(nodeInfo2.nodeId) _ <- c1.disconnect(nodeInfo2.nodeId)
} yield { } yield {
assert(nodeInfo2.features.activated.nonEmpty)
assert(nodeInfo2.network == RegTest)
succeed succeed
} }
} }

View File

@ -21,8 +21,8 @@ TaskKeys.downloadEclair := {
Files.createDirectories(binaryDir) Files.createDirectories(binaryDir)
} }
val version = "0.4" val version = "0.4.1"
val commit = "69c538e" val commit = "e5fb281"
logger.debug(s"(Maybe) downloading Eclair binaries for version: $version") logger.debug(s"(Maybe) downloading Eclair binaries for version: $version")
@ -49,36 +49,6 @@ TaskKeys.downloadEclair := {
logger.info(s"Deleting archive") logger.info(s"Deleting archive")
Files.delete(archiveLocation) Files.delete(archiveLocation)
fixShebang(
versionDir resolve s"eclair-node-$version-$commit" resolve "bin" resolve "eclair-node.sh")
logger.info(s"Download complete") logger.info(s"Download complete")
} }
// remove me when https://github.com/ACINQ/eclair/issues/1421
// and https://github.com/ACINQ/eclair/issues/1422 are fixed
def fixShebang(scriptPath: Path): Unit = {
import java.nio.file.attribute.PosixFilePermissions
import scala.io.Source
import scala.collection.JavaConverters._
val tempPath = scriptPath.getParent resolve scriptPath.getFileName.toString + ".tmp"
Files.createFile(tempPath,
PosixFilePermissions.asFileAttribute(
PosixFilePermissions.fromString("rwxr-xr-x")))
val source = Source
.fromFile(scriptPath.toUri)
val lines = (Vector("#!/usr/bin/env bash") ++ source.getLines()).map(
line =>
if (line == "declare -r lib_dir=\"$(realpath \"${app_home::-4}/lib\")\" # {app_home::-4} transforms ../bin in ../")
"declare -r lib_dir=\"$(realpath \"${app_home:0:${#app_home}-4}/lib\")\" # {app_home:0:${#app_home}-4} transforms ../bin in ../"
else line)
source.close()
Files.write(tempPath, lines.asJava, StandardOpenOption.WRITE)
tempPath.toFile.renameTo(scriptPath.toFile)
}
} }

View File

@ -3,25 +3,7 @@ package org.bitcoins.eclair.rpc.api
import java.net.InetSocketAddress import java.net.InetSocketAddress
import java.time.Instant import java.time.Instant
import org.bitcoins.commons.jsonmodels.eclair.{ import org.bitcoins.commons.jsonmodels.eclair._
AuditResult,
ChannelDesc,
ChannelInfo,
ChannelResult,
ChannelStats,
ChannelUpdate,
GetInfoResult,
IncomingPayment,
InvoiceResult,
NetworkFeesResult,
NodeInfo,
OutgoingPayment,
PaymentId,
PeerInfo,
SendToRouteResult,
UsableBalancesResult,
WebSocketEvent
}
import org.bitcoins.core.currency.{CurrencyUnit, Satoshis} import org.bitcoins.core.currency.{CurrencyUnit, Satoshis}
import org.bitcoins.core.protocol.ln.channel.{ChannelId, FundedChannelId} import org.bitcoins.core.protocol.ln.channel.{ChannelId, FundedChannelId}
import org.bitcoins.core.protocol.ln.currency.MilliSatoshis import org.bitcoins.core.protocol.ln.currency.MilliSatoshis
@ -35,7 +17,7 @@ import org.bitcoins.core.protocol.ln.{
import org.bitcoins.core.protocol.script.ScriptPubKey import org.bitcoins.core.protocol.script.ScriptPubKey
import org.bitcoins.core.protocol.{Address, BitcoinAddress} import org.bitcoins.core.protocol.{Address, BitcoinAddress}
import org.bitcoins.core.wallet.fee.SatoshisPerByte import org.bitcoins.core.wallet.fee.SatoshisPerByte
import org.bitcoins.crypto.Sha256Digest import org.bitcoins.crypto.{DoubleSha256DigestBE, Sha256Digest}
import org.bitcoins.eclair.rpc.network.NodeUri import org.bitcoins.eclair.rpc.network.NodeUri
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -93,7 +75,7 @@ trait EclairApi {
def disconnect(nodeId: NodeId): Future[Unit] def disconnect(nodeId: NodeId): Future[Unit]
def close(id: ChannelId, spk: ScriptPubKey): Future[Unit] def close(id: ChannelId, spk: ScriptPubKey): Future[ChannelCommandResult]
def findRoute( def findRoute(
nodeId: NodeId, nodeId: NodeId,
@ -105,9 +87,9 @@ trait EclairApi {
invoice: LnInvoice, invoice: LnInvoice,
amountMsat: MilliSatoshis): Future[Vector[NodeId]] amountMsat: MilliSatoshis): Future[Vector[NodeId]]
def forceClose(channelId: ChannelId): Future[Unit] def forceClose(channelId: ChannelId): Future[ChannelCommandResult]
def forceClose(shortChannelId: ShortChannelId): Future[Unit] def forceClose(shortChannelId: ShortChannelId): Future[ChannelCommandResult]
def getInfo: Future[GetInfoResult] def getInfo: Future[GetInfoResult]
@ -120,13 +102,13 @@ trait EclairApi {
def updateRelayFee( def updateRelayFee(
channelId: ChannelId, channelId: ChannelId,
feeBaseMsat: MilliSatoshis, feeBaseMsat: MilliSatoshis,
feePropertionalMillionths: Long): Future[Unit] feePropertionalMillionths: Long): Future[ChannelCommandResult]
def updateRelayFee( def updateRelayFee(
shortChannelId: ShortChannelId, shortChannelId: ShortChannelId,
feeBaseMsat: MilliSatoshis, feeBaseMsat: MilliSatoshis,
feePropertionalMillionths: Long feePropertionalMillionths: Long
): Future[Unit] ): Future[ChannelCommandResult]
def open( def open(
nodeId: NodeId, nodeId: NodeId,
@ -296,4 +278,13 @@ trait EclairApi {
def connectToWebSocket(eventHandler: WebSocketEvent => Unit): Future[Unit] def connectToWebSocket(eventHandler: WebSocketEvent => Unit): Future[Unit]
def getNewAddress(): Future[BitcoinAddress] def getNewAddress(): Future[BitcoinAddress]
def onChainBalance(): Future[OnChainBalance]
def onChainTransactions(): Future[Vector[WalletTransaction]]
def sendOnChain(
address: BitcoinAddress,
amount: Satoshis,
confirmationTarget: Int): Future[DoubleSha256DigestBE]
} }

View File

@ -31,7 +31,7 @@ import org.bitcoins.core.protocol.script.ScriptPubKey
import org.bitcoins.core.protocol.{Address, BitcoinAddress} import org.bitcoins.core.protocol.{Address, BitcoinAddress}
import org.bitcoins.core.util.{BytesUtil, FutureUtil, StartStop} import org.bitcoins.core.util.{BytesUtil, FutureUtil, StartStop}
import org.bitcoins.core.wallet.fee.SatoshisPerByte import org.bitcoins.core.wallet.fee.SatoshisPerByte
import org.bitcoins.crypto.Sha256Digest import org.bitcoins.crypto.{DoubleSha256DigestBE, Sha256Digest}
import org.bitcoins.eclair.rpc.api._ import org.bitcoins.eclair.rpc.api._
import org.bitcoins.eclair.rpc.config.EclairInstance import org.bitcoins.eclair.rpc.config.EclairInstance
import org.bitcoins.eclair.rpc.network.NodeUri import org.bitcoins.eclair.rpc.network.NodeUri
@ -65,7 +65,7 @@ class EclairRpcClient(
} }
override def allNodes(): Future[Vector[NodeInfo]] = { override def allNodes(): Future[Vector[NodeInfo]] = {
eclairCall[Vector[NodeInfo]]("allnodes") eclairCall[Vector[NodeInfo]]("nodes")
} }
override def allUpdates(): Future[Vector[ChannelUpdate]] = override def allUpdates(): Future[Vector[ChannelUpdate]] =
@ -108,22 +108,22 @@ class EclairRpcClient(
private def close( private def close(
channelId: ChannelId, channelId: ChannelId,
shortChannelId: Option[ShortChannelId], shortChannelId: Option[ShortChannelId],
scriptPubKey: Option[ScriptPubKey]): Future[Unit] = { scriptPubKey: Option[ScriptPubKey]): Future[ChannelCommandResult] = {
val params = val params =
Seq("channelId" -> channelId.hex) ++ Seq( Seq("channelId" -> channelId.hex) ++ Seq(
shortChannelId.map(x => "shortChannelId" -> x.toString), shortChannelId.map(x => "shortChannelId" -> x.toString),
scriptPubKey.map(x => "scriptPubKey" -> BytesUtil.encodeHex(x.asmBytes)) scriptPubKey.map(x => "scriptPubKey" -> BytesUtil.encodeHex(x.asmBytes))
).flatten ).flatten
eclairCall[String]("close", params: _*).map(_ => ()) eclairCall[ChannelCommandResult]("close", params: _*)
} }
def close(channelId: ChannelId): Future[Unit] = def close(channelId: ChannelId): Future[ChannelCommandResult] =
close(channelId, scriptPubKey = None, shortChannelId = None) close(channelId, scriptPubKey = None, shortChannelId = None)
override def close( override def close(
channelId: ChannelId, channelId: ChannelId,
scriptPubKey: ScriptPubKey): Future[Unit] = { scriptPubKey: ScriptPubKey): Future[ChannelCommandResult] = {
close(channelId, scriptPubKey = Some(scriptPubKey), shortChannelId = None) close(channelId, scriptPubKey = Some(scriptPubKey), shortChannelId = None)
} }
@ -173,13 +173,16 @@ class EclairRpcClient(
eclairCall[Vector[NodeId]]("findroute", params: _*) eclairCall[Vector[NodeId]]("findroute", params: _*)
} }
override def forceClose(channelId: ChannelId): Future[Unit] = { override def forceClose(
eclairCall[String]("forceclose", "channelId" -> channelId.hex).map(_ => ()) channelId: ChannelId): Future[ChannelCommandResult] = {
eclairCall[ChannelCommandResult]("forceclose", "channelId" -> channelId.hex)
} }
override def forceClose(shortChannelId: ShortChannelId): Future[Unit] = { override def forceClose(
eclairCall[String]("forceclose", shortChannelId: ShortChannelId): Future[ChannelCommandResult] = {
"shortChannelId" -> shortChannelId.toString).map(_ => ()) eclairCall[ChannelCommandResult](
"forceclose",
"shortChannelId" -> shortChannelId.toString)
} }
override def getInfo: Future[GetInfoResult] = { override def getInfo: Future[GetInfoResult] = {
@ -530,8 +533,8 @@ class EclairRpcClient(
override def updateRelayFee( override def updateRelayFee(
channelId: ChannelId, channelId: ChannelId,
feeBaseMsat: MilliSatoshis, feeBaseMsat: MilliSatoshis,
feeProportionalMillionths: Long): Future[Unit] = { feeProportionalMillionths: Long): Future[ChannelCommandResult] = {
eclairCall[Unit]( eclairCall[ChannelCommandResult](
"updaterelayfee", "updaterelayfee",
"channelId" -> channelId.hex, "channelId" -> channelId.hex,
"feeBaseMsat" -> feeBaseMsat.toLong.toString, "feeBaseMsat" -> feeBaseMsat.toLong.toString,
@ -542,8 +545,8 @@ class EclairRpcClient(
override def updateRelayFee( override def updateRelayFee(
shortChannelId: ShortChannelId, shortChannelId: ShortChannelId,
feeBaseMsat: MilliSatoshis, feeBaseMsat: MilliSatoshis,
feeProportionalMillionths: Long): Future[Unit] = { feeProportionalMillionths: Long): Future[ChannelCommandResult] = {
eclairCall[Unit]( eclairCall[ChannelCommandResult](
"updaterelayfee", "updaterelayfee",
"shortChannelId" -> shortChannelId.toHumanReadableString, "shortChannelId" -> shortChannelId.toHumanReadableString,
"feeBaseMsat" -> feeBaseMsat.toLong.toString, "feeBaseMsat" -> feeBaseMsat.toLong.toString,
@ -609,6 +612,25 @@ class EclairRpcClient(
eclairCall[BitcoinAddress]("getnewaddress") eclairCall[BitcoinAddress]("getnewaddress")
} }
override def onChainBalance(): Future[OnChainBalance] = {
eclairCall[OnChainBalance]("onchainbalance")
}
override def onChainTransactions(): Future[Vector[WalletTransaction]] = {
eclairCall[Vector[WalletTransaction]]("onchaintransactions")
}
override def sendOnChain(
address: BitcoinAddress,
amount: Satoshis,
confirmationTarget: Int): Future[DoubleSha256DigestBE] = {
eclairCall[DoubleSha256DigestBE](
"sendonchain",
"address" -> address.toString,
"amountSatoshis" -> amount.toLong.toString,
"confirmationTarget" -> confirmationTarget.toString)
}
private def eclairCall[T](command: String, parameters: (String, String)*)( private def eclairCall[T](command: String, parameters: (String, String)*)(
implicit reader: Reads[T]): Future[T] = { implicit reader: Reads[T]): Future[T] = {
val request = buildRequest(getDaemon, command, parameters: _*) val request = buildRequest(getDaemon, command, parameters: _*)
@ -946,8 +968,8 @@ object EclairRpcClient {
implicit system: ActorSystem) = new EclairRpcClient(instance, binary) implicit system: ActorSystem) = new EclairRpcClient(instance, binary)
/** The current commit we support of Eclair */ /** The current commit we support of Eclair */
private[bitcoins] val commit = "69c538e" private[bitcoins] val commit = "e5fb281"
/** The current version we support of Eclair */ /** The current version we support of Eclair */
private[bitcoins] val version = "0.4" private[bitcoins] val version = "0.4.1"
} }