mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-14 12:43:03 +02:00
Add resolveMaxSwapFee helper that computes an effective maximum swap fee from --max_swap_fee_sat and --max_swap_fee_ppm CLI flags. When both are set the tighter (lower) cap wins. The helper also rejects early if the server-quoted fee already exceeds the resolved cap.
118 lines
3.5 KiB
Go
118 lines
3.5 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"math/big"
|
|
|
|
"github.com/btcsuite/btcd/btcutil"
|
|
"github.com/lightninglabs/loop/looprpc"
|
|
)
|
|
|
|
const (
|
|
// feePPMBase converts ppm fee limits into satoshi portions.
|
|
feePPMBase = 1_000_000
|
|
|
|
// maxSwapFeeSatLimit is the largest absolute swap-fee cap accepted by
|
|
// the CLI for static loop-ins.
|
|
maxSwapFeeSatLimit = uint64(10_000_000)
|
|
)
|
|
|
|
// resolveMaxSwapFee computes the effective maximum swap fee (in satoshis) for a
|
|
// static address loop-in, applying user-supplied caps when present. If both a
|
|
// satoshi cap and a ppm cap are provided, the tighter (lower) of the two is
|
|
// used. When neither override is set the quoted fee from the server is returned
|
|
// unchanged.
|
|
//
|
|
// The function also performs an early check against the current quote: if the
|
|
// server-quoted fee already exceeds the resolved cap the caller receives an
|
|
// error so the swap can be rejected before confirmation.
|
|
func resolveMaxSwapFee(quoteReq *looprpc.QuoteRequest,
|
|
quote *looprpc.InQuoteResponse,
|
|
satIsSet bool, maxFeeSat uint64,
|
|
ppmIsSet bool, maxFeePpm uint64) (btcutil.Amount, error) {
|
|
|
|
// If a flag is used, make sure the value is within a reasonable range.
|
|
if satIsSet && maxFeeSat == 0 {
|
|
return 0, fmt.Errorf("--max_swap_fee_sat must be positive")
|
|
}
|
|
if satIsSet && maxFeeSat > maxSwapFeeSatLimit {
|
|
return 0, fmt.Errorf("--max_swap_fee_sat must be <= %d",
|
|
maxSwapFeeSatLimit)
|
|
}
|
|
if ppmIsSet && maxFeePpm == 0 {
|
|
return 0, fmt.Errorf("--max_swap_fee_ppm must be positive")
|
|
}
|
|
if ppmIsSet && maxFeePpm > feePPMBase {
|
|
return 0, fmt.Errorf("--max_swap_fee_ppm must be <= %d",
|
|
feePPMBase)
|
|
}
|
|
|
|
// When no override is set, fall back to the quoted fee.
|
|
if !satIsSet && !ppmIsSet {
|
|
return btcutil.Amount(quote.SwapFeeSat), nil
|
|
}
|
|
|
|
// Determine the effective swap amount. For static loop-ins the user
|
|
// may omit the amount, in which case the server derives it from the
|
|
// selected deposits and returns it in QuotedAmt.
|
|
swapAmt := quoteReq.Amt
|
|
if swapAmt == 0 {
|
|
swapAmt = quote.QuotedAmt
|
|
}
|
|
|
|
var ppmCapSat uint64
|
|
|
|
if ppmIsSet {
|
|
if swapAmt <= 0 {
|
|
return 0, fmt.Errorf("swap amount %d invalid for "+
|
|
"ppm fee cap", swapAmt)
|
|
}
|
|
|
|
ppmCapSat = ppmCapForSwapAmount(swapAmt, maxFeePpm)
|
|
if ppmCapSat == 0 {
|
|
return 0, fmt.Errorf("ppm cap rounds to 0 sat for "+
|
|
"swap amount %d; use --max_swap_fee_sat "+
|
|
"instead", swapAmt)
|
|
}
|
|
}
|
|
|
|
// Pick the tighter cap when both are set.
|
|
var resolvedSat uint64
|
|
switch {
|
|
case satIsSet && ppmIsSet:
|
|
resolvedSat = min(maxFeeSat, ppmCapSat)
|
|
|
|
case satIsSet:
|
|
resolvedSat = maxFeeSat
|
|
|
|
// maxFeePpm is bounded to feePPMBase, so the ppm-derived cap
|
|
// cannot exceed swapAmt and therefore fits into uint64.
|
|
default:
|
|
resolvedSat = ppmCapSat
|
|
}
|
|
|
|
// Reject early if the quote already exceeds the cap.
|
|
if quote.SwapFeeSat > int64(resolvedSat) {
|
|
return 0, fmt.Errorf("quoted swap fee %d sat exceeds "+
|
|
"maximum allowed %d sat", quote.SwapFeeSat, resolvedSat)
|
|
}
|
|
|
|
return btcutil.Amount(int64(resolvedSat)), nil
|
|
}
|
|
|
|
// ppmCapForSwapAmount converts a ppm fee limit to a satoshi cap for the given
|
|
// swap amount, rounding down to whole satoshis. Big integers are used
|
|
// internally to avoid intermediate multiplication overflow, but the returned
|
|
// value always fits into uint64 because callers bound maxFeePpm to
|
|
// feePPMBase.
|
|
func ppmCapForSwapAmount(swapAmt int64, maxFeePpm uint64) uint64 {
|
|
swapAmtBig := new(big.Int).SetInt64(swapAmt)
|
|
maxFeePpmBig := new(big.Int).SetUint64(maxFeePpm)
|
|
|
|
capSat := new(big.Int).Quo(
|
|
new(big.Int).Mul(swapAmtBig, maxFeePpmBig),
|
|
big.NewInt(feePPMBase),
|
|
)
|
|
|
|
return capSat.Uint64()
|
|
}
|