Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement the on-chain commands #22

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion contrib/eclair-cli_autocomplete.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ _eclair_cli() {
# `_init_completion` is a helper function provided by the Bash-completion package.
_init_completion || return

local commands="getinfo connect disconnect open rbfopen cpfpbumpfees close forceclose updaterelayfee peers nodes node allchannels allupdates createinvoice deleteinvoice parseinvoice payinvoice sendtonode sendtoroute getsentinfo getreceivedinfo listreceivedpayments getinvoice listinvoices listpendinginvoices"
local commands="getinfo connect disconnect open rbfopen cpfpbumpfees close forceclose updaterelayfee peers nodes node allchannels allupdates createinvoice deleteinvoice parseinvoice payinvoice sendtonode sendtoroute getsentinfo getreceivedinfo listreceivedpayments getinvoice listinvoices listpendinginvoices findroute findroutetonode findroutebetweennodes getnewaddress sendonchain onchainbalance onchaintransactions"
local common_opts="-p --host"
local connect_opts="--uri --nodeId --address --port"
local disconnect_opts="--nodeId"
Expand All @@ -44,6 +44,11 @@ _eclair_cli() {
local getinvoice_opts="--paymentHash"
local listinvoices_opts="--from --to --count --skip"
local listpendinginvoices_opts="--from --to --count --skip"
local findroute_opts="--invoice --amountMsat --ignoreNodeIds --ignoreShortChannelIds --format --maxFeeMsat --includeLocalChannelCost --pathFindingExperimentName"
local findroutetonode_opts="--nodeId --amountMsat --ignoreNodeIds --ignoreShortChannelIds --format --maxFeeMsat --includeLocalChannelCost --pathFindingExperimentName"
local findroutebetweennodes_opts="--sourceNodeId --targetNodeId --amountMsat --ignoreNodeIds --ignoreShortChannelIds --format --maxFeeMsat --includeLocalChannelCost --pathFindingExperimentName"
local sendonchain_opts="--address --amountSatoshis --confirmationTarget"
local onchaintransactions_opts="--count --skip"
# If the current word starts with a dash (-), it's an option rather than a command
if [[ ${cur} == -* ]]; then
local cmd=""
Expand Down Expand Up @@ -126,6 +131,21 @@ _eclair_cli() {
listpendinginvoices)
COMPREPLY=( $(compgen -W "${listpendinginvoices_opts} ${common_opts}" -- ${cur}) )
;;
findroute)
COMPREPLY=( $(compgen -W "${findroute_opts} ${common_opts}" -- ${cur}) )
;;
findroutetonode)
COMPREPLY=( $(compgen -W "${findroutetonode_opts} ${common_opts}" -- ${cur}) )
;;
findroutebetweennodes)
COMPREPLY=( $(compgen -W "${findroutebetweennodes_opts} ${common_opts}" -- ${cur}) )
;;
sendonchain)
COMPREPLY=( $(compgen -W "${sendonchain_opts} ${common_opts}" -- ${cur}) )
;;
onchaintransactions)
COMPREPLY=( $(compgen -W "${onchaintransactions_opts} ${common_opts}" -- ${cur}) )
;;
*)
COMPREPLY=( $(compgen -W "${common_opts}" -- ${cur}) )
;;
Expand Down
10 changes: 8 additions & 2 deletions src/nativeMain/kotlin/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ fun main(args: Array<String>) {
ForceCloseCommand(resultWriter, apiClientBuilder),
UpdateRelayFeeCommand(resultWriter, apiClientBuilder),
PeersCommand(resultWriter, apiClientBuilder),
UpdateRelayFeeCommand(resultWriter, apiClientBuilder),
NodesCommand(resultWriter, apiClientBuilder),
NodeCommand(resultWriter, apiClientBuilder),
AllChannelsCommand(resultWriter, apiClientBuilder),
Expand All @@ -35,7 +34,14 @@ fun main(args: Array<String>) {
ListReceivedPaymentsCommand(resultWriter, apiClientBuilder),
GetInvoiceCommand(resultWriter, apiClientBuilder),
ListInvoicesCommand(resultWriter, apiClientBuilder),
ListPendingInvoicesCommand(resultWriter, apiClientBuilder)
ListPendingInvoicesCommand(resultWriter, apiClientBuilder),
FindRouteCommand(resultWriter, apiClientBuilder),
FindRouteToNodeCommand(resultWriter, apiClientBuilder),
FindRouteBetweenNodesCommand(resultWriter, apiClientBuilder),
GetNewAddressCommand(resultWriter, apiClientBuilder),
SendOnChainCommand(resultWriter, apiClientBuilder),
OnChainBalanceCommand(resultWriter, apiClientBuilder),
OnChainTransactionsCommand(resultWriter, apiClientBuilder)
)
parser.parse(args)
}
209 changes: 209 additions & 0 deletions src/nativeMain/kotlin/api/EclairClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,49 @@ interface IEclairClient {
count: Int?,
skip: Int?
): Either<ApiError, String>

suspend fun findroute(
invoice: String,
amountMsat: Int?,
ignoreNodeIds: List<String>?,
ignoreShortChannelIds: List<String>?,
format: String?,
maxFeeMsat: Int?,
includeLocalChannelCost: Boolean?,
pathFindingExperimentName: String?
): Either<ApiError, String>


suspend fun findroutetonode(
nodeId: String,
amountMsat: Int,
ignoreNodeIds: List<String>?,
ignoreShortChannelIds: List<String>?,
format: String?,
maxFeeMsat: Int?,
includeLocalChannelCost: Boolean?,
pathFindingExperimentName: String?
): Either<ApiError, String>

suspend fun findroutebetweennodes(
sourceNodeId: String,
targetNodeId: String,
amountMsat: Int,
ignoreNodeIds: List<String>?,
ignoreShortChannelIds: List<String>?,
format: String?,
maxFeeMsat: Int?,
includeLocalChannelCost: Boolean?,
pathFindingExperimentName: String?
): Either<ApiError, String>

suspend fun getnewaddress(): Either<ApiError, String>

suspend fun sendonchain(address: String, amountSatoshis: Int, confirmationTarget: Int): Either<ApiError, String>

suspend fun onchainbalance(): Either<ApiError, String>

suspend fun onchaintransactions(count: Int, skip: Int): Either<ApiError, String>
}

class EclairClient(private val apiHost: String, private val apiPassword: String) : IEclairClient {
Expand Down Expand Up @@ -748,4 +791,170 @@ class EclairClient(private val apiHost: String, private val apiPassword: String)
Either.Left(ApiError(0, e.message ?: "Unknown exception"))
}
}

override suspend fun findroute(
invoice: String,
amountMsat: Int?,
ignoreNodeIds: List<String>?,
ignoreShortChannelIds: List<String>?,
format: String?,
maxFeeMsat: Int?,
includeLocalChannelCost: Boolean?,
pathFindingExperimentName: String?
): Either<ApiError, String> {
return try {
val response: HttpResponse = httpClient.submitForm(
url = "$apiHost/findroute",
formParameters = Parameters.build {
append("invoice", invoice)
amountMsat?.let { append("amountMsat", it.toString()) }
ignoreNodeIds?.let { append("ignoreNodeIds", it.joinToString(",")) }
ignoreShortChannelIds?.let { append("ignoreShortChannelIds", it.joinToString(",")) }
format?.let { append("format", it) }
maxFeeMsat?.let { append("maxFeeMsat", it.toString()) }
includeLocalChannelCost?.let { append("includeLocalChannelCost", it.toString()) }
pathFindingExperimentName?.let { append("pathFindingExperimentName", it) }
}
)
when (response.status) {
HttpStatusCode.OK -> Either.Right((response.bodyAsText()))
else -> Either.Left(convertHttpError(response.status))
}
} catch (e: Throwable) {
Either.Left(ApiError(0, e.message ?: "Unknown exception"))
}
}

override suspend fun findroutetonode(
nodeId: String,
amountMsat: Int,
ignoreNodeIds: List<String>?,
ignoreShortChannelIds: List<String>?,
format: String?,
maxFeeMsat: Int?,
includeLocalChannelCost: Boolean?,
pathFindingExperimentName: String?
): Either<ApiError, String> {
return try {
val response: HttpResponse = httpClient.submitForm(
url = "$apiHost/findroutetonode",
formParameters = Parameters.build {
append("nodeId", nodeId)
append("amountMsat", amountMsat.toString())
ignoreNodeIds?.let { append("ignoreNodeIds", it.joinToString(",")) }
ignoreShortChannelIds?.let { append("ignoreShortChannelIds", it.joinToString(",")) }
format?.let { append("format", it) }
maxFeeMsat?.let { append("maxFeeMsat", it.toString()) }
includeLocalChannelCost?.let { append("includeLocalChannelCost", it.toString()) }
pathFindingExperimentName?.let { append("pathFindingExperimentName", it) }
}
)
when (response.status) {
HttpStatusCode.OK -> Either.Right((response.bodyAsText()))
else -> Either.Left(convertHttpError(response.status))
}
} catch (e: Throwable) {
Either.Left(ApiError(0, e.message ?: "Unknown exception"))
}
}

override suspend fun findroutebetweennodes(
sourceNodeId: String,
targetNodeId: String,
amountMsat: Int,
ignoreNodeIds: List<String>?,
ignoreShortChannelIds: List<String>?,
format: String?,
maxFeeMsat: Int?,
includeLocalChannelCost: Boolean?,
pathFindingExperimentName: String?
): Either<ApiError, String> {
return try {
val response: HttpResponse = httpClient.submitForm(
url = "$apiHost/findroutebetweennodes",
formParameters = Parameters.build {
append("sourceNodeId", sourceNodeId)
append("targetNodeId", targetNodeId)
append("amountMsat", amountMsat.toString())
ignoreNodeIds?.let { append("ignoreNodeIds", it.joinToString(",")) }
ignoreShortChannelIds?.let { append("ignoreShortChannelIds", it.joinToString(",")) }
format?.let { append("format", it) }
maxFeeMsat?.let { append("maxFeeMsat", it.toString()) }
includeLocalChannelCost?.let { append("includeLocalChannelCost", it.toString()) }
pathFindingExperimentName?.let { append("pathFindingExperimentName", it) }
}
)
when (response.status) {
HttpStatusCode.OK -> Either.Right((response.bodyAsText()))
else -> Either.Left(convertHttpError(response.status))
}
} catch (e: Throwable) {
Either.Left(ApiError(0, e.message ?: "Unknown exception"))
}
}

override suspend fun getnewaddress(): Either<ApiError, String> {
return try {
val response: HttpResponse = httpClient.post("$apiHost/getnewaddress")
when (response.status) {
HttpStatusCode.OK -> Either.Right(response.bodyAsText())
else -> Either.Left(convertHttpError(response.status))
}
} catch (e: Throwable) {
Either.Left(ApiError(0, e.message ?: "unknown exception"))
}
}

override suspend fun sendonchain(
address: String,
amountSatoshis: Int,
confirmationTarget: Int
): Either<ApiError, String> {
return try {
val response: HttpResponse = httpClient.submitForm(
url = "$apiHost/sendonchain",
formParameters = Parameters.build {
append("address", address)
append("amountSatoshis", amountSatoshis.toString())
append("confirmationTarget", confirmationTarget.toString())
}
)
when (response.status) {
HttpStatusCode.OK -> Either.Right(Json.decodeFromString(response.bodyAsText()))
else -> Either.Left(convertHttpError(response.status))
}
} catch (e: Throwable) {
Either.Left(ApiError(0, e.message ?: "unknown exception"))
}
}

override suspend fun onchainbalance(): Either<ApiError, String> {
return try {
val response: HttpResponse = httpClient.post("$apiHost/onchainbalance")
when (response.status) {
HttpStatusCode.OK -> Either.Right(response.bodyAsText())
else -> Either.Left(convertHttpError(response.status))
}
} catch (e: Throwable) {
Either.Left(ApiError(0, e.message ?: "unknown exception"))
}
}

override suspend fun onchaintransactions(count: Int, skip: Int): Either<ApiError, String> {
return try {
val response: HttpResponse = httpClient.submitForm(
url = "$apiHost/onchaintransactions",
formParameters = Parameters.build {
append("count", count.toString())
append("skip", skip.toString())
}
)
when (response.status) {
HttpStatusCode.OK -> Either.Right((response.bodyAsText()))
else -> Either.Left(convertHttpError(response.status))
}
} catch (e: Throwable) {
Either.Left(ApiError(0, e.message ?: "Unknown exception"))
}
}
}
66 changes: 66 additions & 0 deletions src/nativeMain/kotlin/commands/FindRoute.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package commands

import IResultWriter
import api.IEclairClientBuilder
import arrow.core.flatMap
import kotlinx.cli.ArgType
import kotlinx.coroutines.runBlocking
import types.FindRouteResponse
import types.Serialization

class FindRouteCommand(
private val resultWriter: IResultWriter,
private val eclairClientBuilder: IEclairClientBuilder
) : BaseCommand(
"findroute",
"Finds a route to the node specified by the invoice. The formats currently supported are nodeId, shortChannelId or full"
) {
private val invoice by option(
ArgType.String,
description = "The invoice containing the destination"
)
private val amountMsat by option(
ArgType.Int,
description = "The amount that should go through the route"
)
private val ignoreNodeIds by option(
ArgType.String,
description = "A list of nodes to exclude from path-finding"
)
private val ignoreShortChannelIds by option(
ArgType.String,
description = "A list of channels to exclude from path-finding"
)
private val format by option(
ArgType.String,
description = "Format that will be used for the resulting route"
)
private val maxFeeMsat by option(
ArgType.Int,
description = "Maximum fee allowed for this payment"
)
private val includeLocalChannelCost by option(
ArgType.Boolean,
description = "If true, the relay fees of local channels will be counted"
)
private val pathFindingExperimentName by option(
ArgType.String,
description = "Name of the path-finding configuration that should be used"
)

override fun execute() = runBlocking {
val eclairClient = eclairClientBuilder.build(host, password)
val result = eclairClient.findroute(
invoice!!,
amountMsat,
ignoreNodeIds?.split(","),
ignoreShortChannelIds?.split(","),
format,
maxFeeMsat,
includeLocalChannelCost,
pathFindingExperimentName
).flatMap { apiResponse -> Serialization.decode<FindRouteResponse>(apiResponse) }
.map { decoded -> Serialization.encode(decoded) }
resultWriter.write(result)
}
}
Loading