Skip to content

Commit

Permalink
feat: v2 api (#83)
Browse files Browse the repository at this point in the history
* feat: initial swaptree implementation

* feat: initial btc and liquid taproot swaps

* feat: initial cli integration

* feat: submarine and reverse pair grpc

* chore: db migration

* feat: run db migrations inside transaction

* feat: late invoice and refunds for normal swaps

* chore: cleanup error messages

Co-authored-by: michael1011 <me@michael1011.at>

* chore: cleanup boltz package

* fix: error if less onchain coins than expected

* fix: update database version inside transaction

* test: dont create unnecessary client

* test: lower cltv expiry in regtest for lower swap timeouts

* refactor: add partial signer callback in constructtransaction

* chore: rm unnecessary rpc

* fix: error descriptions

Co-authored-by: michael1011 <me@michael1011.at>

* fix: check length of output scripts

* refactor: add constatn for regtest cltv

* feat: prefer clearnet uris when connecting nodes

* refactor: use map instead of hardcoded node names

* fix: correct pair in autoswap

* feat: improve node connection selection

---------

Co-authored-by: michael1011 <me@michael1011.at>
  • Loading branch information
jackstar12 and michael1011 authored Feb 12, 2024
1 parent dc8541d commit db4bd87
Show file tree
Hide file tree
Showing 59 changed files with 4,889 additions and 2,882 deletions.
40 changes: 23 additions & 17 deletions autoswap/autoswap.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,15 @@ import (

var ErrorNotConfigured = errors.New("autoswap not configured")

type SwapExecution struct {
Amount uint64
Channel *boltzrpc.LightningChannel
Pair boltz.Pair
type Limits struct {
MinAmount uint64
MaxAmount uint64
}

type PairInfo struct {
Limits
PercentageFee utils.Percentage
OnchainFee uint64
}

type AutoSwapper struct {
Expand All @@ -36,7 +41,7 @@ type AutoSwapper struct {
ExecuteSwap func(request *boltzrpc.CreateSwapRequest) error
ExecuteReverseSwap func(request *boltzrpc.CreateReverseSwapRequest) error
ListChannels func() ([]*lightning.LightningChannel, error)
GetServiceInfo func(pair boltz.Pair) (*boltzrpc.Fees, *boltzrpc.Limits, error)
GetPairInfo func(pair *boltzrpc.Pair, swapType boltz.SwapType) (*PairInfo, error)
}

func (swapper *AutoSwapper) Init(database *database.Database, onchain *onchain.Onchain, configPath string) {
Expand Down Expand Up @@ -78,7 +83,7 @@ func (swapper *AutoSwapper) SetConfig(cfg *Config) error {
if cfg.Type != "" {
message += " of type " + string(cfg.Type)
}
message += " for pair " + string(cfg.pair)
message += " for currency " + string(cfg.Currency)

logger.Info(message)
swapper.cfg = cfg
Expand Down Expand Up @@ -176,11 +181,6 @@ func (swapper *AutoSwapper) validateRecommendations(
recommendations []*rawRecommendation,
budget int64,
) ([]*SwapRecommendation, error) {
fees, limits, err := swapper.GetServiceInfo(swapper.cfg.pair)
if err != nil {
return nil, err
}

dismissedChannels, err := swapper.getDismissedChannels()
if err != nil {
return nil, err
Expand All @@ -195,7 +195,13 @@ func (swapper *AutoSwapper) validateRecommendations(

var checked []*SwapRecommendation
for _, recommendation := range recommendations {
recommendation := recommendation.Check(fees, limits, swapper.cfg)
pairInfo, err := swapper.GetPairInfo(swapper.cfg.GetPair(recommendation.Type), recommendation.Type)
if err != nil {
logger.Warn("Could not get pair info: " + err.Error())
continue
}

recommendation := recommendation.Check(pairInfo, swapper.cfg)
reasons, ok := dismissedChannels[recommendation.Channel.GetId()]
if ok {
recommendation.DismissedReasons = append(recommendation.DismissedReasons, reasons...)
Expand Down Expand Up @@ -234,25 +240,25 @@ func (swapper *AutoSwapper) GetSwapRecommendations() ([]*SwapRecommendation, err
}

func (swapper *AutoSwapper) execute(recommendation *SwapRecommendation, address string) error {
pair := string(swapper.cfg.pair)
var chanIds []string
if chanId := recommendation.Channel.GetId(); chanId != 0 {
chanIds = append(chanIds, chanId.ToCln())
}
pair := swapper.cfg.GetPair(recommendation.Type)
var err error
if recommendation.Type == boltz.ReverseSwap {
err = swapper.ExecuteReverseSwap(&boltzrpc.CreateReverseSwapRequest{
Amount: int64(recommendation.Amount),
Address: address,
AcceptZeroConf: swapper.cfg.AcceptZeroConf,
PairId: pair,
AcceptZeroConf: bool(swapper.cfg.AcceptZeroConf),
Pair: pair,
ChanIds: chanIds,
Wallet: &swapper.cfg.Wallet,
})
} else if recommendation.Type == boltz.NormalSwap {
err = swapper.ExecuteSwap(&boltzrpc.CreateSwapRequest{
Amount: int64(recommendation.Amount),
PairId: pair,
Pair: pair,
ChanIds: chanIds,
AutoSend: true,
Wallet: &swapper.cfg.Wallet,
Expand Down Expand Up @@ -300,7 +306,7 @@ func (swapper *AutoSwapper) Start() error {
logger.Debugf("Got new address %v from wallet %v", address, wallet.Name())
}
} else if address == "" {
err = fmt.Errorf("neither external address or wallet is available for pair %s: %v", cfg.pair, err)
err = fmt.Errorf("neither external address or wallet is available for currency %s: %v", cfg.Currency, err)
} else if normalSwaps {
err = fmt.Errorf("normal swaps require a wallet: %v", err)
}
Expand Down
28 changes: 12 additions & 16 deletions autoswap/autoswap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,6 @@ import (
"github.com/stretchr/testify/require"
)

var fees = &boltzrpc.Fees{
Percentage: 10,
Miner: &boltzrpc.MinerFees{
Normal: 10,
Reverse: 10,
},
}
var limits = &boltzrpc.Limits{
Minimal: 100,
Maximal: 1000,
}

func getTestDb(t *testing.T) *database.Database {
db := &database.Database{
Path: ":memory:",
Expand All @@ -43,8 +31,16 @@ func getSwapper(t *testing.T, cfg *Config) *AutoSwapper {
ListChannels: func() ([]*lightning.LightningChannel, error) {
return nil, nil
},
GetServiceInfo: func(pair boltz.Pair) (*boltzrpc.Fees, *boltzrpc.Limits, error) {
return fees, limits, nil
GetPairInfo: func(pair *boltzrpc.Pair, swapType boltz.SwapType) (*PairInfo, error) {
return &PairInfo{
Limits{
MinAmount: 100,
MaxAmount: 1000,
},
10,
10,
}, nil

},
}
swapper.Init(getTestDb(t), nil, ".")
Expand Down Expand Up @@ -472,12 +468,12 @@ func TestDismissedChannels(t *testing.T) {
db := swapper.database

for _, swap := range tc.swaps {
swap.PairId = boltz.PairBtc
swap.Pair = boltz.PairBtc
require.NoError(t, db.CreateSwap(swap))
}

for _, reverseSwap := range tc.reverseSwaps {
reverseSwap.PairId = boltz.PairBtc
reverseSwap.Pair = boltz.PairBtc
require.NoError(t, db.CreateReverseSwap(reverseSwap))
}

Expand Down
41 changes: 29 additions & 12 deletions autoswap/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"strconv"
"strings"

"github.com/BoltzExchange/boltz-client/boltzrpc"
"github.com/BoltzExchange/boltz-client/lightning"
"github.com/BoltzExchange/boltz-client/utils"
"github.com/BurntSushi/toml"
Expand Down Expand Up @@ -75,15 +76,16 @@ type Config struct {
Type boltz.SwapType
PerChannel bool
Wallet string
MaxSwapAmount uint64

pair boltz.Pair
maxBalance Balance
minBalance Balance
strategy Strategy
strategyName string
}

type DismissedChannels map[lightning.ChanId][]string
type ChannelLimits map[lightning.ChanId]uint64

func (dismissed DismissedChannels) addChannels(chanIds []lightning.ChanId, reason string) {
for _, chanId := range chanIds {
Expand Down Expand Up @@ -148,10 +150,8 @@ func (cfg *Config) Init() error {

switch strings.ToUpper(string(cfg.Currency)) {
case string(boltz.CurrencyBtc):
cfg.pair = boltz.PairBtc
cfg.Currency = boltz.CurrencyBtc
case string(boltz.CurrencyLiquid), "":
cfg.pair = boltz.PairLiquid
cfg.Currency = boltz.CurrencyLiquid
default:
return errors.New("invalid currency")
Expand All @@ -161,17 +161,17 @@ func (cfg *Config) Init() error {
}

func (cfg *Config) GetAddress(network *boltz.Network) (address string, err error) {
if cfg.pair == boltz.PairLiquid && cfg.LiquidAddress != "" {
if cfg.Currency == boltz.CurrencyLiquid && cfg.LiquidAddress != "" {
address = cfg.LiquidAddress
} else if cfg.pair == boltz.PairBtc && cfg.BitcoinAddress != "" {
} else if cfg.Currency == boltz.CurrencyBtc && cfg.BitcoinAddress != "" {
address = cfg.BitcoinAddress
}
if address == "" {
return "", errors.New("No address for pair " + string(cfg.pair))
return "", errors.New("No address for Currency " + string(cfg.Currency))
}
err = boltz.ValidateAddress(network, address, cfg.pair)
err = boltz.ValidateAddress(network, address, cfg.Currency)
if err != nil {
return "", errors.New("Invalid address for pair " + string(cfg.pair) + " :" + err.Error())
return "", errors.New("Invalid address for Currency " + string(cfg.Currency) + " :" + err.Error())
}
return address, nil
}
Expand Down Expand Up @@ -257,12 +257,12 @@ func (cfg *Config) SetValue(field string, value any) error {
f.SetBool(value)
case reflect.String:
switch f.Interface().(type) {
case boltz.Pair:
pair, err := boltz.ParsePair(stringValue)
case boltz.Currency:
currency, err := boltz.ParseCurrency(stringValue)
if err != nil {
return fmt.Errorf("invalid pair value: %w", err)
return fmt.Errorf("invalid currency value: %w", err)
}
f.Set(reflect.ValueOf(pair))
f.Set(reflect.ValueOf(currency))
case boltz.SwapType:
swapType, err := boltz.ParseSwapType(stringValue)
if err != nil {
Expand Down Expand Up @@ -290,3 +290,20 @@ func (cfg *Config) Write(path string) error {
func (cfg *Config) StrategyName() string {
return cfg.strategyName
}

func (cfg *Config) GetPair(swapType boltz.SwapType) *boltzrpc.Pair {
currency := boltzrpc.Currency_Btc
if cfg.Currency == boltz.CurrencyLiquid {
currency = boltzrpc.Currency_Liquid
}
result := &boltzrpc.Pair{}
switch swapType {
case boltz.NormalSwap:
result.From = currency
result.To = boltzrpc.Currency_Btc
case boltz.ReverseSwap:
result.From = boltzrpc.Currency_Btc
result.To = currency
}
return result
}
20 changes: 20 additions & 0 deletions autoswap/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package autoswap
import (
"testing"

"github.com/BoltzExchange/boltz-client/boltz"
"github.com/BoltzExchange/boltz-client/boltzrpc"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -37,3 +39,21 @@ func TestSetConfigValue(t *testing.T) {
// cant set unexported field
require.Error(t, cfg.SetValue("strategyName", "L-BTC"))
}

func TestGetPair(t *testing.T) {
cfg := DefaultConfig

pair := cfg.GetPair(boltz.NormalSwap)
require.Equal(t, boltzrpc.Currency_Liquid, pair.From)
require.Equal(t, boltzrpc.Currency_Btc, pair.To)

pair = cfg.GetPair(boltz.ReverseSwap)
require.Equal(t, boltzrpc.Currency_Liquid, pair.To)
require.Equal(t, boltzrpc.Currency_Btc, pair.From)

require.NoError(t, cfg.SetValue("Currency", "BTC"))

pair = cfg.GetPair(boltz.ReverseSwap)
require.Equal(t, boltzrpc.Currency_Btc, pair.To)
require.Equal(t, boltzrpc.Currency_Btc, pair.From)
}
26 changes: 9 additions & 17 deletions autoswap/recommendation.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ package autoswap

import (
"fmt"
"github.com/BoltzExchange/boltz-client/lightning"
"math"

"github.com/BoltzExchange/boltz-client/lightning"

"github.com/BoltzExchange/boltz-client/boltz"
"github.com/BoltzExchange/boltz-client/boltzrpc"
"github.com/BoltzExchange/boltz-client/utils"
)

Expand Down Expand Up @@ -38,29 +38,21 @@ func (recommendation *SwapRecommendation) Dismissed() bool {
return len(recommendation.DismissedReasons) > 0
}

func (recommendation rawRecommendation) estimateFee(fees *boltzrpc.Fees) uint64 {
serviceFee := utils.Percentage(fees.Percentage).Calculate(float64(recommendation.Amount))

var onchainFee uint32
if recommendation.Type == boltz.NormalSwap {
onchainFee = fees.Miner.Normal
} else if recommendation.Type == boltz.ReverseSwap {
onchainFee = fees.Miner.Reverse
}

return uint64(serviceFee) + uint64(onchainFee)
func (recommendation rawRecommendation) estimateFee(pair *PairInfo) uint64 {
serviceFee := utils.Percentage(pair.PercentageFee).Calculate(float64(recommendation.Amount))
return uint64(serviceFee) + pair.OnchainFee
}

func (recommendation rawRecommendation) Check(fees *boltzrpc.Fees, limits *boltzrpc.Limits, cfg *Config) *SwapRecommendation {
func (recommendation rawRecommendation) Check(pair *PairInfo, cfg *Config) *SwapRecommendation {
var dismissedReasons []string

if recommendation.Amount < uint64(limits.Minimal) {
if recommendation.Amount < uint64(pair.MinAmount) {
dismissedReasons = append(dismissedReasons, ReasonAmountBelowMin)
}
recommendation.Amount = uint64(math.Min(float64(recommendation.Amount), float64(limits.Maximal)))
recommendation.Amount = uint64(math.Min(float64(recommendation.Amount), float64(pair.MaxAmount)))

maxFee := cfg.MaxFeePercent.Calculate(float64(recommendation.Amount))
fee := recommendation.estimateFee(fees)
fee := recommendation.estimateFee(pair)
if float64(fee) > maxFee {
dismissedReasons = append(dismissedReasons, ReasonMaxFeePercent)
}
Expand Down
Loading

0 comments on commit db4bd87

Please sign in to comment.