Skip to content

Commit

Permalink
feat: support creating swaps with lnurls and lnaddresses (#308)
Browse files Browse the repository at this point in the history
* feat: support creating swaps with lnurls and lnaddresses

* docs: mention lnurl and lnaddress in rpc

* chore: update regtest
  • Loading branch information
jackstar12 authored Oct 16, 2024
1 parent 22981c0 commit 1527dca
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 8 deletions.
5 changes: 4 additions & 1 deletion boltzrpc/boltzrpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion boltzrpc/boltzrpc.proto
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,10 @@ message CreateSwapRequest {
optional string refund_address = 5;
// wallet to pay swap from. only used if `send_from_internal` is set to true
optional uint64 wallet_id = 6;
// invoice to use for the swap. if not set, the daemon will get a new invoice from the lightning node
// bolt11 invoice, lnurl, or lnaddress to use for the swap.
// required in standalone mode.
// when connected to a lightning node, a new invoice for `amount` sats will be fetched
// the `amount` field has to be populated in case of a lnurl and lnaddress
optional string invoice = 7;
// Boltz does not accept 0-conf for Liquid transactions with a fee of 0.01 sat/vByte;
// when `zero_conf` is enabled, a fee of 0.1 sat/vByte will be used for Liquid lockup transactions
Expand Down
2 changes: 1 addition & 1 deletion docs/grpc.md
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,7 @@ Channel creations are an optional extension to a submarine swap in the data type
| `send_from_internal` | [`bool`](#bool) | | the daemon will pay the swap using the onchain wallet specified in the `wallet` field or any wallet otherwise. |
| `refund_address` | [`string`](#string) | optional | address where the coins should go if the swap fails. Refunds will go to any of the daemons wallets otherwise. |
| `wallet_id` | [`uint64`](#uint64) | optional | wallet to pay swap from. only used if `send_from_internal` is set to true |
| `invoice` | [`string`](#string) | optional | invoice to use for the swap. if not set, the daemon will get a new invoice from the lightning node |
| `invoice` | [`string`](#string) | optional | bolt11 invoice, lnurl, or lnaddress to use for the swap. required in standalone mode. when connected to a lightning node, a new invoice for `amount` sats will be fetched the `amount` field has to be populated in case of a lnurl and lnaddress |
| `zero_conf` | [`bool`](#bool) | optional | Boltz does not accept 0-conf for Liquid transactions with a fee of 0.01 sat/vByte; when `zero_conf` is enabled, a fee of 0.1 sat/vByte will be used for Liquid lockup transactions |
| `sat_per_vbyte` | [`double`](#double) | optional | Fee rate to use when sending from internal wallet |

Expand Down
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ require (
github.com/envoyproxy/protoc-gen-validate v1.0.2 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/fergusstrange/embedded-postgres v1.25.0 // indirect
github.com/fiatjaf/go-lnurl v1.13.1 // indirect
github.com/go-errors/errors v1.4.2 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
Expand Down Expand Up @@ -147,6 +148,7 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007 // indirect
github.com/nbd-wtf/ln-decodepay v1.6.0 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
Expand All @@ -169,6 +171,9 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
github.com/tidwall/gjson v1.9.3 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 // indirect
github.com/trivago/tgo v1.0.7 // indirect
github.com/tsuyoshiwada/go-gitcmd v0.0.0-20180205145712-5f1f5f9475df // indirect
Expand Down
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,8 @@ github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fergusstrange/embedded-postgres v1.25.0 h1:sa+k2Ycrtz40eCRPOzI7Ry7TtkWXXJ+YRsxpKMDhxK0=
github.com/fergusstrange/embedded-postgres v1.25.0/go.mod h1:t/MLs0h9ukYM6FSt99R7InCHs1nW0ordoVCcnzmpTYw=
github.com/fiatjaf/go-lnurl v1.13.1 h1:7AeseawVNsl7JkZgAkTLf1mxAPbQuNrCPn3v7Qod0VQ=
github.com/fiatjaf/go-lnurl v1.13.1/go.mod h1:GaZb1TFGKiMlmG7yphWg6pifLL2vJaRiDHAn3ZV9L9s=
github.com/frankban/quicktest v1.0.0/go.mod h1:R98jIehRai+d1/3Hv2//jOVCTJhW1VBavT6B6CuGq2k=
github.com/frankban/quicktest v1.1.0/go.mod h1:R98jIehRai+d1/3Hv2//jOVCTJhW1VBavT6B6CuGq2k=
github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20=
Expand Down Expand Up @@ -906,6 +908,8 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007 h1:28i1IjGcx8AofiB4N3q5Yls55VEaitzuEPkFJEVgGkA=
github.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007/go.mod h1:m2XC9Qq0AlmmVksL6FktJCdTYyLk7V3fKyp0sl1yWQo=
github.com/nbd-wtf/ln-decodepay v1.6.0 h1:SwitboTc319Ezh7lYfT/JiLclLIP2PhRCEVXgbWSwo0=
github.com/nbd-wtf/ln-decodepay v1.6.0/go.mod h1:ZY0ZeLImteHHe9Uub75c+V23L0EMNYnBX0lnEAI0KWM=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
Expand Down Expand Up @@ -1005,6 +1009,12 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
github.com/tidwall/gjson v1.9.3 h1:hqzS9wAHMO+KVBBkLxYdkEeeFHuqr95GfClRLKlgK0E=
github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE=
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk=
github.com/trivago/tgo v1.0.7 h1:uaWH/XIy9aWYWpjm2CU3RpcqZXmX2ysQ9/Go+d9gyrM=
Expand Down
8 changes: 4 additions & 4 deletions regtest.patch
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ index 247da7e..38991ea 100755
[liquid]
symbol = "L-BTC"
diff --git a/docker-compose.yml b/docker-compose.yml
index 76b687f..02b6dc2 100644
index 695f8ce..b121cb3 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -61,7 +61,6 @@ x-services:
- --dev-fast-gossip
@@ -66,7 +66,6 @@ x-services:
- --dev-fast-reconnect
- --plugin=/root/hold
- --plugin=/root/clnurl
- - --plugin=/root/mpay.sh
- --experimental-offers
- --clnurl-host=0.0.0.0
expose:
- 9735
19 changes: 18 additions & 1 deletion rpcserver/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"encoding/hex"
"errors"
"fmt"
"github.com/fiatjaf/go-lnurl"
"math"
"net/url"
"regexp"
Expand Down Expand Up @@ -694,10 +695,26 @@ func (server *routedBoltzServer) createSwap(ctx context.Context, isAuto bool, re

var preimage, preimageHash []byte
if invoice := request.GetInvoice(); invoice != "" {
_, lnurlParams, err := lnurl.HandleLNURL(invoice)
if err == nil {
if kind := lnurlParams.LNURLKind(); kind != "lnurl-pay" {
return nil, status.Errorf(codes.InvalidArgument, "lnurl is not pay, but: %s", kind)
}
logger.Infof("Fetching invoice for LNURL: %s", invoice)
lnurlPay := lnurlParams.(lnurl.LNURLPayParams)
if request.Amount == 0 {
return nil, status.Errorf(codes.InvalidArgument, "amount has to be specified for lnurl")
}
payValues, err := lnurlPay.Call(int64(request.Amount*1000), "", nil)
if err != nil {
return nil, err
}
invoice = payValues.PR
}
logger.Infof("Creating Swap for invoice: %s", invoice)
decoded, err := zpay32.Decode(invoice, server.network.Btc)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid invoice: %s", err)
return nil, status.Errorf(codes.InvalidArgument, "invalid invoice or lnurl: %s", err)
}
swapResponse, err = server.checkMagicRoutingHint(decoded, invoice)
if err != nil {
Expand Down
35 changes: 35 additions & 0 deletions rpcserver/rpcserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1807,6 +1807,41 @@ func TestSwap(t *testing.T) {
require.NoError(t, err)
require.True(t, paid)
})

t.Run("LNURL", func(t *testing.T) {
// provided by clnurl plugin
lnurl := "LNURL1DP68GUP69UHNZV3H9CCZUVPWXYARXVPSXQHKZURF9AKXUATJD3CQQKE2EU"
amount := uint64(100000)

t.Run("Invalid", func(t *testing.T) {
lnurl := "invalid"
_, err := client.CreateSwap(&boltzrpc.CreateSwapRequest{
Invoice: &lnurl,
Amount: amount,
})
requireCode(t, err, codes.InvalidArgument)
})

t.Run("AmountRequired", func(t *testing.T) {
_, err := client.CreateSwap(&boltzrpc.CreateSwapRequest{
Invoice: &lnurl,
})
requireCode(t, err, codes.InvalidArgument)
})

t.Run("Valid", func(t *testing.T) {
swap, err := client.CreateSwap(&boltzrpc.CreateSwapRequest{
Invoice: &lnurl,
Amount: amount,
})
require.NoError(t, err)
info, err := client.GetSwapInfo(swap.Id)
require.NoError(t, err)
invoice, err := zpay32.Decode(info.Swap.Invoice, &chaincfg.RegressionNetParams)
require.NoError(t, err)
require.Equal(t, amount, uint64(invoice.MilliSat.ToSatoshis()))
})
})
})

t.Run("Standalone", func(t *testing.T) {
Expand Down

0 comments on commit 1527dca

Please sign in to comment.