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

Add a best-effort method for estimating liquidity fees #107

Merged
merged 1 commit into from
Aug 28, 2024
Merged
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
9 changes: 9 additions & 0 deletions src/commonMain/kotlin/fr/acinq/lightning/bin/Api.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import fr.acinq.lightning.BuildVersions
import fr.acinq.lightning.Lightning.randomBytes32
import fr.acinq.lightning.NodeParams
import fr.acinq.lightning.bin.api.WebsocketProtocolAuthenticationProvider
import fr.acinq.lightning.bin.conf.LSP
import fr.acinq.lightning.bin.db.SqlitePaymentsDb
import fr.acinq.lightning.bin.db.WalletPaymentId
import fr.acinq.lightning.bin.json.ApiType.*
Expand Down Expand Up @@ -56,6 +57,8 @@ import io.ktor.server.routing.*
import io.ktor.server.websocket.*
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
Expand Down Expand Up @@ -161,6 +164,12 @@ class Api(
.sum().truncateToSatoshi()
call.respond(Balance(balance, nodeParams.feeCredit.value))
}
get("estimateliquidityfees") {
val amount = call.parameters.getLong("amountSat").sat
val feerate = peer.onChainFeeratesFlow.filterNotNull().first().fundingFeerate
val liquidityFees = LSP.liquidityFees(amount, feerate, isNew = peer.channels.isEmpty())
call.respond(LiquidityFees(liquidityFees))
}
get("listchannels") {
call.respond(peer.channels.values.toList())
}
Expand Down
54 changes: 32 additions & 22 deletions src/commonMain/kotlin/fr/acinq/lightning/bin/conf/Lsp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ package fr.acinq.lightning.bin.conf

import fr.acinq.bitcoin.Chain
import fr.acinq.bitcoin.PublicKey
import fr.acinq.bitcoin.Satoshi
import fr.acinq.lightning.*
import fr.acinq.lightning.blockchain.fee.FeeratePerKw
import fr.acinq.lightning.utils.msat
import fr.acinq.lightning.utils.sat
import fr.acinq.lightning.wire.LiquidityAds


data class LSP(val walletParams: WalletParams, val swapInXpub: String) {
Expand Down Expand Up @@ -53,27 +56,34 @@ data class LSP(val walletParams: WalletParams, val swapInXpub: String) {
else -> error("unsupported chain $chain")
}

// fun liquidityLeaseRate(amount: Satoshi): LiquidityAds.LeaseRate {
// // WARNING : THIS MUST BE KEPT IN SYNC WITH LSP OTHERWISE FUNDING REQUEST WILL BE REJECTED BY PHOENIX
// val fundingWeight = if (amount <= 100_000.sat) {
// 271 * 2 // 2-inputs (wpkh) / 0-change
// } else if (amount <= 250_000.sat) {
// 271 * 2 // 2-inputs (wpkh) / 0-change
// } else if (amount <= 500_000.sat) {
// 271 * 4 // 4-inputs (wpkh) / 0-change
// } else if (amount <= 1_000_000.sat) {
// 271 * 4 // 4-inputs (wpkh) / 0-change
// } else {
// 271 * 6 // 6-inputs (wpkh) / 0-change
// }
// return LiquidityAds.LeaseRate(
// leaseDuration = 0,
// fundingWeight = fundingWeight,
// leaseFeeProportional = 100, // 1%
// leaseFeeBase = 0.sat,
// maxRelayFeeProportional = 100,
// maxRelayFeeBase = 1_000.msat
// )
// }
fun liquidityFees(amount: Satoshi, feerate: FeeratePerKw, isNew: Boolean): LiquidityAds.LeaseFees {
val creationFee = if (isNew) 1_000.sat else 0.sat
val leaseRate = liquidityLeaseRate(amount)
val leaseFees = leaseRate.fees(feerate, requestedAmount = amount, contributedAmount = amount)
return leaseFees.copy(serviceFee = creationFee + leaseFees.serviceFee)
}

private fun liquidityLeaseRate(amount: Satoshi): LiquidityAds.LeaseRate {
// WARNING : THIS MUST BE KEPT IN SYNC WITH LSP OTHERWISE FUNDING REQUEST WILL BE REJECTED BY PHOENIX
val fundingWeight = if (amount <= 100_000.sat) {
271 * 2 // 2-inputs (wpkh) / 0-change
} else if (amount <= 250_000.sat) {
271 * 2 // 2-inputs (wpkh) / 0-change
} else if (amount <= 500_000.sat) {
271 * 4 // 4-inputs (wpkh) / 0-change
} else if (amount <= 1_000_000.sat) {
271 * 4 // 4-inputs (wpkh) / 0-change
} else {
271 * 6 // 6-inputs (wpkh) / 0-change
}
return LiquidityAds.LeaseRate(
leaseDuration = 0,
fundingWeight = fundingWeight,
leaseFeeProportional = 100, // 1%
leaseFeeBase = 0.sat,
maxRelayFeeProportional = 100,
maxRelayFeeBase = 1_000.msat
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import fr.acinq.lightning.json.JsonSerializers
import fr.acinq.lightning.payment.Bolt11Invoice
import fr.acinq.lightning.payment.OfferPaymentMetadata
import fr.acinq.lightning.utils.UUID
import fr.acinq.lightning.wire.LiquidityAds
import io.ktor.http.*
import kotlinx.datetime.Clock
import kotlinx.serialization.SerialName
Expand Down Expand Up @@ -74,6 +75,11 @@ sealed class ApiType {
@Serializable
data class Balance(@SerialName("balanceSat") val amount: Satoshi, @SerialName("feeCreditSat") val feeCredit: Satoshi) : ApiType()

@Serializable
data class LiquidityFees(@SerialName("miningFeeSat") val miningFee: Satoshi, @SerialName("serviceFeeSat") val serviceFee: Satoshi) : ApiType() {
constructor(leaseFees: LiquidityAds.LeaseFees) : this(leaseFees.miningFee, leaseFees.serviceFee)
}

@Serializable
data class GeneratedInvoice(@SerialName("amountSat") val amount: Satoshi?, val paymentHash: ByteVector32, val serialized: String) : ApiType()

Expand Down
12 changes: 12 additions & 0 deletions src/commonMain/kotlin/fr/acinq/lightning/cli/PhoenixCli.kt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ fun main(args: Array<String>) =
.subcommands(
GetInfo(),
GetBalance(),
EstimateLiquidityFees(),
ListChannels(),
GetOutgoingPayment(),
ListOutgoingPayments(),
Expand Down Expand Up @@ -139,6 +140,17 @@ class GetBalance : PhoenixCliCommand(name = "getbalance", help = "Returns your c
}
}

class EstimateLiquidityFees : PhoenixCliCommand(name = "estimateliquidityfees", help = "Estimates the liquidity fees for a given amount, at current feerates.") {
private val amountSat by option("--amountSat").long().required()
override suspend fun httpRequest() = commonOptions.httpClient.use {
it.get(url = commonOptions.baseUrl / "estimateliquidityfees") {
url {
parameters.append("amountSat", amountSat.toString())
}
}
}
}

class ListChannels : PhoenixCliCommand(name = "listchannels", help = "List all channels") {
override suspend fun httpRequest() = commonOptions.httpClient.use {
it.get(url = commonOptions.baseUrl / "listchannels")
Expand Down