mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
liquidity: move fees behind interface
This commit is contained in:
parent
9ce7fe4df9
commit
c778124718
7 changed files with 419 additions and 289 deletions
|
|
@ -70,32 +70,37 @@ func TestAutoLoopDisabled(t *testing.T) {
|
|||
func TestAutoLoopEnabled(t *testing.T) {
|
||||
defer test.Guard(t)()
|
||||
|
||||
channels := []lndclient.ChannelInfo{
|
||||
channel1, channel2,
|
||||
}
|
||||
var (
|
||||
channels = []lndclient.ChannelInfo{
|
||||
channel1, channel2,
|
||||
}
|
||||
|
||||
// Create a set of parameters with autoloop enabled. The autoloop budget
|
||||
// is set to allow exactly 2 swaps at the prices that we set in our
|
||||
// test quotes.
|
||||
params := Parameters{
|
||||
Autoloop: true,
|
||||
AutoFeeBudget: 40066,
|
||||
AutoFeeStartDate: testTime,
|
||||
MaxAutoInFlight: 2,
|
||||
FailureBackOff: time.Hour,
|
||||
SweepFeeRateLimit: 20000,
|
||||
SweepConfTarget: 10,
|
||||
MaximumPrepay: 20000,
|
||||
MaximumSwapFeePPM: 1000,
|
||||
MaximumRoutingFeePPM: 1000,
|
||||
MaximumPrepayRoutingFeePPM: 1000,
|
||||
MaximumMinerFee: 20000,
|
||||
ChannelRules: map[lnwire.ShortChannelID]*ThresholdRule{
|
||||
chanID1: chanRule,
|
||||
chanID2: chanRule,
|
||||
},
|
||||
}
|
||||
swapFeePPM uint64 = 1000
|
||||
routeFeePPM uint64 = 1000
|
||||
prepayFeePPM uint64 = 1000
|
||||
prepayAmount = btcutil.Amount(20000)
|
||||
maxMiner = btcutil.Amount(20000)
|
||||
|
||||
// Create a set of parameters with autoloop enabled. The
|
||||
// autoloop budget is set to allow exactly 2 swaps at the prices
|
||||
// that we set in our test quotes.
|
||||
params = Parameters{
|
||||
Autoloop: true,
|
||||
AutoFeeBudget: 40066,
|
||||
AutoFeeStartDate: testTime,
|
||||
MaxAutoInFlight: 2,
|
||||
FailureBackOff: time.Hour,
|
||||
SweepConfTarget: 10,
|
||||
FeeLimit: NewFeeCategoryLimit(
|
||||
swapFeePPM, routeFeePPM, prepayFeePPM, maxMiner,
|
||||
prepayAmount, 20000,
|
||||
),
|
||||
ChannelRules: map[lnwire.ShortChannelID]*ThresholdRule{
|
||||
chanID1: chanRule,
|
||||
chanID2: chanRule,
|
||||
},
|
||||
}
|
||||
)
|
||||
c := newAutoloopTestCtx(t, params, channels, testRestrictions)
|
||||
c.start()
|
||||
|
||||
|
|
@ -104,18 +109,20 @@ func TestAutoLoopEnabled(t *testing.T) {
|
|||
var (
|
||||
amt = chan1Rec.Amount
|
||||
|
||||
maxSwapFee = ppmToSat(amt, params.MaximumSwapFeePPM)
|
||||
maxSwapFee = ppmToSat(amt, swapFeePPM)
|
||||
|
||||
// Create a quote that is within our limits. We do not set miner
|
||||
// fee because this value is not actually set by the server.
|
||||
quote1 = &loop.LoopOutQuote{
|
||||
SwapFee: maxSwapFee,
|
||||
PrepayAmount: params.MaximumPrepay - 10,
|
||||
PrepayAmount: prepayAmount - 10,
|
||||
MinerFee: maxMiner - 10,
|
||||
}
|
||||
|
||||
quote2 = &loop.LoopOutQuote{
|
||||
SwapFee: maxSwapFee,
|
||||
PrepayAmount: params.MaximumPrepay - 20,
|
||||
PrepayAmount: prepayAmount - 20,
|
||||
MinerFee: maxMiner - 10,
|
||||
}
|
||||
|
||||
quoteRequest = &loop.LoopOutQuoteRequest{
|
||||
|
|
@ -134,18 +141,17 @@ func TestAutoLoopEnabled(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
maxRouteFee = ppmToSat(amt, params.MaximumRoutingFeePPM)
|
||||
maxRouteFee = ppmToSat(amt, routeFeePPM)
|
||||
|
||||
chan1Swap = &loop.OutRequest{
|
||||
Amount: amt,
|
||||
MaxSwapRoutingFee: maxRouteFee,
|
||||
MaxPrepayRoutingFee: ppmToSat(
|
||||
quote1.PrepayAmount,
|
||||
params.MaximumPrepayRoutingFeePPM,
|
||||
quote1.PrepayAmount, prepayFeePPM,
|
||||
),
|
||||
MaxSwapFee: quote1.SwapFee,
|
||||
MaxPrepayAmount: quote1.PrepayAmount,
|
||||
MaxMinerFee: params.MaximumMinerFee,
|
||||
MaxMinerFee: maxMiner,
|
||||
SweepConfTarget: params.SweepConfTarget,
|
||||
OutgoingChanSet: loopdb.ChannelSet{chanID1.ToUint64()},
|
||||
Label: labels.AutoloopLabel(swap.TypeOut),
|
||||
|
|
@ -156,12 +162,11 @@ func TestAutoLoopEnabled(t *testing.T) {
|
|||
Amount: amt,
|
||||
MaxSwapRoutingFee: maxRouteFee,
|
||||
MaxPrepayRoutingFee: ppmToSat(
|
||||
quote2.PrepayAmount,
|
||||
params.MaximumPrepayRoutingFeePPM,
|
||||
quote2.PrepayAmount, routeFeePPM,
|
||||
),
|
||||
MaxSwapFee: quote2.SwapFee,
|
||||
MaxPrepayAmount: quote2.PrepayAmount,
|
||||
MaxMinerFee: params.MaximumMinerFee,
|
||||
MaxMinerFee: maxMiner,
|
||||
SweepConfTarget: params.SweepConfTarget,
|
||||
OutgoingChanSet: loopdb.ChannelSet{chanID2.ToUint64()},
|
||||
Label: labels.AutoloopLabel(swap.TypeOut),
|
||||
|
|
@ -216,7 +221,7 @@ func TestAutoLoopEnabled(t *testing.T) {
|
|||
State: loopdb.StateSuccess,
|
||||
Cost: loopdb.SwapCost{
|
||||
Server: quote1.SwapFee,
|
||||
Onchain: params.MaximumMinerFee,
|
||||
Onchain: maxMiner,
|
||||
Offchain: maxRouteFee +
|
||||
chan1Rec.MaxPrepayRoutingFee,
|
||||
},
|
||||
|
|
@ -273,42 +278,48 @@ func TestAutoLoopEnabled(t *testing.T) {
|
|||
func TestCompositeRules(t *testing.T) {
|
||||
defer test.Guard(t)()
|
||||
|
||||
// Setup our channels so that we have two channels with peer 2, and
|
||||
// a single channel with peer 1.
|
||||
channel3 := lndclient.ChannelInfo{
|
||||
ChannelID: chanID3.ToUint64(),
|
||||
PubKeyBytes: peer2,
|
||||
LocalBalance: 10000,
|
||||
RemoteBalance: 0,
|
||||
Capacity: 10000,
|
||||
}
|
||||
var (
|
||||
// Setup our channels so that we have two channels with peer 2,
|
||||
// and a single channel with peer 1.
|
||||
channel3 = lndclient.ChannelInfo{
|
||||
ChannelID: chanID3.ToUint64(),
|
||||
PubKeyBytes: peer2,
|
||||
LocalBalance: 10000,
|
||||
RemoteBalance: 0,
|
||||
Capacity: 10000,
|
||||
}
|
||||
|
||||
channels := []lndclient.ChannelInfo{
|
||||
channel1, channel2, channel3,
|
||||
}
|
||||
channels = []lndclient.ChannelInfo{
|
||||
channel1, channel2, channel3,
|
||||
}
|
||||
|
||||
// Create a set of parameters with autoloop enabled, set our budget to
|
||||
// a value that will easily accommodate our two swaps.
|
||||
params := Parameters{
|
||||
Autoloop: true,
|
||||
AutoFeeBudget: 100000,
|
||||
AutoFeeStartDate: testTime,
|
||||
MaxAutoInFlight: 2,
|
||||
FailureBackOff: time.Hour,
|
||||
SweepFeeRateLimit: 20000,
|
||||
SweepConfTarget: 10,
|
||||
MaximumPrepay: 20000,
|
||||
MaximumSwapFeePPM: 1000,
|
||||
MaximumRoutingFeePPM: 1000,
|
||||
MaximumPrepayRoutingFeePPM: 1000,
|
||||
MaximumMinerFee: 20000,
|
||||
ChannelRules: map[lnwire.ShortChannelID]*ThresholdRule{
|
||||
chanID1: chanRule,
|
||||
},
|
||||
PeerRules: map[route.Vertex]*ThresholdRule{
|
||||
peer2: chanRule,
|
||||
},
|
||||
}
|
||||
swapFeePPM uint64 = 1000
|
||||
routeFeePPM uint64 = 1000
|
||||
prepayFeePPM uint64 = 1000
|
||||
prepayAmount = btcutil.Amount(20000)
|
||||
maxMiner = btcutil.Amount(20000)
|
||||
|
||||
// Create a set of parameters with autoloop enabled, set our
|
||||
// budget to a value that will easily accommodate our two swaps.
|
||||
params = Parameters{
|
||||
FeeLimit: NewFeeCategoryLimit(
|
||||
swapFeePPM, routeFeePPM, prepayFeePPM, maxMiner,
|
||||
prepayAmount, 20000,
|
||||
),
|
||||
Autoloop: true,
|
||||
AutoFeeBudget: 100000,
|
||||
AutoFeeStartDate: testTime,
|
||||
MaxAutoInFlight: 2,
|
||||
FailureBackOff: time.Hour,
|
||||
SweepConfTarget: 10,
|
||||
ChannelRules: map[lnwire.ShortChannelID]*ThresholdRule{
|
||||
chanID1: chanRule,
|
||||
},
|
||||
PeerRules: map[route.Vertex]*ThresholdRule{
|
||||
peer2: chanRule,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
c := newAutoloopTestCtx(t, params, channels, testRestrictions)
|
||||
c.start()
|
||||
|
|
@ -320,11 +331,12 @@ func TestCompositeRules(t *testing.T) {
|
|||
// our budget, with an amount which would balance the peer
|
||||
/// across all of its channels.
|
||||
peerAmount = btcutil.Amount(15000)
|
||||
maxPeerSwapFee = ppmToSat(peerAmount, params.MaximumSwapFeePPM)
|
||||
maxPeerSwapFee = ppmToSat(peerAmount, swapFeePPM)
|
||||
|
||||
peerSwapQuote = &loop.LoopOutQuote{
|
||||
SwapFee: maxPeerSwapFee,
|
||||
PrepayAmount: params.MaximumPrepay - 20,
|
||||
PrepayAmount: prepayAmount - 20,
|
||||
MinerFee: maxMiner - 10,
|
||||
}
|
||||
|
||||
peerSwapQuoteRequest = &loop.LoopOutQuoteRequest{
|
||||
|
|
@ -332,20 +344,17 @@ func TestCompositeRules(t *testing.T) {
|
|||
SweepConfTarget: params.SweepConfTarget,
|
||||
}
|
||||
|
||||
maxPeerRouteFee = ppmToSat(
|
||||
peerAmount, params.MaximumRoutingFeePPM,
|
||||
)
|
||||
maxPeerRouteFee = ppmToSat(peerAmount, routeFeePPM)
|
||||
|
||||
peerSwap = &loop.OutRequest{
|
||||
Amount: peerAmount,
|
||||
MaxSwapRoutingFee: maxPeerRouteFee,
|
||||
MaxPrepayRoutingFee: ppmToSat(
|
||||
peerSwapQuote.PrepayAmount,
|
||||
params.MaximumPrepayRoutingFeePPM,
|
||||
peerSwapQuote.PrepayAmount, routeFeePPM,
|
||||
),
|
||||
MaxSwapFee: peerSwapQuote.SwapFee,
|
||||
MaxPrepayAmount: peerSwapQuote.PrepayAmount,
|
||||
MaxMinerFee: params.MaximumMinerFee,
|
||||
MaxMinerFee: maxMiner,
|
||||
SweepConfTarget: params.SweepConfTarget,
|
||||
OutgoingChanSet: loopdb.ChannelSet{
|
||||
chanID2.ToUint64(), chanID3.ToUint64(),
|
||||
|
|
@ -356,11 +365,12 @@ func TestCompositeRules(t *testing.T) {
|
|||
// Create a quote for our single channel swap that is within
|
||||
// our budget.
|
||||
chanAmount = chan1Rec.Amount
|
||||
maxChanSwapFee = ppmToSat(chanAmount, params.MaximumSwapFeePPM)
|
||||
maxChanSwapFee = ppmToSat(chanAmount, swapFeePPM)
|
||||
|
||||
channelSwapQuote = &loop.LoopOutQuote{
|
||||
SwapFee: maxChanSwapFee,
|
||||
PrepayAmount: params.MaximumPrepay - 10,
|
||||
PrepayAmount: prepayAmount - 10,
|
||||
MinerFee: maxMiner - 10,
|
||||
}
|
||||
|
||||
chanSwapQuoteRequest = &loop.LoopOutQuoteRequest{
|
||||
|
|
@ -368,20 +378,17 @@ func TestCompositeRules(t *testing.T) {
|
|||
SweepConfTarget: params.SweepConfTarget,
|
||||
}
|
||||
|
||||
maxChanRouteFee = ppmToSat(
|
||||
chanAmount, params.MaximumRoutingFeePPM,
|
||||
)
|
||||
maxChanRouteFee = ppmToSat(chanAmount, routeFeePPM)
|
||||
|
||||
chanSwap = &loop.OutRequest{
|
||||
Amount: chanAmount,
|
||||
MaxSwapRoutingFee: maxChanRouteFee,
|
||||
MaxPrepayRoutingFee: ppmToSat(
|
||||
channelSwapQuote.PrepayAmount,
|
||||
params.MaximumPrepayRoutingFeePPM,
|
||||
channelSwapQuote.PrepayAmount, routeFeePPM,
|
||||
),
|
||||
MaxSwapFee: channelSwapQuote.SwapFee,
|
||||
MaxPrepayAmount: channelSwapQuote.PrepayAmount,
|
||||
MaxMinerFee: params.MaximumMinerFee,
|
||||
MaxMinerFee: maxMiner,
|
||||
SweepConfTarget: params.SweepConfTarget,
|
||||
OutgoingChanSet: loopdb.ChannelSet{chanID1.ToUint64()},
|
||||
Label: labels.AutoloopLabel(swap.TypeOut),
|
||||
|
|
|
|||
|
|
@ -65,10 +65,13 @@ func newAutoloopTestCtx(t *testing.T, parameters Parameters,
|
|||
// Create a mock lnd and set our expected fee rate for sweeps to our
|
||||
// sweep fee rate limit value.
|
||||
lnd := test.NewMockLnd()
|
||||
lnd.SetFeeEstimate(
|
||||
defaultParameters.SweepConfTarget,
|
||||
defaultParameters.SweepFeeRateLimit,
|
||||
)
|
||||
|
||||
categories, ok := parameters.FeeLimit.(*FeeCategoryLimit)
|
||||
if ok {
|
||||
lnd.SetFeeEstimate(
|
||||
parameters.SweepConfTarget, categories.SweepFeeRateLimit,
|
||||
)
|
||||
}
|
||||
|
||||
testCtx := &autoloopTestCtx{
|
||||
t: t,
|
||||
|
|
|
|||
226
liquidity/fees.go
Normal file
226
liquidity/fees.go
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
package liquidity
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightninglabs/loop"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
)
|
||||
|
||||
const (
|
||||
// defaultSwapFeePPM is the default limit we place on swap fees,
|
||||
// expressed as parts per million of swap volume, 0.5%.
|
||||
defaultSwapFeePPM = 5000
|
||||
|
||||
// defaultRoutingFeePPM is the default limit we place on routing fees
|
||||
// for the swap invoice, expressed as parts per million of swap volume,
|
||||
// 1%.
|
||||
defaultRoutingFeePPM = 10000
|
||||
|
||||
// defaultRoutingFeePPM is the default limit we place on routing fees
|
||||
// for the prepay invoice, expressed as parts per million of prepay
|
||||
// volume, 0.5%.
|
||||
defaultPrepayRoutingFeePPM = 5000
|
||||
|
||||
// defaultMaximumMinerFee is the default limit we place on miner fees
|
||||
// per swap. We apply a multiplier to this default fee to guard against
|
||||
// the case where we have broadcast the preimage, then fees spike and
|
||||
// we need to sweep the preimage.
|
||||
defaultMaximumMinerFee = 15000 * 100
|
||||
|
||||
// defaultMaximumPrepay is the default limit we place on prepay
|
||||
// invoices.
|
||||
defaultMaximumPrepay = 30000
|
||||
|
||||
// defaultSweepFeeRateLimit is the default limit we place on estimated
|
||||
// sweep fees, (750 * 4 /1000 = 3 sat/vByte).
|
||||
defaultSweepFeeRateLimit = chainfee.SatPerKWeight(750)
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrZeroMinerFee is returned if a zero maximum miner fee is set.
|
||||
ErrZeroMinerFee = errors.New("maximum miner fee must be non-zero")
|
||||
|
||||
// ErrZeroSwapFeePPM is returned if a zero server fee ppm is set.
|
||||
ErrZeroSwapFeePPM = errors.New("swap fee PPM must be non-zero")
|
||||
|
||||
// ErrZeroRoutingPPM is returned if a zero routing fee ppm is set.
|
||||
ErrZeroRoutingPPM = errors.New("routing fee PPM must be non-zero")
|
||||
|
||||
// ErrZeroPrepayPPM is returned if a zero prepay routing fee ppm is set.
|
||||
ErrZeroPrepayPPM = errors.New("prepay routing fee PPM must be non-zero")
|
||||
|
||||
// ErrZeroPrepay is returned if a zero maximum prepay is set.
|
||||
ErrZeroPrepay = errors.New("maximum prepay must be non-zero")
|
||||
|
||||
// ErrInvalidSweepFeeRateLimit is returned if an invalid sweep fee limit
|
||||
// is set.
|
||||
ErrInvalidSweepFeeRateLimit = fmt.Errorf("sweep fee rate limit must "+
|
||||
"be > %v sat/vByte",
|
||||
satPerKwToSatPerVByte(chainfee.AbsoluteFeePerKwFloor))
|
||||
)
|
||||
|
||||
// Compile time assertion that FeeCategoryLimit implements FeeLimit.
|
||||
var _ FeeLimit = (*FeeCategoryLimit)(nil)
|
||||
|
||||
// FeeCategoryLimit is an implementation of the fee limit interface which sets
|
||||
// a specific fee limit per fee category.
|
||||
type FeeCategoryLimit struct {
|
||||
// MaximumPrepay is the maximum prepay amount we are willing to pay per
|
||||
// swap.
|
||||
MaximumPrepay btcutil.Amount
|
||||
|
||||
// MaximumSwapFeePPM is the maximum server fee we are willing to pay per
|
||||
// swap expressed as parts per million of the swap volume.
|
||||
MaximumSwapFeePPM uint64
|
||||
|
||||
// MaximumRoutingFeePPM is the maximum off-chain routing fee we
|
||||
// are willing to pay for off chain invoice routing fees per swap,
|
||||
// expressed as parts per million of the swap amount.
|
||||
MaximumRoutingFeePPM uint64
|
||||
|
||||
// MaximumPrepayRoutingFeePPM is the maximum off-chain routing fee we
|
||||
// are willing to pay for off chain prepay routing fees per swap,
|
||||
// expressed as parts per million of the prepay amount.
|
||||
MaximumPrepayRoutingFeePPM uint64
|
||||
|
||||
// MaximumMinerFee is the maximum on chain fee that we cap our miner
|
||||
// fee at in case where we need to claim on chain because we have
|
||||
// revealed the preimage, but fees have spiked. We will not initiate a
|
||||
// swap if we estimate that the sweep cost will be above our sweep
|
||||
// fee limit, and we use fee estimates at time of sweep to set our fees,
|
||||
// so this is just a sane cap covering the special case where we need to
|
||||
// sweep during a fee spike.
|
||||
MaximumMinerFee btcutil.Amount
|
||||
|
||||
// SweepFeeRateLimit is the limit that we place on our estimated sweep
|
||||
// fee. A swap will not be suggested if estimated fee rate is above this
|
||||
// value.
|
||||
SweepFeeRateLimit chainfee.SatPerKWeight
|
||||
}
|
||||
|
||||
// NewFeeCategoryLimit created a new fee limit struct which sets individual
|
||||
// fee limits per category.
|
||||
func NewFeeCategoryLimit(swapFeePPM, routingFeePPM, prepayFeePPM uint64,
|
||||
minerFee, prepay btcutil.Amount,
|
||||
sweepLimit chainfee.SatPerKWeight) *FeeCategoryLimit {
|
||||
|
||||
return &FeeCategoryLimit{
|
||||
MaximumPrepay: prepay,
|
||||
MaximumSwapFeePPM: swapFeePPM,
|
||||
MaximumRoutingFeePPM: routingFeePPM,
|
||||
MaximumPrepayRoutingFeePPM: prepayFeePPM,
|
||||
MaximumMinerFee: minerFee,
|
||||
SweepFeeRateLimit: sweepLimit,
|
||||
}
|
||||
}
|
||||
|
||||
func defaultFeeCategoryLimit() *FeeCategoryLimit {
|
||||
return NewFeeCategoryLimit(defaultSwapFeePPM, defaultRoutingFeePPM,
|
||||
defaultPrepayRoutingFeePPM, defaultMaximumMinerFee,
|
||||
defaultMaximumPrepay, defaultSweepFeeRateLimit)
|
||||
}
|
||||
|
||||
// String returns the string representation of our fee category limits.
|
||||
func (f *FeeCategoryLimit) String() string {
|
||||
return fmt.Sprintf("fee categories: maximum prepay: %v, maximum "+
|
||||
"miner fee: %v, maximum swap fee ppm: %v, maximum "+
|
||||
"routing fee ppm: %v, maximum prepay routing fee ppm: %v,"+
|
||||
"sweep fee limit: %v", f.MaximumPrepay, f.MaximumMinerFee,
|
||||
f.MaximumSwapFeePPM, f.MaximumRoutingFeePPM,
|
||||
f.MaximumPrepayRoutingFeePPM, f.SweepFeeRateLimit,
|
||||
)
|
||||
}
|
||||
|
||||
func (f *FeeCategoryLimit) validate() error {
|
||||
// Check that we have non-zero fee limits.
|
||||
if f.MaximumSwapFeePPM == 0 {
|
||||
return ErrZeroSwapFeePPM
|
||||
}
|
||||
|
||||
if f.MaximumRoutingFeePPM == 0 {
|
||||
return ErrZeroRoutingPPM
|
||||
}
|
||||
|
||||
if f.MaximumPrepayRoutingFeePPM == 0 {
|
||||
return ErrZeroPrepayPPM
|
||||
}
|
||||
|
||||
if f.MaximumPrepay == 0 {
|
||||
return ErrZeroPrepay
|
||||
}
|
||||
|
||||
if f.MaximumMinerFee == 0 {
|
||||
return ErrZeroMinerFee
|
||||
}
|
||||
|
||||
// Check that our sweep limit is above our minimum fee rate. We use
|
||||
// absolute fee floor rather than kw floor because we will allow users
|
||||
// to specify fee rate is sat/vByte and want to allow 1 sat/vByte.
|
||||
if f.SweepFeeRateLimit < chainfee.AbsoluteFeePerKwFloor {
|
||||
return ErrInvalidSweepFeeRateLimit
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// mayLoopOut checks our estimated loop out sweep fee against our sweep limit.
|
||||
func (f *FeeCategoryLimit) mayLoopOut(estimate chainfee.SatPerKWeight) error {
|
||||
if estimate > f.SweepFeeRateLimit {
|
||||
log.Debugf("Current fee estimate to sweep: %v sat/vByte "+
|
||||
"exceeds limit of: %v sat/vByte",
|
||||
satPerKwToSatPerVByte(estimate),
|
||||
satPerKwToSatPerVByte(f.SweepFeeRateLimit))
|
||||
|
||||
return newReasonError(ReasonSweepFees)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// loopOutLimits checks whether the quote provided is within our fee limits.
|
||||
func (f *FeeCategoryLimit) loopOutLimits(amount btcutil.Amount,
|
||||
quote *loop.LoopOutQuote) error {
|
||||
|
||||
maxFee := ppmToSat(amount, f.MaximumSwapFeePPM)
|
||||
|
||||
if quote.SwapFee > maxFee {
|
||||
log.Debugf("quoted swap fee: %v > maximum swap fee: %v",
|
||||
quote.SwapFee, maxFee)
|
||||
|
||||
return newReasonError(ReasonSwapFee)
|
||||
}
|
||||
|
||||
if quote.MinerFee > f.MaximumMinerFee {
|
||||
log.Debugf("quoted miner fee: %v > maximum miner "+
|
||||
"fee: %v", quote.MinerFee, f.MaximumMinerFee)
|
||||
|
||||
return newReasonError(ReasonMinerFee)
|
||||
}
|
||||
|
||||
if quote.PrepayAmount > f.MaximumPrepay {
|
||||
log.Debugf("quoted prepay: %v > maximum prepay: %v",
|
||||
quote.PrepayAmount, f.MaximumPrepay)
|
||||
|
||||
return newReasonError(ReasonPrepay)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// loopOutFees returns the prepay and routing and miner fees we are willing to
|
||||
// pay for a loop out swap.
|
||||
func (f *FeeCategoryLimit) loopOutFees(amount btcutil.Amount,
|
||||
quote *loop.LoopOutQuote) (btcutil.Amount, btcutil.Amount,
|
||||
btcutil.Amount) {
|
||||
|
||||
prepayMaxFee := ppmToSat(
|
||||
quote.PrepayAmount, f.MaximumPrepayRoutingFeePPM,
|
||||
)
|
||||
|
||||
routeMaxFee := ppmToSat(amount, f.MaximumRoutingFeePPM)
|
||||
|
||||
return prepayMaxFee, routeMaxFee, f.MaximumMinerFee
|
||||
}
|
||||
|
|
@ -3,9 +3,33 @@ package liquidity
|
|||
import (
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightninglabs/loop"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
// FeeLimit is an interface implemented by different strategies for limiting
|
||||
// the fees we pay for autoloops.
|
||||
type FeeLimit interface {
|
||||
// String returns the string representation of fee limits.
|
||||
String() string
|
||||
|
||||
// validate returns an error if the values provided are invalid.
|
||||
validate() error
|
||||
|
||||
// mayLoopOut checks whether we may dispatch a loop out swap based on
|
||||
// the current fee conditions.
|
||||
mayLoopOut(estimate chainfee.SatPerKWeight) error
|
||||
|
||||
// loopOutLimits checks whether the quote provided is within our fee
|
||||
// limits for the swap amount.
|
||||
loopOutLimits(amount btcutil.Amount, quote *loop.LoopOutQuote) error
|
||||
|
||||
// loopOutFees return the maximum prepay and invoice routing fees for
|
||||
// a swap amount and quote.
|
||||
loopOutFees(amount btcutil.Amount, quote *loop.LoopOutQuote) (
|
||||
btcutil.Amount, btcutil.Amount, btcutil.Amount)
|
||||
}
|
||||
|
||||
// swapSuggestion is an interface implemented by suggested swaps for our
|
||||
// different swap types. This interface is used to allow us to handle different
|
||||
// swap types with the same autoloop logic.
|
||||
|
|
|
|||
|
|
@ -63,34 +63,6 @@ const (
|
|||
// FeeBase is the base that we use to express fees.
|
||||
FeeBase = 1e6
|
||||
|
||||
// defaultSwapFeePPM is the default limit we place on swap fees,
|
||||
// expressed as parts per million of swap volume, 0.5%.
|
||||
defaultSwapFeePPM = 5000
|
||||
|
||||
// defaultRoutingFeePPM is the default limit we place on routing fees
|
||||
// for the swap invoice, expressed as parts per million of swap volume,
|
||||
// 1%.
|
||||
defaultRoutingFeePPM = 10000
|
||||
|
||||
// defaultRoutingFeePPM is the default limit we place on routing fees
|
||||
// for the prepay invoice, expressed as parts per million of prepay
|
||||
// volume, 0.5%.
|
||||
defaultPrepayRoutingFeePPM = 5000
|
||||
|
||||
// defaultMaximumMinerFee is the default limit we place on miner fees
|
||||
// per swap. We apply a multiplier to this default fee to guard against
|
||||
// the case where we have broadcast the preimage, then fees spike and
|
||||
// we need to sweep the preimage.
|
||||
defaultMaximumMinerFee = 15000 * 100
|
||||
|
||||
// defaultMaximumPrepay is the default limit we place on prepay
|
||||
// invoices.
|
||||
defaultMaximumPrepay = 30000
|
||||
|
||||
// defaultSweepFeeRateLimit is the default limit we place on estimated
|
||||
// sweep fees, (750 * 4 /1000 = 3 sat/vByte).
|
||||
defaultSweepFeeRateLimit = chainfee.SatPerKWeight(750)
|
||||
|
||||
// defaultMaxInFlight is the default number of in-flight automatically
|
||||
// dispatched swaps we allow. Note that this does not enable automated
|
||||
// swaps itself (because we want non-zero values to be expressed in
|
||||
|
|
@ -121,44 +93,18 @@ var (
|
|||
// defaultParameters contains the default parameters that we start our
|
||||
// liquidity manger with.
|
||||
defaultParameters = Parameters{
|
||||
AutoFeeBudget: defaultBudget,
|
||||
MaxAutoInFlight: defaultMaxInFlight,
|
||||
ChannelRules: make(map[lnwire.ShortChannelID]*ThresholdRule),
|
||||
PeerRules: make(map[route.Vertex]*ThresholdRule),
|
||||
FailureBackOff: defaultFailureBackoff,
|
||||
SweepFeeRateLimit: defaultSweepFeeRateLimit,
|
||||
SweepConfTarget: loop.DefaultSweepConfTarget,
|
||||
MaximumSwapFeePPM: defaultSwapFeePPM,
|
||||
MaximumRoutingFeePPM: defaultRoutingFeePPM,
|
||||
MaximumPrepayRoutingFeePPM: defaultPrepayRoutingFeePPM,
|
||||
MaximumMinerFee: defaultMaximumMinerFee,
|
||||
MaximumPrepay: defaultMaximumPrepay,
|
||||
AutoFeeBudget: defaultBudget,
|
||||
MaxAutoInFlight: defaultMaxInFlight,
|
||||
ChannelRules: make(map[lnwire.ShortChannelID]*ThresholdRule),
|
||||
PeerRules: make(map[route.Vertex]*ThresholdRule),
|
||||
FailureBackOff: defaultFailureBackoff,
|
||||
SweepConfTarget: loop.DefaultSweepConfTarget,
|
||||
FeeLimit: defaultFeeCategoryLimit(),
|
||||
}
|
||||
|
||||
// ErrZeroChannelID is returned if we get a rule for a 0 channel ID.
|
||||
ErrZeroChannelID = fmt.Errorf("zero channel ID not allowed")
|
||||
|
||||
// ErrInvalidSweepFeeRateLimit is returned if an invalid sweep fee limit
|
||||
// is set.
|
||||
ErrInvalidSweepFeeRateLimit = fmt.Errorf("sweep fee rate limit must "+
|
||||
"be > %v sat/vByte",
|
||||
satPerKwToSatPerVByte(chainfee.AbsoluteFeePerKwFloor))
|
||||
|
||||
// ErrZeroMinerFee is returned if a zero maximum miner fee is set.
|
||||
ErrZeroMinerFee = errors.New("maximum miner fee must be non-zero")
|
||||
|
||||
// ErrZeroSwapFeePPM is returned if a zero server fee ppm is set.
|
||||
ErrZeroSwapFeePPM = errors.New("swap fee PPM must be non-zero")
|
||||
|
||||
// ErrZeroRoutingPPM is returned if a zero routing fee ppm is set.
|
||||
ErrZeroRoutingPPM = errors.New("routing fee PPM must be non-zero")
|
||||
|
||||
// ErrZeroPrepayPPM is returned if a zero prepay routing fee ppm is set.
|
||||
ErrZeroPrepayPPM = errors.New("prepay routing fee PPM must be non-zero")
|
||||
|
||||
// ErrZeroPrepay is returned if a zero maximum prepay is set.
|
||||
ErrZeroPrepay = errors.New("maximum prepay must be non-zero")
|
||||
|
||||
// ErrNegativeBudget is returned if a negative swap budget is set.
|
||||
ErrNegativeBudget = errors.New("swap budget must be >= 0")
|
||||
|
||||
|
|
@ -254,41 +200,12 @@ type Parameters struct {
|
|||
// TODO(carla): add exponential backoff
|
||||
FailureBackOff time.Duration
|
||||
|
||||
// SweepFeeRateLimit is the limit that we place on our estimated sweep
|
||||
// fee. A swap will not be suggested if estimated fee rate is above this
|
||||
// value.
|
||||
SweepFeeRateLimit chainfee.SatPerKWeight
|
||||
|
||||
// SweepConfTarget is the number of blocks we aim to confirm our sweep
|
||||
// transaction in. This value affects the on chain fees we will pay.
|
||||
SweepConfTarget int32
|
||||
|
||||
// MaximumPrepay is the maximum prepay amount we are willing to pay per
|
||||
// swap.
|
||||
MaximumPrepay btcutil.Amount
|
||||
|
||||
// MaximumSwapFeePPM is the maximum server fee we are willing to pay per
|
||||
// swap expressed as parts per million of the swap volume.
|
||||
MaximumSwapFeePPM int
|
||||
|
||||
// MaximumRoutingFeePPM is the maximum off-chain routing fee we
|
||||
// are willing to pay for off chain invoice routing fees per swap,
|
||||
// expressed as parts per million of the swap amount.
|
||||
MaximumRoutingFeePPM int
|
||||
|
||||
// MaximumPrepayRoutingFeePPM is the maximum off-chain routing fee we
|
||||
// are willing to pay for off chain prepay routing fees per swap,
|
||||
// expressed as parts per million of the prepay amount.
|
||||
MaximumPrepayRoutingFeePPM int
|
||||
|
||||
// MaximumMinerFee is the maximum on chain fee that we cap our miner
|
||||
// fee at in case where we need to claim on chain because we have
|
||||
// revealed the preimage, but fees have spiked. We will not initiate a
|
||||
// swap if we estimate that the sweep cost will be above our sweep
|
||||
// fee limit, and we use fee estimates at time of sweep to set our fees,
|
||||
// so this is just a sane cap covering the special case where we need to
|
||||
// sweep during a fee spike.
|
||||
MaximumMinerFee btcutil.Amount
|
||||
// FeeLimit controls the fee limit we place on swaps.
|
||||
FeeLimit FeeLimit
|
||||
|
||||
// ClientRestrictions are the restrictions placed on swap size by the
|
||||
// client.
|
||||
|
|
@ -324,15 +241,10 @@ func (p Parameters) String() string {
|
|||
}
|
||||
|
||||
return fmt.Sprintf("rules: %v, failure backoff: %v, sweep "+
|
||||
"fee rate limit: %v, sweep conf target: %v, maximum prepay: "+
|
||||
"%v, maximum miner fee: %v, maximum swap fee ppm: %v, maximum "+
|
||||
"routing fee ppm: %v, maximum prepay routing fee ppm: %v, "+
|
||||
"auto budget: %v, budget start: %v, max auto in flight: %v, "+
|
||||
"minimum swap size=%v, maximum swap size=%v",
|
||||
strings.Join(ruleList, ","), p.FailureBackOff,
|
||||
p.SweepFeeRateLimit, p.SweepConfTarget, p.MaximumPrepay,
|
||||
p.MaximumMinerFee, p.MaximumSwapFeePPM,
|
||||
p.MaximumRoutingFeePPM, p.MaximumPrepayRoutingFeePPM,
|
||||
"sweep conf target: %v, fees: %v, auto budget: %v, budget "+
|
||||
"start: %v, max auto in flight: %v, minimum swap size=%v, "+
|
||||
"maximum swap size=%v", strings.Join(ruleList, ","),
|
||||
p.FailureBackOff, p.SweepConfTarget, p.FeeLimit,
|
||||
p.AutoFeeBudget, p.AutoFeeStartDate, p.MaxAutoInFlight,
|
||||
p.ClientRestrictions.Minimum, p.ClientRestrictions.Maximum)
|
||||
}
|
||||
|
|
@ -403,38 +315,14 @@ func (p Parameters) validate(minConfs int32, openChans []lndclient.ChannelInfo,
|
|||
}
|
||||
}
|
||||
|
||||
// Check that our sweep limit is above our minimum fee rate. We use
|
||||
// absolute fee floor rather than kw floor because we will allow users
|
||||
// to specify fee rate is sat/vByte and want to allow 1 sat/vByte.
|
||||
if p.SweepFeeRateLimit < chainfee.AbsoluteFeePerKwFloor {
|
||||
return ErrInvalidSweepFeeRateLimit
|
||||
}
|
||||
|
||||
// Check that our confirmation target is above our required minimum.
|
||||
if p.SweepConfTarget < minConfs {
|
||||
return fmt.Errorf("confirmation target must be at least: %v",
|
||||
minConfs)
|
||||
}
|
||||
|
||||
// Check that we have non-zero fee limits.
|
||||
if p.MaximumSwapFeePPM == 0 {
|
||||
return ErrZeroSwapFeePPM
|
||||
}
|
||||
|
||||
if p.MaximumRoutingFeePPM == 0 {
|
||||
return ErrZeroRoutingPPM
|
||||
}
|
||||
|
||||
if p.MaximumPrepayRoutingFeePPM == 0 {
|
||||
return ErrZeroPrepayPPM
|
||||
}
|
||||
|
||||
if p.MaximumPrepay == 0 {
|
||||
return ErrZeroPrepay
|
||||
}
|
||||
|
||||
if p.MaximumMinerFee == 0 {
|
||||
return ErrZeroMinerFee
|
||||
if err := p.FeeLimit.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.AutoFeeBudget < 0 {
|
||||
|
|
@ -733,14 +621,14 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) (
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if estimate > m.params.SweepFeeRateLimit {
|
||||
log.Debugf("Current fee estimate to sweep within: %v blocks "+
|
||||
"%v sat/vByte exceeds limit of: %v sat/vByte",
|
||||
m.params.SweepConfTarget,
|
||||
satPerKwToSatPerVByte(estimate),
|
||||
satPerKwToSatPerVByte(m.params.SweepFeeRateLimit))
|
||||
if err := m.params.FeeLimit.mayLoopOut(estimate); err != nil {
|
||||
var reasonErr *reasonError
|
||||
if errors.As(err, &reasonErr) {
|
||||
return m.singleReasonSuggestion(reasonErr.reason), nil
|
||||
|
||||
return m.singleReasonSuggestion(ReasonSweepFees), nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the current server side restrictions, combined with the client
|
||||
|
|
@ -989,9 +877,8 @@ func (m *Manager) loopOutSwap(ctx context.Context, amount btcutil.Amount,
|
|||
|
||||
// Check that the estimated fees for the suggested swap are
|
||||
// below the fee limits configured by the manager.
|
||||
feeReason := m.checkFeeLimits(quote, amount)
|
||||
if feeReason != ReasonNone {
|
||||
return nil, newReasonError(feeReason)
|
||||
if err := m.params.FeeLimit.loopOutLimits(amount, quote); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
outRequest, err := m.makeLoopOutRequest(
|
||||
|
|
@ -1054,23 +941,24 @@ func (m *Manager) makeLoopOutRequest(ctx context.Context,
|
|||
amount btcutil.Amount, balance *balances, quote *loop.LoopOutQuote,
|
||||
autoloop bool) (loop.OutRequest, error) {
|
||||
|
||||
prepayMaxFee := ppmToSat(
|
||||
quote.PrepayAmount, m.params.MaximumPrepayRoutingFeePPM,
|
||||
prepayMaxFee, routeMaxFee, minerFee := m.params.FeeLimit.loopOutFees(
|
||||
amount, quote,
|
||||
)
|
||||
|
||||
routeMaxFee := ppmToSat(amount, m.params.MaximumRoutingFeePPM)
|
||||
|
||||
var chanSet loopdb.ChannelSet
|
||||
for _, channel := range balance.channels {
|
||||
chanSet = append(chanSet, channel.ToUint64())
|
||||
}
|
||||
|
||||
// Create a request with our calculated routing fees. We can use the
|
||||
// swap fee, prepay amount and miner fee from the quote because we have
|
||||
// already validated them.
|
||||
request := loop.OutRequest{
|
||||
Amount: amount,
|
||||
OutgoingChanSet: chanSet,
|
||||
MaxPrepayRoutingFee: prepayMaxFee,
|
||||
MaxSwapRoutingFee: routeMaxFee,
|
||||
MaxMinerFee: m.params.MaximumMinerFee,
|
||||
MaxMinerFee: minerFee,
|
||||
MaxSwapFee: quote.SwapFee,
|
||||
MaxPrepayAmount: quote.PrepayAmount,
|
||||
SweepConfTarget: m.params.SweepConfTarget,
|
||||
|
|
@ -1305,37 +1193,6 @@ func (s *swapTraffic) maySwap(peer route.Vertex,
|
|||
return nil
|
||||
}
|
||||
|
||||
// checkFeeLimits takes a set of fees for a swap and checks whether they exceed
|
||||
// our swap limits.
|
||||
func (m *Manager) checkFeeLimits(quote *loop.LoopOutQuote,
|
||||
swapAmt btcutil.Amount) Reason {
|
||||
|
||||
maxFee := ppmToSat(swapAmt, m.params.MaximumSwapFeePPM)
|
||||
|
||||
if quote.SwapFee > maxFee {
|
||||
log.Debugf("quoted swap fee: %v > maximum swap fee: %v",
|
||||
quote.SwapFee, maxFee)
|
||||
|
||||
return ReasonSwapFee
|
||||
}
|
||||
|
||||
if quote.MinerFee > m.params.MaximumMinerFee {
|
||||
log.Debugf("quoted miner fee: %v > maximum miner "+
|
||||
"fee: %v", quote.MinerFee, m.params.MaximumMinerFee)
|
||||
|
||||
return ReasonMinerFee
|
||||
}
|
||||
|
||||
if quote.PrepayAmount > m.params.MaximumPrepay {
|
||||
log.Debugf("quoted prepay: %v > maximum prepay: %v",
|
||||
quote.PrepayAmount, m.params.MaximumPrepay)
|
||||
|
||||
return ReasonPrepay
|
||||
}
|
||||
|
||||
return ReasonNone
|
||||
}
|
||||
|
||||
// satPerKwToSatPerVByte converts sat per kWeight to sat per vByte.
|
||||
func satPerKwToSatPerVByte(satPerKw chainfee.SatPerKWeight) int64 {
|
||||
return int64(satPerKw.FeePerKVByte() / 1000)
|
||||
|
|
@ -1343,8 +1200,8 @@ func satPerKwToSatPerVByte(satPerKw chainfee.SatPerKWeight) int64 {
|
|||
|
||||
// ppmToSat takes an amount and a measure of parts per million for the amount
|
||||
// and returns the amount that the ppm represents.
|
||||
func ppmToSat(amount btcutil.Amount, ppm int) btcutil.Amount {
|
||||
return btcutil.Amount(uint64(amount) * uint64(ppm) / FeeBase)
|
||||
func ppmToSat(amount btcutil.Amount, ppm uint64) btcutil.Amount {
|
||||
return btcutil.Amount(uint64(amount) * ppm / FeeBase)
|
||||
}
|
||||
|
||||
func mSatToSatoshis(amount lnwire.MilliSatoshi) btcutil.Amount {
|
||||
|
|
|
|||
|
|
@ -125,8 +125,7 @@ func newTestConfig() (*Config, *test.LndMockServices) {
|
|||
// Set our fee estimate for the default number of confirmations to our
|
||||
// limit so that our fees will be ok by default.
|
||||
lnd.SetFeeEstimate(
|
||||
defaultParameters.SweepConfTarget,
|
||||
defaultParameters.SweepFeeRateLimit,
|
||||
defaultParameters.SweepConfTarget, defaultSweepFeeRateLimit,
|
||||
)
|
||||
|
||||
return &Config{
|
||||
|
|
@ -978,8 +977,13 @@ func TestFeeBudget(t *testing.T) {
|
|||
}
|
||||
params.AutoFeeStartDate = testBudgetStart
|
||||
params.AutoFeeBudget = testCase.budget
|
||||
params.MaximumMinerFee = testCase.maxMinerFee
|
||||
params.MaxAutoInFlight = 2
|
||||
params.FeeLimit = NewFeeCategoryLimit(
|
||||
defaultSwapFeePPM, defaultRoutingFeePPM,
|
||||
defaultPrepayRoutingFeePPM,
|
||||
testCase.maxMinerFee, defaultMaximumPrepay,
|
||||
defaultSweepFeeRateLimit,
|
||||
)
|
||||
|
||||
// Set our custom max miner fee on each expected swap,
|
||||
// rather than having to create multiple vars for
|
||||
|
|
@ -1129,8 +1133,7 @@ func TestSizeRestrictions(t *testing.T) {
|
|||
OutgoingChanSet: loopdb.ChannelSet{chanID1.ToUint64()},
|
||||
MaxPrepayRoutingFee: prepayFee,
|
||||
MaxSwapRoutingFee: ppmToSat(
|
||||
7000,
|
||||
defaultRoutingFeePPM,
|
||||
7000, defaultRoutingFeePPM,
|
||||
),
|
||||
MaxMinerFee: defaultMaximumMinerFee,
|
||||
MaxSwapFee: testQuote.SwapFee,
|
||||
|
|
|
|||
|
|
@ -572,22 +572,14 @@ func (s *swapClientServer) GetLiquidityParams(_ context.Context,
|
|||
|
||||
cfg := s.liquidityMgr.GetParameters()
|
||||
|
||||
satPerByte := cfg.SweepFeeRateLimit.FeePerKVByte() / 1000
|
||||
|
||||
totalRules := len(cfg.ChannelRules) + len(cfg.PeerRules)
|
||||
|
||||
rpcCfg := &looprpc.LiquidityParameters{
|
||||
MaxMinerFeeSat: uint64(cfg.MaximumMinerFee),
|
||||
MaxSwapFeePpm: uint64(cfg.MaximumSwapFeePPM),
|
||||
MaxRoutingFeePpm: uint64(cfg.MaximumRoutingFeePPM),
|
||||
MaxPrepayRoutingFeePpm: uint64(cfg.MaximumPrepayRoutingFeePPM),
|
||||
MaxPrepaySat: uint64(cfg.MaximumPrepay),
|
||||
SweepFeeRateSatPerVbyte: uint64(satPerByte),
|
||||
SweepConfTarget: cfg.SweepConfTarget,
|
||||
FailureBackoffSec: uint64(cfg.FailureBackOff.Seconds()),
|
||||
Autoloop: cfg.Autoloop,
|
||||
AutoloopBudgetSat: uint64(cfg.AutoFeeBudget),
|
||||
AutoMaxInFlight: uint64(cfg.MaxAutoInFlight),
|
||||
SweepConfTarget: cfg.SweepConfTarget,
|
||||
FailureBackoffSec: uint64(cfg.FailureBackOff.Seconds()),
|
||||
Autoloop: cfg.Autoloop,
|
||||
AutoloopBudgetSat: uint64(cfg.AutoFeeBudget),
|
||||
AutoMaxInFlight: uint64(cfg.MaxAutoInFlight),
|
||||
Rules: make(
|
||||
[]*looprpc.LiquidityRule, 0, totalRules,
|
||||
),
|
||||
|
|
@ -595,6 +587,20 @@ func (s *swapClientServer) GetLiquidityParams(_ context.Context,
|
|||
MaxSwapAmount: uint64(cfg.ClientRestrictions.Maximum),
|
||||
}
|
||||
|
||||
feeCategories, ok := cfg.FeeLimit.(*liquidity.FeeCategoryLimit)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown fee limit: %T", cfg.FeeLimit)
|
||||
}
|
||||
|
||||
satPerByte := feeCategories.SweepFeeRateLimit.FeePerKVByte() / 1000
|
||||
|
||||
rpcCfg.SweepFeeRateSatPerVbyte = uint64(satPerByte)
|
||||
rpcCfg.MaxMinerFeeSat = uint64(feeCategories.MaximumMinerFee)
|
||||
rpcCfg.MaxSwapFeePpm = feeCategories.MaximumSwapFeePPM
|
||||
rpcCfg.MaxRoutingFeePpm = feeCategories.MaximumRoutingFeePPM
|
||||
rpcCfg.MaxPrepayRoutingFeePpm = feeCategories.MaximumPrepayRoutingFeePPM
|
||||
rpcCfg.MaxPrepaySat = uint64(feeCategories.MaximumPrepay)
|
||||
|
||||
// Zero golang time is different to a zero unix time, so we only set
|
||||
// our start date if it is non-zero.
|
||||
if !cfg.AutoFeeStartDate.IsZero() {
|
||||
|
|
@ -639,14 +645,18 @@ func (s *swapClientServer) SetLiquidityParams(ctx context.Context,
|
|||
in.Parameters.SweepFeeRateSatPerVbyte * 1000,
|
||||
)
|
||||
|
||||
feeLimit := liquidity.NewFeeCategoryLimit(
|
||||
in.Parameters.MaxSwapFeePpm,
|
||||
in.Parameters.MaxRoutingFeePpm,
|
||||
in.Parameters.MaxPrepayRoutingFeePpm,
|
||||
btcutil.Amount(in.Parameters.MaxMinerFeeSat),
|
||||
btcutil.Amount(in.Parameters.MaxPrepaySat),
|
||||
satPerVbyte.FeePerKWeight(),
|
||||
)
|
||||
|
||||
params := liquidity.Parameters{
|
||||
MaximumMinerFee: btcutil.Amount(in.Parameters.MaxMinerFeeSat),
|
||||
MaximumSwapFeePPM: int(in.Parameters.MaxSwapFeePpm),
|
||||
MaximumRoutingFeePPM: int(in.Parameters.MaxRoutingFeePpm),
|
||||
MaximumPrepayRoutingFeePPM: int(in.Parameters.MaxPrepayRoutingFeePpm),
|
||||
MaximumPrepay: btcutil.Amount(in.Parameters.MaxPrepaySat),
|
||||
SweepFeeRateLimit: satPerVbyte.FeePerKWeight(),
|
||||
SweepConfTarget: in.Parameters.SweepConfTarget,
|
||||
FeeLimit: feeLimit,
|
||||
SweepConfTarget: in.Parameters.SweepConfTarget,
|
||||
FailureBackOff: time.Duration(in.Parameters.FailureBackoffSec) *
|
||||
time.Second,
|
||||
Autoloop: in.Parameters.Autoloop,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue