Skip to content

Commit

Permalink
Add a best-effort method for estimating liquidity fees (ACINQ#107)
Browse files Browse the repository at this point in the history
Returns a best-effort **estimate** of liquidity fees for a given amount.

Note that:
- it depends on the current mining feerate, which is volatile
- this estimate is the full cost, it doesn't take into account the `fee credit` (which will be deduced)
- if you receive `A` sat with a setting of `--auto-liquidity B`, then the actual liquidity amount will be `A+B` sat.

```
./phoenix-cli estimateliquidityfees --amountSat 2000000
{
    "miningFeeSat": 1219,
    "serviceFeeSat": 20000
}

```

See ACINQ#88,ACINQ#104.
  • Loading branch information
pm47 authored and vincenzopalazzo committed Nov 7, 2024
1 parent bab03bc commit 3b8d32d
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 22 deletions.
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

0 comments on commit 3b8d32d

Please sign in to comment.