Lightning Loop initial version

This commit is contained in:
Joost Jager 2019-03-06 21:13:50 +01:00
parent c5eecda492
commit 21fcd8d94e
No known key found for this signature in database
GPG key ID: A61B9D4C393C59C7
60 changed files with 9236 additions and 8 deletions

300
cmd/swapcli/main.go Normal file
View file

@ -0,0 +1,300 @@
package main
import (
"context"
"errors"
"fmt"
"os"
"strconv"
"time"
"github.com/lightninglabs/nautilus/utils"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/nautilus/cmd/swapd/rpc"
"github.com/urfave/cli"
"google.golang.org/grpc"
)
var (
swapdAddress = "localhost:11010"
// Define route independent max routing fees. We have currently no way
// to get a reliable estimate of the routing fees. Best we can do is the
// minimum routing fees, which is not very indicative.
maxRoutingFeeBase = btcutil.Amount(10)
maxRoutingFeeRate = int64(50000)
)
var unchargeCommand = cli.Command{
Name: "uncharge",
Usage: "perform an off-chain to on-chain swap",
ArgsUsage: "amt [addr]",
Description: `
Send the amount in satoshis specified by the amt argument on-chain.
Optionally a BASE58 encoded bitcoin destination address may be
specified. If not specified, a new wallet address will be generated.`,
Flags: []cli.Flag{
cli.Uint64Flag{
Name: "channel",
Usage: "the 8-byte compact channel ID of the channel to uncharge",
},
},
Action: uncharge,
}
var termsCommand = cli.Command{
Name: "terms",
Usage: "show current server swap terms",
Action: terms,
}
func main() {
app := cli.NewApp()
app.Version = "0.0.1"
app.Usage = "command line interface to swapd"
app.Commands = []cli.Command{unchargeCommand, termsCommand}
app.Action = monitor
err := app.Run(os.Args)
if err != nil {
fmt.Println(err)
}
}
func terms(ctx *cli.Context) error {
client, cleanup, err := getClient(ctx)
if err != nil {
return err
}
defer cleanup()
terms, err := client.GetUnchargeTerms(
context.Background(), &rpc.TermsRequest{},
)
if err != nil {
return err
}
fmt.Printf("Amount: %d - %d\n",
btcutil.Amount(terms.MinSwapAmount),
btcutil.Amount(terms.MaxSwapAmount),
)
if err != nil {
return err
}
printTerms := func(terms *rpc.TermsResponse) {
fmt.Printf("Amount: %d - %d\n",
btcutil.Amount(terms.MinSwapAmount),
btcutil.Amount(terms.MaxSwapAmount),
)
fmt.Printf("Fee: %d + %.4f %% (%d prepaid)\n",
btcutil.Amount(terms.SwapFeeBase),
utils.FeeRateAsPercentage(terms.SwapFeeRate),
btcutil.Amount(terms.PrepayAmt),
)
fmt.Printf("Cltv delta: %v blocks\n", terms.CltvDelta)
}
fmt.Println("Uncharge")
fmt.Println("--------")
printTerms(terms)
return nil
}
func monitor(ctx *cli.Context) error {
client, cleanup, err := getClient(ctx)
if err != nil {
return err
}
defer cleanup()
stream, err := client.Monitor(
context.Background(), &rpc.MonitorRequest{})
if err != nil {
return err
}
for {
swap, err := stream.Recv()
if err != nil {
return fmt.Errorf("recv: %v", err)
}
logSwap(swap)
}
}
func getClient(ctx *cli.Context) (rpc.SwapClientClient, func(), error) {
conn, err := getSwapCliConn(swapdAddress)
if err != nil {
return nil, nil, err
}
cleanup := func() { conn.Close() }
swapCliClient := rpc.NewSwapClientClient(conn)
return swapCliClient, cleanup, nil
}
func getMaxRoutingFee(amt btcutil.Amount) btcutil.Amount {
return utils.CalcFee(amt, maxRoutingFeeBase, maxRoutingFeeRate)
}
type limits struct {
maxSwapRoutingFee btcutil.Amount
maxPrepayRoutingFee btcutil.Amount
maxMinerFee btcutil.Amount
maxSwapFee btcutil.Amount
maxPrepayAmt btcutil.Amount
}
func getLimits(amt btcutil.Amount, quote *rpc.QuoteResponse) *limits {
return &limits{
maxSwapRoutingFee: getMaxRoutingFee(btcutil.Amount(amt)),
maxPrepayRoutingFee: getMaxRoutingFee(btcutil.Amount(
quote.PrepayAmt,
)),
// Apply a multiplier to the estimated miner fee, to not get the swap
// canceled because fees increased in the mean time.
maxMinerFee: btcutil.Amount(quote.MinerFee) * 3,
maxSwapFee: btcutil.Amount(quote.SwapFee),
maxPrepayAmt: btcutil.Amount(quote.PrepayAmt),
}
}
func displayLimits(amt btcutil.Amount, l *limits) error {
totalSuccessMax := l.maxSwapRoutingFee + l.maxPrepayRoutingFee +
l.maxMinerFee + l.maxSwapFee
fmt.Printf("Max swap fees for %d uncharge: %d\n",
btcutil.Amount(amt), totalSuccessMax,
)
fmt.Printf("CONTINUE SWAP? (y/n), expand fee detail (x): ")
var answer string
fmt.Scanln(&answer)
switch answer {
case "y":
return nil
case "x":
fmt.Println()
fmt.Printf("Max on-chain fee: %d\n", l.maxMinerFee)
fmt.Printf("Max off-chain swap routing fee: %d\n",
l.maxSwapRoutingFee)
fmt.Printf("Max off-chain prepay routing fee: %d\n",
l.maxPrepayRoutingFee)
fmt.Printf("Max swap fee: %d\n", l.maxSwapFee)
fmt.Printf("Max no show penalty: %d\n",
l.maxPrepayAmt)
fmt.Printf("CONTINUE SWAP? (y/n): ")
fmt.Scanln(&answer)
if answer == "y" {
return nil
}
}
return errors.New("swap canceled")
}
func parseAmt(text string) (btcutil.Amount, error) {
amtInt64, err := strconv.ParseInt(text, 10, 64)
if err != nil {
return 0, fmt.Errorf("invalid amt value")
}
return btcutil.Amount(amtInt64), nil
}
func uncharge(ctx *cli.Context) error {
// Show command help if no arguments and flags were provided.
if ctx.NArg() < 1 {
cli.ShowCommandHelp(ctx, "uncharge")
return nil
}
args := ctx.Args()
amt, err := parseAmt(args[0])
if err != nil {
return err
}
var destAddr string
args = args.Tail()
if args.Present() {
destAddr = args.First()
}
client, cleanup, err := getClient(ctx)
if err != nil {
return err
}
defer cleanup()
quote, err := client.GetUnchargeQuote(
context.Background(),
&rpc.QuoteRequest{
Amt: int64(amt),
},
)
if err != nil {
return err
}
limits := getLimits(amt, quote)
if err := displayLimits(amt, limits); err != nil {
return err
}
var unchargeChannel uint64
if ctx.IsSet("channel") {
unchargeChannel = ctx.Uint64("channel")
}
resp, err := client.Uncharge(context.Background(), &rpc.UnchargeRequest{
Amt: int64(amt),
Dest: destAddr,
MaxMinerFee: int64(limits.maxMinerFee),
MaxPrepayAmt: int64(limits.maxPrepayAmt),
MaxSwapFee: int64(limits.maxSwapFee),
MaxPrepayRoutingFee: int64(limits.maxPrepayRoutingFee),
MaxSwapRoutingFee: int64(limits.maxSwapRoutingFee),
UnchargeChannel: unchargeChannel,
})
if err != nil {
return err
}
fmt.Printf("Swap initiated with id: %v\n", resp.Id[:8])
fmt.Printf("Run swapcli without a command to monitor progress.\n")
return nil
}
func logSwap(swap *rpc.SwapStatus) {
fmt.Printf("%v %v %v %v - %v\n",
time.Unix(0, swap.LastUpdateTime).Format(time.RFC3339),
swap.Type, swap.State, btcutil.Amount(swap.Amt),
swap.HtlcAddress,
)
}
func getSwapCliConn(address string) (*grpc.ClientConn, error) {
opts := []grpc.DialOption{
grpc.WithInsecure(),
}
conn, err := grpc.Dial(address, opts...)
if err != nil {
return nil, fmt.Errorf("unable to connect to RPC server: %v", err)
}
return conn, nil
}

156
cmd/swapd/daemon.go Normal file
View file

@ -0,0 +1,156 @@
package main
import (
"context"
"fmt"
"net"
"os"
"os/signal"
"runtime/pprof"
"sync"
"time"
"github.com/lightninglabs/nautilus/client"
clientrpc "github.com/lightninglabs/nautilus/cmd/swapd/rpc"
"github.com/urfave/cli"
"google.golang.org/grpc"
)
// daemon runs swapd in daemon mode. It will listen for grpc connections,
// execute commands and pass back swap status information.
func daemon(ctx *cli.Context) error {
lnd, err := getLnd(ctx)
if err != nil {
return err
}
defer lnd.Close()
swapClient, cleanup, err := getClient(ctx, &lnd.LndServices)
if err != nil {
return err
}
defer cleanup()
// Before starting the client, build an in-memory view of all swaps.
// This view is used to update newly connected clients with the most
// recent swaps.
storedSwaps, err := swapClient.GetUnchargeSwaps()
if err != nil {
return err
}
for _, swap := range storedSwaps {
swaps[swap.Hash] = client.SwapInfo{
SwapType: client.SwapTypeUncharge,
SwapContract: swap.Contract.SwapContract,
State: swap.State(),
SwapHash: swap.Hash,
LastUpdate: swap.LastUpdateTime(),
}
}
// Instantiate the swapd gRPC server.
server := swapClientServer{
impl: swapClient,
lnd: &lnd.LndServices,
}
serverOpts := []grpc.ServerOption{}
grpcServer := grpc.NewServer(serverOpts...)
clientrpc.RegisterSwapClientServer(grpcServer, &server)
// Next, Start the gRPC server listening for HTTP/2 connections.
logger.Infof("Starting RPC listener")
lis, err := net.Listen("tcp", defaultListenAddr)
if err != nil {
return fmt.Errorf("RPC server unable to listen on %s",
defaultListenAddr)
}
defer lis.Close()
statusChan := make(chan client.SwapInfo)
mainCtx, cancel := context.WithCancel(context.Background())
var wg sync.WaitGroup
// Start the swap client itself.
wg.Add(1)
go func() {
defer wg.Done()
logger.Infof("Starting swap client")
err := swapClient.Run(mainCtx, statusChan)
if err != nil {
logger.Error(err)
}
logger.Infof("Swap client stopped")
logger.Infof("Stopping gRPC server")
grpcServer.Stop()
cancel()
}()
// Start a goroutine that broadcasts swap updates to clients.
wg.Add(1)
go func() {
defer wg.Done()
logger.Infof("Waiting for updates")
for {
select {
case swap := <-statusChan:
swapsLock.Lock()
swaps[swap.SwapHash] = swap
for _, subscriber := range subscribers {
select {
case subscriber <- swap:
case <-mainCtx.Done():
return
}
}
swapsLock.Unlock()
case <-mainCtx.Done():
return
}
}
}()
// Start the grpc server.
wg.Add(1)
go func() {
defer wg.Done()
logger.Infof("RPC server listening on %s", lis.Addr())
err = grpcServer.Serve(lis)
if err != nil {
logger.Error(err)
}
}()
interruptChannel := make(chan os.Signal, 1)
signal.Notify(interruptChannel, os.Interrupt)
// Run until the users terminates swapd or an error occurred.
select {
case <-interruptChannel:
logger.Infof("Received SIGINT (Ctrl+C).")
// TODO: Remove debug code.
// Debug code to dump goroutines on hanging exit.
go func() {
time.Sleep(5 * time.Second)
pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
}()
cancel()
case <-mainCtx.Done():
}
wg.Wait()
return nil
}

24
cmd/swapd/log.go Normal file
View file

@ -0,0 +1,24 @@
package main
import (
"os"
"github.com/btcsuite/btclog"
)
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var (
backendLog = btclog.NewBackend(logWriter{})
logger = backendLog.Logger("SWAPD")
)
// logWriter implements an io.Writer that outputs to both standard output and
// the write-end pipe of an initialized log rotator.
type logWriter struct{}
func (logWriter) Write(p []byte) (n int, err error) {
os.Stdout.Write(p)
return len(p), nil
}

70
cmd/swapd/main.go Normal file
View file

@ -0,0 +1,70 @@
package main
import (
"fmt"
"os"
"sync"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/nautilus/client"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/urfave/cli"
)
const (
defaultListenPort = 11010
defaultConfTarget = int32(2)
)
var (
defaultListenAddr = fmt.Sprintf("localhost:%d", defaultListenPort)
defaultSwapletDir = btcutil.AppDataDir("swaplet", false)
swaps = make(map[lntypes.Hash]client.SwapInfo)
subscribers = make(map[int]chan<- interface{})
nextSubscriberID int
swapsLock sync.Mutex
)
func main() {
app := cli.NewApp()
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "network",
Value: "mainnet",
Usage: "network to run on (regtest, testnet, mainnet)",
},
cli.StringFlag{
Name: "lnd",
Value: "localhost:10009",
Usage: "lnd instance rpc address host:port",
},
cli.StringFlag{
Name: "swapserver",
Value: "swap.lightning.today:11009",
Usage: "swap server address host:port",
},
cli.StringFlag{
Name: "macaroonpath",
Usage: "path to lnd macaroon",
},
cli.StringFlag{
Name: "tlspath",
Usage: "path to lnd tls certificate",
},
cli.BoolFlag{
Name: "insecure",
Usage: "disable tls",
},
}
app.Version = "0.0.1"
app.Usage = "swaps execution daemon"
app.Commands = []cli.Command{viewCommand}
app.Action = daemon
err := app.Run(os.Args)
if err != nil {
fmt.Println(err)
}
}

7
cmd/swapd/rpc/gen_protos.sh Executable file
View file

@ -0,0 +1,7 @@
#!/bin/sh
# Generate the protos.
protoc -I/usr/local/include -I. \
-I$GOPATH/src \
--go_out=plugins=grpc:. \
swapclient.proto

View file

@ -0,0 +1,925 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: swapclient.proto
package rpc
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type SwapType int32
const (
// UNCHARGE indicates an uncharge swap (off-chain to on-chain)
SwapType_UNCHARGE SwapType = 0
)
var SwapType_name = map[int32]string{
0: "UNCHARGE",
}
var SwapType_value = map[string]int32{
"UNCHARGE": 0,
}
func (x SwapType) String() string {
return proto.EnumName(SwapType_name, int32(x))
}
func (SwapType) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_swapclient_d9c5a6779b6644af, []int{0}
}
type SwapState int32
const (
// *
// INITIATED is the initial state of a swap. At that point, the initiation
// call to the server has been made and the payment process has been started
// for the swap and prepayment invoices.
SwapState_INITIATED SwapState = 0
// *
// PREIMAGE_REVEALED is reached when the sweep tx publication is first
// attempted. From that point on, we should consider the preimage to no
// longer be secret and we need to do all we can to get the sweep confirmed.
// This state will mostly coalesce with StateHtlcConfirmed, except in the
// case where we wait for fees to come down before we sweep.
SwapState_PREIMAGE_REVEALED SwapState = 1
// *
// SUCCESS is the final swap state that is reached when the sweep tx has
// the required confirmation depth.
SwapState_SUCCESS SwapState = 3
// *
// FAILED is the final swap state for a failed swap with or without loss of
// the swap amount.
SwapState_FAILED SwapState = 4
)
var SwapState_name = map[int32]string{
0: "INITIATED",
1: "PREIMAGE_REVEALED",
3: "SUCCESS",
4: "FAILED",
}
var SwapState_value = map[string]int32{
"INITIATED": 0,
"PREIMAGE_REVEALED": 1,
"SUCCESS": 3,
"FAILED": 4,
}
func (x SwapState) String() string {
return proto.EnumName(SwapState_name, int32(x))
}
func (SwapState) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_swapclient_d9c5a6779b6644af, []int{1}
}
type UnchargeRequest struct {
// *
// Requested swap amount in sat. This does not include the swap and miner
// fee.
Amt int64 `protobuf:"varint,1,opt,name=amt,proto3" json:"amt,omitempty"`
// *
// Base58 encoded destination address for the swap.
Dest string `protobuf:"bytes,2,opt,name=dest,proto3" json:"dest,omitempty"`
// *
// Maximum off-chain fee in msat that may be paid for payment to the server.
// This limit is applied during path finding. Typically this value is taken
// from the response of the GetQuote call.
MaxSwapRoutingFee int64 `protobuf:"varint,3,opt,name=max_swap_routing_fee,json=maxSwapRoutingFee,proto3" json:"max_swap_routing_fee,omitempty"`
// *
// Maximum off-chain fee in msat that may be paid for payment to the server.
// This limit is applied during path finding. Typically this value is taken
// from the response of the GetQuote call.
MaxPrepayRoutingFee int64 `protobuf:"varint,4,opt,name=max_prepay_routing_fee,json=maxPrepayRoutingFee,proto3" json:"max_prepay_routing_fee,omitempty"`
// *
// Maximum we are willing to pay the server for the swap. This value is not
// disclosed in the swap initiation call, but if the server asks for a
// higher fee, we abort the swap. Typically this value is taken from the
// response of the GetQuote call. It includes the prepay amount.
MaxSwapFee int64 `protobuf:"varint,5,opt,name=max_swap_fee,json=maxSwapFee,proto3" json:"max_swap_fee,omitempty"`
// *
// Maximum amount of the swap fee that may be charged as a prepayment.
MaxPrepayAmt int64 `protobuf:"varint,6,opt,name=max_prepay_amt,json=maxPrepayAmt,proto3" json:"max_prepay_amt,omitempty"`
// *
// Maximum in on-chain fees that we are willing to spent. If we want to
// sweep the on-chain htlc and the fee estimate turns out higher than this
// value, we cancel the swap. If the fee estimate is lower, we publish the
// sweep tx.
//
// If the sweep tx isn't confirmed, we are forced to ratchet up fees until
// it is swept. Possibly even exceeding max_miner_fee if we get close to the
// htlc timeout. Because the initial publication revealed the preimage, we
// have no other choice. The server may already have pulled the off-chain
// htlc. Only when the fee becomes higher than the swap amount, we can only
// wait for fees to come down and hope - if we are past the timeout - that
// the server isn't publishing the revocation.
//
// max_miner_fee is typically taken from the response of the GetQuote call.
MaxMinerFee int64 `protobuf:"varint,7,opt,name=max_miner_fee,json=maxMinerFee,proto3" json:"max_miner_fee,omitempty"`
// *
// The channel to uncharge. If zero, the channel to uncharge is selected based
// on the lowest routing fee for the swap payment to the server.
UnchargeChannel uint64 `protobuf:"varint,8,opt,name=uncharge_channel,json=unchargeChannel,proto3" json:"uncharge_channel,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *UnchargeRequest) Reset() { *m = UnchargeRequest{} }
func (m *UnchargeRequest) String() string { return proto.CompactTextString(m) }
func (*UnchargeRequest) ProtoMessage() {}
func (*UnchargeRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_swapclient_d9c5a6779b6644af, []int{0}
}
func (m *UnchargeRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_UnchargeRequest.Unmarshal(m, b)
}
func (m *UnchargeRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_UnchargeRequest.Marshal(b, m, deterministic)
}
func (dst *UnchargeRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_UnchargeRequest.Merge(dst, src)
}
func (m *UnchargeRequest) XXX_Size() int {
return xxx_messageInfo_UnchargeRequest.Size(m)
}
func (m *UnchargeRequest) XXX_DiscardUnknown() {
xxx_messageInfo_UnchargeRequest.DiscardUnknown(m)
}
var xxx_messageInfo_UnchargeRequest proto.InternalMessageInfo
func (m *UnchargeRequest) GetAmt() int64 {
if m != nil {
return m.Amt
}
return 0
}
func (m *UnchargeRequest) GetDest() string {
if m != nil {
return m.Dest
}
return ""
}
func (m *UnchargeRequest) GetMaxSwapRoutingFee() int64 {
if m != nil {
return m.MaxSwapRoutingFee
}
return 0
}
func (m *UnchargeRequest) GetMaxPrepayRoutingFee() int64 {
if m != nil {
return m.MaxPrepayRoutingFee
}
return 0
}
func (m *UnchargeRequest) GetMaxSwapFee() int64 {
if m != nil {
return m.MaxSwapFee
}
return 0
}
func (m *UnchargeRequest) GetMaxPrepayAmt() int64 {
if m != nil {
return m.MaxPrepayAmt
}
return 0
}
func (m *UnchargeRequest) GetMaxMinerFee() int64 {
if m != nil {
return m.MaxMinerFee
}
return 0
}
func (m *UnchargeRequest) GetUnchargeChannel() uint64 {
if m != nil {
return m.UnchargeChannel
}
return 0
}
type SwapResponse struct {
// *
// Swap identifier to track status in the update stream that is returned from
// the Start() call. Currently this is the hash that locks the htlcs.
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *SwapResponse) Reset() { *m = SwapResponse{} }
func (m *SwapResponse) String() string { return proto.CompactTextString(m) }
func (*SwapResponse) ProtoMessage() {}
func (*SwapResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_swapclient_d9c5a6779b6644af, []int{1}
}
func (m *SwapResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_SwapResponse.Unmarshal(m, b)
}
func (m *SwapResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_SwapResponse.Marshal(b, m, deterministic)
}
func (dst *SwapResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_SwapResponse.Merge(dst, src)
}
func (m *SwapResponse) XXX_Size() int {
return xxx_messageInfo_SwapResponse.Size(m)
}
func (m *SwapResponse) XXX_DiscardUnknown() {
xxx_messageInfo_SwapResponse.DiscardUnknown(m)
}
var xxx_messageInfo_SwapResponse proto.InternalMessageInfo
func (m *SwapResponse) GetId() string {
if m != nil {
return m.Id
}
return ""
}
type MonitorRequest struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *MonitorRequest) Reset() { *m = MonitorRequest{} }
func (m *MonitorRequest) String() string { return proto.CompactTextString(m) }
func (*MonitorRequest) ProtoMessage() {}
func (*MonitorRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_swapclient_d9c5a6779b6644af, []int{2}
}
func (m *MonitorRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_MonitorRequest.Unmarshal(m, b)
}
func (m *MonitorRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_MonitorRequest.Marshal(b, m, deterministic)
}
func (dst *MonitorRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_MonitorRequest.Merge(dst, src)
}
func (m *MonitorRequest) XXX_Size() int {
return xxx_messageInfo_MonitorRequest.Size(m)
}
func (m *MonitorRequest) XXX_DiscardUnknown() {
xxx_messageInfo_MonitorRequest.DiscardUnknown(m)
}
var xxx_messageInfo_MonitorRequest proto.InternalMessageInfo
type SwapStatus struct {
// *
// Requested swap amount in sat. This does not include the swap and miner
// fee.
Amt int64 `protobuf:"varint,1,opt,name=amt,proto3" json:"amt,omitempty"`
// *
// Swap identifier to track status in the update stream that is returned from
// the Start() call. Currently this is the hash that locks the htlcs.
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"`
// *
// Swap type
Type SwapType `protobuf:"varint,3,opt,name=type,proto3,enum=rpc.SwapType" json:"type,omitempty"`
// *
// State the swap is currently in, see State enum.
State SwapState `protobuf:"varint,4,opt,name=state,proto3,enum=rpc.SwapState" json:"state,omitempty"`
// *
// Initiation time of the swap.
InitiationTime int64 `protobuf:"varint,5,opt,name=initiation_time,json=initiationTime,proto3" json:"initiation_time,omitempty"`
// *
// Initiation time of the swap.
LastUpdateTime int64 `protobuf:"varint,6,opt,name=last_update_time,json=lastUpdateTime,proto3" json:"last_update_time,omitempty"`
// *
// Htlc address.
HtlcAddress string `protobuf:"bytes,7,opt,name=htlc_address,json=htlcAddress,proto3" json:"htlc_address,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *SwapStatus) Reset() { *m = SwapStatus{} }
func (m *SwapStatus) String() string { return proto.CompactTextString(m) }
func (*SwapStatus) ProtoMessage() {}
func (*SwapStatus) Descriptor() ([]byte, []int) {
return fileDescriptor_swapclient_d9c5a6779b6644af, []int{3}
}
func (m *SwapStatus) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_SwapStatus.Unmarshal(m, b)
}
func (m *SwapStatus) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_SwapStatus.Marshal(b, m, deterministic)
}
func (dst *SwapStatus) XXX_Merge(src proto.Message) {
xxx_messageInfo_SwapStatus.Merge(dst, src)
}
func (m *SwapStatus) XXX_Size() int {
return xxx_messageInfo_SwapStatus.Size(m)
}
func (m *SwapStatus) XXX_DiscardUnknown() {
xxx_messageInfo_SwapStatus.DiscardUnknown(m)
}
var xxx_messageInfo_SwapStatus proto.InternalMessageInfo
func (m *SwapStatus) GetAmt() int64 {
if m != nil {
return m.Amt
}
return 0
}
func (m *SwapStatus) GetId() string {
if m != nil {
return m.Id
}
return ""
}
func (m *SwapStatus) GetType() SwapType {
if m != nil {
return m.Type
}
return SwapType_UNCHARGE
}
func (m *SwapStatus) GetState() SwapState {
if m != nil {
return m.State
}
return SwapState_INITIATED
}
func (m *SwapStatus) GetInitiationTime() int64 {
if m != nil {
return m.InitiationTime
}
return 0
}
func (m *SwapStatus) GetLastUpdateTime() int64 {
if m != nil {
return m.LastUpdateTime
}
return 0
}
func (m *SwapStatus) GetHtlcAddress() string {
if m != nil {
return m.HtlcAddress
}
return ""
}
type TermsRequest struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *TermsRequest) Reset() { *m = TermsRequest{} }
func (m *TermsRequest) String() string { return proto.CompactTextString(m) }
func (*TermsRequest) ProtoMessage() {}
func (*TermsRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_swapclient_d9c5a6779b6644af, []int{4}
}
func (m *TermsRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_TermsRequest.Unmarshal(m, b)
}
func (m *TermsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_TermsRequest.Marshal(b, m, deterministic)
}
func (dst *TermsRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_TermsRequest.Merge(dst, src)
}
func (m *TermsRequest) XXX_Size() int {
return xxx_messageInfo_TermsRequest.Size(m)
}
func (m *TermsRequest) XXX_DiscardUnknown() {
xxx_messageInfo_TermsRequest.DiscardUnknown(m)
}
var xxx_messageInfo_TermsRequest proto.InternalMessageInfo
type TermsResponse struct {
// *
// The node pubkey where the swap payment needs to be paid
// to. This can be used to test connectivity before initiating the swap.
SwapPaymentDest string `protobuf:"bytes,1,opt,name=swap_payment_dest,json=swapPaymentDest,proto3" json:"swap_payment_dest,omitempty"`
// *
// The base fee for a swap (sat)
SwapFeeBase int64 `protobuf:"varint,2,opt,name=swap_fee_base,json=swapFeeBase,proto3" json:"swap_fee_base,omitempty"`
// *
// The fee rate for a swap (parts per million)
SwapFeeRate int64 `protobuf:"varint,3,opt,name=swap_fee_rate,json=swapFeeRate,proto3" json:"swap_fee_rate,omitempty"`
// *
// Required prepay amount
PrepayAmt int64 `protobuf:"varint,4,opt,name=prepay_amt,json=prepayAmt,proto3" json:"prepay_amt,omitempty"`
// *
// Minimum swap amount (sat)
MinSwapAmount int64 `protobuf:"varint,5,opt,name=min_swap_amount,json=minSwapAmount,proto3" json:"min_swap_amount,omitempty"`
// *
// Maximum swap amount (sat)
MaxSwapAmount int64 `protobuf:"varint,6,opt,name=max_swap_amount,json=maxSwapAmount,proto3" json:"max_swap_amount,omitempty"`
// *
// On-chain cltv expiry delta
CltvDelta int32 `protobuf:"varint,7,opt,name=cltv_delta,json=cltvDelta,proto3" json:"cltv_delta,omitempty"`
// *
// Maximum cltv expiry delta
MaxCltv int32 `protobuf:"varint,8,opt,name=max_cltv,json=maxCltv,proto3" json:"max_cltv,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *TermsResponse) Reset() { *m = TermsResponse{} }
func (m *TermsResponse) String() string { return proto.CompactTextString(m) }
func (*TermsResponse) ProtoMessage() {}
func (*TermsResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_swapclient_d9c5a6779b6644af, []int{5}
}
func (m *TermsResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_TermsResponse.Unmarshal(m, b)
}
func (m *TermsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_TermsResponse.Marshal(b, m, deterministic)
}
func (dst *TermsResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_TermsResponse.Merge(dst, src)
}
func (m *TermsResponse) XXX_Size() int {
return xxx_messageInfo_TermsResponse.Size(m)
}
func (m *TermsResponse) XXX_DiscardUnknown() {
xxx_messageInfo_TermsResponse.DiscardUnknown(m)
}
var xxx_messageInfo_TermsResponse proto.InternalMessageInfo
func (m *TermsResponse) GetSwapPaymentDest() string {
if m != nil {
return m.SwapPaymentDest
}
return ""
}
func (m *TermsResponse) GetSwapFeeBase() int64 {
if m != nil {
return m.SwapFeeBase
}
return 0
}
func (m *TermsResponse) GetSwapFeeRate() int64 {
if m != nil {
return m.SwapFeeRate
}
return 0
}
func (m *TermsResponse) GetPrepayAmt() int64 {
if m != nil {
return m.PrepayAmt
}
return 0
}
func (m *TermsResponse) GetMinSwapAmount() int64 {
if m != nil {
return m.MinSwapAmount
}
return 0
}
func (m *TermsResponse) GetMaxSwapAmount() int64 {
if m != nil {
return m.MaxSwapAmount
}
return 0
}
func (m *TermsResponse) GetCltvDelta() int32 {
if m != nil {
return m.CltvDelta
}
return 0
}
func (m *TermsResponse) GetMaxCltv() int32 {
if m != nil {
return m.MaxCltv
}
return 0
}
type QuoteRequest struct {
// *
// Requested swap amount in sat.
Amt int64 `protobuf:"varint,1,opt,name=amt,proto3" json:"amt,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *QuoteRequest) Reset() { *m = QuoteRequest{} }
func (m *QuoteRequest) String() string { return proto.CompactTextString(m) }
func (*QuoteRequest) ProtoMessage() {}
func (*QuoteRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_swapclient_d9c5a6779b6644af, []int{6}
}
func (m *QuoteRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_QuoteRequest.Unmarshal(m, b)
}
func (m *QuoteRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_QuoteRequest.Marshal(b, m, deterministic)
}
func (dst *QuoteRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_QuoteRequest.Merge(dst, src)
}
func (m *QuoteRequest) XXX_Size() int {
return xxx_messageInfo_QuoteRequest.Size(m)
}
func (m *QuoteRequest) XXX_DiscardUnknown() {
xxx_messageInfo_QuoteRequest.DiscardUnknown(m)
}
var xxx_messageInfo_QuoteRequest proto.InternalMessageInfo
func (m *QuoteRequest) GetAmt() int64 {
if m != nil {
return m.Amt
}
return 0
}
type QuoteResponse struct {
// *
// The fee that the swap server is charging for the swap.
SwapFee int64 `protobuf:"varint,1,opt,name=swap_fee,json=swapFee,proto3" json:"swap_fee,omitempty"`
// *
// The part of the swap fee that is requested as a
// prepayment.
PrepayAmt int64 `protobuf:"varint,2,opt,name=prepay_amt,json=prepayAmt,proto3" json:"prepay_amt,omitempty"`
// *
// An estimate of the on-chain fee that needs to be paid to
// sweep the htlc.
MinerFee int64 `protobuf:"varint,3,opt,name=miner_fee,json=minerFee,proto3" json:"miner_fee,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *QuoteResponse) Reset() { *m = QuoteResponse{} }
func (m *QuoteResponse) String() string { return proto.CompactTextString(m) }
func (*QuoteResponse) ProtoMessage() {}
func (*QuoteResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_swapclient_d9c5a6779b6644af, []int{7}
}
func (m *QuoteResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_QuoteResponse.Unmarshal(m, b)
}
func (m *QuoteResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_QuoteResponse.Marshal(b, m, deterministic)
}
func (dst *QuoteResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_QuoteResponse.Merge(dst, src)
}
func (m *QuoteResponse) XXX_Size() int {
return xxx_messageInfo_QuoteResponse.Size(m)
}
func (m *QuoteResponse) XXX_DiscardUnknown() {
xxx_messageInfo_QuoteResponse.DiscardUnknown(m)
}
var xxx_messageInfo_QuoteResponse proto.InternalMessageInfo
func (m *QuoteResponse) GetSwapFee() int64 {
if m != nil {
return m.SwapFee
}
return 0
}
func (m *QuoteResponse) GetPrepayAmt() int64 {
if m != nil {
return m.PrepayAmt
}
return 0
}
func (m *QuoteResponse) GetMinerFee() int64 {
if m != nil {
return m.MinerFee
}
return 0
}
func init() {
proto.RegisterType((*UnchargeRequest)(nil), "rpc.UnchargeRequest")
proto.RegisterType((*SwapResponse)(nil), "rpc.SwapResponse")
proto.RegisterType((*MonitorRequest)(nil), "rpc.MonitorRequest")
proto.RegisterType((*SwapStatus)(nil), "rpc.SwapStatus")
proto.RegisterType((*TermsRequest)(nil), "rpc.TermsRequest")
proto.RegisterType((*TermsResponse)(nil), "rpc.TermsResponse")
proto.RegisterType((*QuoteRequest)(nil), "rpc.QuoteRequest")
proto.RegisterType((*QuoteResponse)(nil), "rpc.QuoteResponse")
proto.RegisterEnum("rpc.SwapType", SwapType_name, SwapType_value)
proto.RegisterEnum("rpc.SwapState", SwapState_name, SwapState_value)
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// SwapClientClient is the client API for SwapClient service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type SwapClientClient interface {
// *
// Uncharge initiates an uncharge swap with the given parameters. The call
// returns after the swap has been set up with the swap server. From that
// point onwards, progress can be tracked via the SwapStatus stream
// that is returned from Monitor().
Uncharge(ctx context.Context, in *UnchargeRequest, opts ...grpc.CallOption) (*SwapResponse, error)
// *
// Monitor will return a stream of swap updates for currently active swaps.
Monitor(ctx context.Context, in *MonitorRequest, opts ...grpc.CallOption) (SwapClient_MonitorClient, error)
// *
// GetTerms returns the terms that the server enforces for swaps.
GetUnchargeTerms(ctx context.Context, in *TermsRequest, opts ...grpc.CallOption) (*TermsResponse, error)
// *
// GetQuote returns a quote for a swap with the provided parameters.
GetUnchargeQuote(ctx context.Context, in *QuoteRequest, opts ...grpc.CallOption) (*QuoteResponse, error)
}
type swapClientClient struct {
cc *grpc.ClientConn
}
func NewSwapClientClient(cc *grpc.ClientConn) SwapClientClient {
return &swapClientClient{cc}
}
func (c *swapClientClient) Uncharge(ctx context.Context, in *UnchargeRequest, opts ...grpc.CallOption) (*SwapResponse, error) {
out := new(SwapResponse)
err := c.cc.Invoke(ctx, "/rpc.SwapClient/Uncharge", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *swapClientClient) Monitor(ctx context.Context, in *MonitorRequest, opts ...grpc.CallOption) (SwapClient_MonitorClient, error) {
stream, err := c.cc.NewStream(ctx, &_SwapClient_serviceDesc.Streams[0], "/rpc.SwapClient/Monitor", opts...)
if err != nil {
return nil, err
}
x := &swapClientMonitorClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type SwapClient_MonitorClient interface {
Recv() (*SwapStatus, error)
grpc.ClientStream
}
type swapClientMonitorClient struct {
grpc.ClientStream
}
func (x *swapClientMonitorClient) Recv() (*SwapStatus, error) {
m := new(SwapStatus)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *swapClientClient) GetUnchargeTerms(ctx context.Context, in *TermsRequest, opts ...grpc.CallOption) (*TermsResponse, error) {
out := new(TermsResponse)
err := c.cc.Invoke(ctx, "/rpc.SwapClient/GetUnchargeTerms", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *swapClientClient) GetUnchargeQuote(ctx context.Context, in *QuoteRequest, opts ...grpc.CallOption) (*QuoteResponse, error) {
out := new(QuoteResponse)
err := c.cc.Invoke(ctx, "/rpc.SwapClient/GetUnchargeQuote", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// SwapClientServer is the server API for SwapClient service.
type SwapClientServer interface {
// *
// Uncharge initiates an uncharge swap with the given parameters. The call
// returns after the swap has been set up with the swap server. From that
// point onwards, progress can be tracked via the SwapStatus stream
// that is returned from Monitor().
Uncharge(context.Context, *UnchargeRequest) (*SwapResponse, error)
// *
// Monitor will return a stream of swap updates for currently active swaps.
Monitor(*MonitorRequest, SwapClient_MonitorServer) error
// *
// GetTerms returns the terms that the server enforces for swaps.
GetUnchargeTerms(context.Context, *TermsRequest) (*TermsResponse, error)
// *
// GetQuote returns a quote for a swap with the provided parameters.
GetUnchargeQuote(context.Context, *QuoteRequest) (*QuoteResponse, error)
}
func RegisterSwapClientServer(s *grpc.Server, srv SwapClientServer) {
s.RegisterService(&_SwapClient_serviceDesc, srv)
}
func _SwapClient_Uncharge_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UnchargeRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SwapClientServer).Uncharge(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/rpc.SwapClient/Uncharge",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SwapClientServer).Uncharge(ctx, req.(*UnchargeRequest))
}
return interceptor(ctx, in, info, handler)
}
func _SwapClient_Monitor_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(MonitorRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(SwapClientServer).Monitor(m, &swapClientMonitorServer{stream})
}
type SwapClient_MonitorServer interface {
Send(*SwapStatus) error
grpc.ServerStream
}
type swapClientMonitorServer struct {
grpc.ServerStream
}
func (x *swapClientMonitorServer) Send(m *SwapStatus) error {
return x.ServerStream.SendMsg(m)
}
func _SwapClient_GetUnchargeTerms_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(TermsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SwapClientServer).GetUnchargeTerms(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/rpc.SwapClient/GetUnchargeTerms",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SwapClientServer).GetUnchargeTerms(ctx, req.(*TermsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _SwapClient_GetUnchargeQuote_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(QuoteRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SwapClientServer).GetUnchargeQuote(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/rpc.SwapClient/GetUnchargeQuote",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SwapClientServer).GetUnchargeQuote(ctx, req.(*QuoteRequest))
}
return interceptor(ctx, in, info, handler)
}
var _SwapClient_serviceDesc = grpc.ServiceDesc{
ServiceName: "rpc.SwapClient",
HandlerType: (*SwapClientServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Uncharge",
Handler: _SwapClient_Uncharge_Handler,
},
{
MethodName: "GetUnchargeTerms",
Handler: _SwapClient_GetUnchargeTerms_Handler,
},
{
MethodName: "GetUnchargeQuote",
Handler: _SwapClient_GetUnchargeQuote_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "Monitor",
Handler: _SwapClient_Monitor_Handler,
ServerStreams: true,
},
},
Metadata: "swapclient.proto",
}
func init() { proto.RegisterFile("swapclient.proto", fileDescriptor_swapclient_d9c5a6779b6644af) }
var fileDescriptor_swapclient_d9c5a6779b6644af = []byte{
// 744 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x54, 0x5d, 0x4f, 0xe3, 0x46,
0x14, 0x25, 0xdf, 0xf1, 0x4d, 0xe2, 0x38, 0x03, 0xad, 0x02, 0x15, 0x55, 0xb0, 0x50, 0x9b, 0xf2,
0x40, 0x5b, 0x78, 0xea, 0xa3, 0x9b, 0x18, 0x9a, 0x6a, 0x41, 0xec, 0x24, 0xd9, 0x57, 0x6b, 0x48,
0x06, 0xb0, 0x94, 0xb1, 0xbd, 0x9e, 0x31, 0x24, 0xff, 0x69, 0x1f, 0xf7, 0x57, 0xad, 0xb4, 0xff,
0x61, 0x35, 0x1f, 0x36, 0x09, 0xda, 0x7d, 0xb3, 0xce, 0x3d, 0xf7, 0x8c, 0xef, 0x99, 0x73, 0x07,
0x1c, 0xfe, 0x42, 0x92, 0xc5, 0x2a, 0xa4, 0x91, 0x38, 0x4f, 0xd2, 0x58, 0xc4, 0xa8, 0x92, 0x26,
0x0b, 0xf7, 0x73, 0x19, 0xba, 0xf3, 0x68, 0xf1, 0x44, 0xd2, 0x47, 0x8a, 0xe9, 0xc7, 0x8c, 0x72,
0x81, 0x1c, 0xa8, 0x10, 0x26, 0xfa, 0xa5, 0x41, 0x69, 0x58, 0xc1, 0xf2, 0x13, 0x21, 0xa8, 0x2e,
0x29, 0x17, 0xfd, 0xf2, 0xa0, 0x34, 0xb4, 0xb0, 0xfa, 0x46, 0x7f, 0xc2, 0x01, 0x23, 0xeb, 0x40,
0xca, 0x06, 0x69, 0x9c, 0x89, 0x30, 0x7a, 0x0c, 0x1e, 0x28, 0xed, 0x57, 0x54, 0x5b, 0x8f, 0x91,
0xf5, 0xf4, 0x85, 0x24, 0x58, 0x57, 0xae, 0x28, 0x45, 0x97, 0xf0, 0xb3, 0x6c, 0x48, 0x52, 0x9a,
0x90, 0xcd, 0x4e, 0x4b, 0x55, 0xb5, 0xec, 0x33, 0xb2, 0xbe, 0x53, 0xc5, 0xad, 0xa6, 0x01, 0xb4,
0x8b, 0x53, 0x24, 0xb5, 0xa6, 0xa8, 0x60, 0xd4, 0x25, 0xe3, 0x14, 0xec, 0x2d, 0x59, 0xf9, 0xe3,
0x75, 0xc5, 0x69, 0x17, 0x72, 0x1e, 0x13, 0xc8, 0x85, 0x8e, 0x64, 0xb1, 0x30, 0xa2, 0xa9, 0x12,
0x6a, 0x28, 0x52, 0x8b, 0x91, 0xf5, 0x8d, 0xc4, 0xa4, 0xd2, 0x1f, 0xe0, 0x64, 0xc6, 0x8a, 0x60,
0xf1, 0x44, 0xa2, 0x88, 0xae, 0xfa, 0xcd, 0x41, 0x69, 0x58, 0xc5, 0xdd, 0x1c, 0x1f, 0x69, 0xd8,
0xfd, 0x15, 0xda, 0x6a, 0x3a, 0xca, 0x93, 0x38, 0xe2, 0x14, 0xd9, 0x50, 0x0e, 0x97, 0xca, 0x31,
0x0b, 0x97, 0xc3, 0xa5, 0xeb, 0x80, 0x7d, 0x13, 0x47, 0xa1, 0x88, 0x53, 0x63, 0xaa, 0xfb, 0xb5,
0x04, 0x20, 0x5b, 0xa6, 0x82, 0x88, 0x8c, 0x7f, 0xc7, 0x63, 0x2d, 0x51, 0xce, 0x25, 0xd0, 0x09,
0x54, 0xc5, 0x26, 0xd1, 0x7e, 0xda, 0x17, 0x9d, 0xf3, 0x34, 0x59, 0x9c, 0x4b, 0x81, 0xd9, 0x26,
0xa1, 0x58, 0x95, 0xd0, 0x29, 0xd4, 0xb8, 0x20, 0x42, 0x1b, 0x68, 0x5f, 0xd8, 0x05, 0x47, 0x1e,
0x42, 0xb1, 0x2e, 0xa2, 0xdf, 0xa1, 0x1b, 0x46, 0xa1, 0x08, 0x89, 0x08, 0xe3, 0x28, 0x10, 0x21,
0xcb, 0x5d, 0xb4, 0x5f, 0xe1, 0x59, 0xc8, 0x28, 0x1a, 0x82, 0xb3, 0x22, 0x5c, 0x04, 0x59, 0xb2,
0x24, 0x82, 0x6a, 0xa6, 0xf6, 0xd2, 0x96, 0xf8, 0x5c, 0xc1, 0x8a, 0x79, 0x02, 0xed, 0x27, 0xb1,
0x5a, 0x04, 0x64, 0xb9, 0x4c, 0x29, 0xe7, 0xca, 0x4c, 0x0b, 0xb7, 0x24, 0xe6, 0x69, 0xc8, 0xb5,
0xa1, 0x3d, 0xa3, 0x29, 0xe3, 0xf9, 0xfc, 0x9f, 0xca, 0xd0, 0x31, 0x80, 0xf1, 0xec, 0x0c, 0x7a,
0xea, 0x5a, 0x13, 0xb2, 0x61, 0x34, 0x12, 0x81, 0x4a, 0x98, 0xb6, 0xb0, 0x2b, 0x0b, 0x77, 0x1a,
0x1f, 0xcb, 0xb0, 0xb9, 0xd0, 0xc9, 0x23, 0x10, 0xdc, 0x13, 0x4e, 0x95, 0x4f, 0x15, 0xdc, 0xe2,
0x3a, 0x04, 0xff, 0x12, 0x4e, 0x77, 0x38, 0xa9, 0x74, 0xa5, 0xb2, 0xc3, 0xc1, 0xd2, 0x8b, 0x63,
0x80, 0xad, 0xa0, 0xe8, 0xdc, 0x59, 0x49, 0x91, 0x92, 0xdf, 0xa0, 0xcb, 0xc2, 0x48, 0xa7, 0x8d,
0xb0, 0x38, 0x8b, 0x84, 0xb1, 0xaa, 0xc3, 0xc2, 0x48, 0x1a, 0xeb, 0x29, 0x50, 0xf1, 0xf2, 0x54,
0x1a, 0x5e, 0xdd, 0xf0, 0x74, 0x30, 0x0d, 0xef, 0x18, 0x60, 0xb1, 0x12, 0xcf, 0xc1, 0x92, 0xae,
0x04, 0x51, 0x2e, 0xd5, 0xb0, 0x25, 0x91, 0xb1, 0x04, 0xd0, 0x21, 0x34, 0xa5, 0x8c, 0x04, 0x54,
0xd0, 0x6a, 0xb8, 0xc1, 0xc8, 0x7a, 0xb4, 0x12, 0xcf, 0xee, 0x00, 0xda, 0xef, 0xb3, 0x58, 0xfc,
0x78, 0x27, 0xdd, 0x07, 0xe8, 0x18, 0x86, 0xf1, 0xf3, 0x10, 0x9a, 0xc5, 0x9a, 0x68, 0x5e, 0xc3,
0x8c, 0xfe, 0x66, 0xec, 0xf2, 0xdb, 0xb1, 0x7f, 0x01, 0xeb, 0x75, 0x31, 0xb4, 0x6b, 0x4d, 0x66,
0xb6, 0xe2, 0xac, 0x0f, 0xcd, 0x3c, 0x76, 0xa8, 0x0d, 0xcd, 0xf9, 0xed, 0xe8, 0x3f, 0x0f, 0x5f,
0xfb, 0xce, 0xde, 0xd9, 0xff, 0x60, 0x15, 0x61, 0x43, 0x1d, 0xb0, 0x26, 0xb7, 0x93, 0xd9, 0xc4,
0x9b, 0xf9, 0x63, 0x67, 0x0f, 0xfd, 0x04, 0xbd, 0x3b, 0xec, 0x4f, 0x6e, 0xbc, 0x6b, 0x3f, 0xc0,
0xfe, 0x07, 0xdf, 0x7b, 0xe7, 0x8f, 0x9d, 0x12, 0x6a, 0x41, 0x63, 0x3a, 0x1f, 0x8d, 0xfc, 0xe9,
0xd4, 0xa9, 0x20, 0x80, 0xfa, 0x95, 0x37, 0x91, 0x85, 0xea, 0xc5, 0x17, 0xb3, 0x1e, 0x23, 0xf5,
0x42, 0xa1, 0x4b, 0x68, 0xe6, 0xaf, 0x12, 0x3a, 0x50, 0xb1, 0x7e, 0xf3, 0x48, 0x1d, 0xf5, 0x8a,
0xb0, 0x17, 0x06, 0xfc, 0x0d, 0x0d, 0xb3, 0x74, 0x68, 0x5f, 0x55, 0x77, 0x57, 0xf0, 0xa8, 0xbb,
0xb3, 0x1f, 0x19, 0xff, 0xab, 0x84, 0xfe, 0x01, 0xe7, 0x9a, 0x8a, 0x5c, 0x5b, 0xe5, 0x13, 0x69,
0xe5, 0xed, 0xf0, 0x1e, 0xa1, 0x6d, 0xc8, 0x9c, 0xb6, 0xdb, 0xaa, 0xae, 0xc2, 0xb4, 0x6e, 0x5f,
0x9c, 0x69, 0xdd, 0xb9, 0xa9, 0xfb, 0xba, 0x7a, 0x80, 0x2f, 0xbf, 0x05, 0x00, 0x00, 0xff, 0xff,
0x4d, 0x46, 0x11, 0xa8, 0x94, 0x05, 0x00, 0x00,
}

View file

@ -0,0 +1,259 @@
syntax = "proto3";
package rpc;
message UnchargeRequest {
/**
Requested swap amount in sat. This does not include the swap and miner
fee.
*/
int64 amt = 1;
/**
Base58 encoded destination address for the swap.
*/
string dest = 2;
/**
Maximum off-chain fee in msat that may be paid for payment to the server.
This limit is applied during path finding. Typically this value is taken
from the response of the GetQuote call.
*/
int64 max_swap_routing_fee = 3;
/**
Maximum off-chain fee in msat that may be paid for payment to the server.
This limit is applied during path finding. Typically this value is taken
from the response of the GetQuote call.
*/
int64 max_prepay_routing_fee = 4;
/**
Maximum we are willing to pay the server for the swap. This value is not
disclosed in the swap initiation call, but if the server asks for a
higher fee, we abort the swap. Typically this value is taken from the
response of the GetQuote call. It includes the prepay amount.
*/
int64 max_swap_fee = 5;
/**
Maximum amount of the swap fee that may be charged as a prepayment.
*/
int64 max_prepay_amt = 6;
/**
Maximum in on-chain fees that we are willing to spent. If we want to
sweep the on-chain htlc and the fee estimate turns out higher than this
value, we cancel the swap. If the fee estimate is lower, we publish the
sweep tx.
If the sweep tx isn't confirmed, we are forced to ratchet up fees until
it is swept. Possibly even exceeding max_miner_fee if we get close to the
htlc timeout. Because the initial publication revealed the preimage, we
have no other choice. The server may already have pulled the off-chain
htlc. Only when the fee becomes higher than the swap amount, we can only
wait for fees to come down and hope - if we are past the timeout - that
the server isn't publishing the revocation.
max_miner_fee is typically taken from the response of the GetQuote call.
*/
int64 max_miner_fee = 7;
/**
The channel to uncharge. If zero, the channel to uncharge is selected based
on the lowest routing fee for the swap payment to the server.
*/
uint64 uncharge_channel = 8;
}
message SwapResponse {
/**
Swap identifier to track status in the update stream that is returned from
the Start() call. Currently this is the hash that locks the htlcs.
*/
string id = 1;
}
message MonitorRequest{
}
message SwapStatus {
/**
Requested swap amount in sat. This does not include the swap and miner
fee.
*/
int64 amt = 1;
/**
Swap identifier to track status in the update stream that is returned from
the Start() call. Currently this is the hash that locks the htlcs.
*/
string id = 2;
/**
Swap type
*/
SwapType type = 3;
/**
State the swap is currently in, see State enum.
*/
SwapState state = 4;
/**
Initiation time of the swap.
*/
int64 initiation_time = 5;
/**
Initiation time of the swap.
*/
int64 last_update_time = 6;
/**
Htlc address.
*/
string htlc_address = 7;
}
enum SwapType {
// UNCHARGE indicates an uncharge swap (off-chain to on-chain)
UNCHARGE = 0;
}
enum SwapState {
/**
INITIATED is the initial state of a swap. At that point, the initiation
call to the server has been made and the payment process has been started
for the swap and prepayment invoices.
*/
INITIATED = 0;
/**
PREIMAGE_REVEALED is reached when the sweep tx publication is first
attempted. From that point on, we should consider the preimage to no
longer be secret and we need to do all we can to get the sweep confirmed.
This state will mostly coalesce with StateHtlcConfirmed, except in the
case where we wait for fees to come down before we sweep.
*/
PREIMAGE_REVEALED = 1;
/**
SUCCESS is the final swap state that is reached when the sweep tx has
the required confirmation depth.
*/
SUCCESS = 3;
/**
FAILED is the final swap state for a failed swap with or without loss of
the swap amount.
*/
FAILED = 4;
}
message TermsRequest {
}
message TermsResponse {
/**
The node pubkey where the swap payment needs to be paid
to. This can be used to test connectivity before initiating the swap.
*/
string swap_payment_dest = 1;
/**
The base fee for a swap (sat)
*/
int64 swap_fee_base = 2;
/**
The fee rate for a swap (parts per million)
*/
int64 swap_fee_rate = 3;
/**
Required prepay amount
*/
int64 prepay_amt = 4;
/**
Minimum swap amount (sat)
*/
int64 min_swap_amount = 5;
/**
Maximum swap amount (sat)
*/
int64 max_swap_amount = 6;
/**
On-chain cltv expiry delta
*/
int32 cltv_delta = 7;
/**
Maximum cltv expiry delta
*/
int32 max_cltv = 8;
}
message QuoteRequest {
/**
Requested swap amount in sat.
*/
int64 amt = 1;
}
message QuoteResponse {
/**
The fee that the swap server is charging for the swap.
*/
int64 swap_fee = 1;
/**
The part of the swap fee that is requested as a
prepayment.
*/
int64 prepay_amt = 2;
/**
An estimate of the on-chain fee that needs to be paid to
sweep the htlc.
*/
int64 miner_fee = 3;
}
/**
SwapClient is a service that handles the client side process of onchain/offchain
swaps. The service is designed for a single client.
*/
service SwapClient {
/**
Uncharge initiates an uncharge swap with the given parameters. The call
returns after the swap has been set up with the swap server. From that
point onwards, progress can be tracked via the SwapStatus stream
that is returned from Monitor().
*/
rpc Uncharge(UnchargeRequest) returns (SwapResponse);
/**
Monitor will return a stream of swap updates for currently active swaps.
*/
rpc Monitor(MonitorRequest) returns(stream SwapStatus);
/**
GetTerms returns the terms that the server enforces for swaps.
*/
rpc GetUnchargeTerms(TermsRequest) returns(TermsResponse);
/**
GetQuote returns a quote for a swap with the provided parameters.
*/
rpc GetUnchargeQuote(QuoteRequest) returns(QuoteResponse);
}

View file

@ -0,0 +1,247 @@
package main
import (
"context"
"fmt"
"sort"
"github.com/lightningnetwork/lnd/queue"
"github.com/lightninglabs/nautilus/lndclient"
"github.com/lightninglabs/nautilus/utils"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/nautilus/client"
clientrpc "github.com/lightninglabs/nautilus/cmd/swapd/rpc"
)
const completedSwapsCount = 5
// swapClientServer implements the grpc service exposed by swapd.
type swapClientServer struct {
impl *client.Client
lnd *lndclient.LndServices
}
// Uncharge initiates an uncharge swap with the given parameters. The call
// returns after the swap has been set up with the swap server. From that point
// onwards, progress can be tracked via the UnchargeStatus stream that is
// returned from Monitor().
func (s *swapClientServer) Uncharge(ctx context.Context,
in *clientrpc.UnchargeRequest) (
*clientrpc.SwapResponse, error) {
logger.Infof("Uncharge request received")
var sweepAddr btcutil.Address
if in.Dest == "" {
// Generate sweep address if none specified.
var err error
sweepAddr, err = s.lnd.WalletKit.NextAddr(context.Background())
if err != nil {
return nil, fmt.Errorf("NextAddr error: %v", err)
}
} else {
var err error
sweepAddr, err = btcutil.DecodeAddress(in.Dest, nil)
if err != nil {
return nil, fmt.Errorf("decode address: %v", err)
}
}
req := &client.UnchargeRequest{
Amount: btcutil.Amount(in.Amt),
DestAddr: sweepAddr,
MaxMinerFee: btcutil.Amount(in.MaxMinerFee),
MaxPrepayAmount: btcutil.Amount(in.MaxPrepayAmt),
MaxPrepayRoutingFee: btcutil.Amount(in.MaxPrepayRoutingFee),
MaxSwapRoutingFee: btcutil.Amount(in.MaxSwapRoutingFee),
MaxSwapFee: btcutil.Amount(in.MaxSwapFee),
SweepConfTarget: defaultConfTarget,
}
if in.UnchargeChannel != 0 {
req.UnchargeChannel = &in.UnchargeChannel
}
hash, err := s.impl.Uncharge(ctx, req)
if err != nil {
logger.Errorf("Uncharge: %v", err)
return nil, err
}
return &clientrpc.SwapResponse{
Id: hash.String(),
}, nil
}
func (s *swapClientServer) marshallSwap(swap *client.SwapInfo) (
*clientrpc.SwapStatus, error) {
var state clientrpc.SwapState
switch swap.State {
case client.StateInitiated:
state = clientrpc.SwapState_INITIATED
case client.StatePreimageRevealed:
state = clientrpc.SwapState_PREIMAGE_REVEALED
case client.StateSuccess:
state = clientrpc.SwapState_SUCCESS
default:
// Return less granular status over rpc.
state = clientrpc.SwapState_FAILED
}
htlc, err := utils.NewHtlc(swap.CltvExpiry, swap.SenderKey,
swap.ReceiverKey, swap.SwapHash,
)
if err != nil {
return nil, err
}
address, err := htlc.Address(s.lnd.ChainParams)
if err != nil {
return nil, err
}
return &clientrpc.SwapStatus{
Amt: int64(swap.AmountRequested),
Id: swap.SwapHash.String(),
State: state,
InitiationTime: swap.InitiationTime.UnixNano(),
LastUpdateTime: swap.LastUpdate.UnixNano(),
HtlcAddress: address.EncodeAddress(),
Type: clientrpc.SwapType_UNCHARGE,
}, nil
}
// Monitor will return a stream of swap updates for currently active swaps.
func (s *swapClientServer) Monitor(in *clientrpc.MonitorRequest,
server clientrpc.SwapClient_MonitorServer) error {
logger.Infof("Monitor request received")
send := func(info client.SwapInfo) error {
rpcSwap, err := s.marshallSwap(&info)
if err != nil {
return err
}
return server.Send(rpcSwap)
}
// Start a notification queue for this subscriber.
queue := queue.NewConcurrentQueue(20)
queue.Start()
// Add this subscriber to the global subscriber list. Also create a
// snapshot of all pending and completed swaps within the lock, to
// prevent subscribers from receiving duplicate updates.
swapsLock.Lock()
id := nextSubscriberID
nextSubscriberID++
subscribers[id] = queue.ChanIn()
var pendingSwaps, completedSwaps []client.SwapInfo
for _, swap := range swaps {
if swap.State.Type() == client.StateTypePending {
pendingSwaps = append(pendingSwaps, swap)
} else {
completedSwaps = append(completedSwaps, swap)
}
}
swapsLock.Unlock()
defer func() {
queue.Stop()
swapsLock.Lock()
delete(subscribers, id)
swapsLock.Unlock()
}()
// Sort completed swaps new to old.
sort.Slice(completedSwaps, func(i, j int) bool {
return completedSwaps[i].LastUpdate.After(
completedSwaps[j].LastUpdate,
)
})
// Discard all but top x latest.
if len(completedSwaps) > completedSwapsCount {
completedSwaps = completedSwaps[:completedSwapsCount]
}
// Concatenate both sets.
filteredSwaps := append(pendingSwaps, completedSwaps...)
// Sort again, but this time old to new.
sort.Slice(filteredSwaps, func(i, j int) bool {
return filteredSwaps[i].LastUpdate.Before(
filteredSwaps[j].LastUpdate,
)
})
// Return swaps to caller.
for _, swap := range filteredSwaps {
if err := send(swap); err != nil {
return err
}
}
// As long as the client is connected, keep passing through swap
// updates.
for {
select {
case queueItem, ok := <-queue.ChanOut():
if !ok {
return nil
}
swap := queueItem.(client.SwapInfo)
if err := send(swap); err != nil {
return err
}
case <-server.Context().Done():
return nil
}
}
}
// GetTerms returns the terms that the server enforces for swaps.
func (s *swapClientServer) GetUnchargeTerms(ctx context.Context, req *clientrpc.TermsRequest) (
*clientrpc.TermsResponse, error) {
logger.Infof("Terms request received")
terms, err := s.impl.UnchargeTerms(ctx)
if err != nil {
logger.Errorf("Terms request: %v", err)
return nil, err
}
return &clientrpc.TermsResponse{
MinSwapAmount: int64(terms.MinSwapAmount),
MaxSwapAmount: int64(terms.MaxSwapAmount),
PrepayAmt: int64(terms.PrepayAmt),
SwapFeeBase: int64(terms.SwapFeeBase),
SwapFeeRate: int64(terms.SwapFeeRate),
CltvDelta: int32(terms.CltvDelta),
}, nil
}
// GetQuote returns a quote for a swap with the provided parameters.
func (s *swapClientServer) GetUnchargeQuote(ctx context.Context,
req *clientrpc.QuoteRequest) (*clientrpc.QuoteResponse, error) {
quote, err := s.impl.UnchargeQuote(ctx, &client.UnchargeQuoteRequest{
Amount: btcutil.Amount(req.Amt),
SweepConfTarget: defaultConfTarget,
})
if err != nil {
return nil, err
}
return &clientrpc.QuoteResponse{
MinerFee: int64(quote.MinerFee),
PrepayAmt: int64(quote.PrepayAmount),
SwapFee: int64(quote.SwapFee),
}, nil
}

49
cmd/swapd/utils.go Normal file
View file

@ -0,0 +1,49 @@
package main
import (
"os"
"path/filepath"
"github.com/lightninglabs/nautilus/client"
"github.com/lightninglabs/nautilus/lndclient"
"github.com/urfave/cli"
)
// getLnd returns an instance of the lnd services proxy.
func getLnd(ctx *cli.Context) (*lndclient.GrpcLndServices, error) {
network := ctx.GlobalString("network")
return lndclient.NewLndServices(ctx.GlobalString("lnd"),
"client", network, ctx.GlobalString("macaroonpath"),
ctx.GlobalString("tlspath"),
)
}
// getClient returns an instance of the swap client.
func getClient(ctx *cli.Context, lnd *lndclient.LndServices) (*client.Client, func(), error) {
network := ctx.GlobalString("network")
storeDir, err := getStoreDir(network)
if err != nil {
return nil, nil, err
}
swapClient, cleanUp, err := client.NewClient(
storeDir, ctx.GlobalString("swapserver"),
ctx.GlobalBool("insecure"), lnd,
)
if err != nil {
return nil, nil, err
}
return swapClient, cleanUp, nil
}
func getStoreDir(network string) (string, error) {
dir := filepath.Join(defaultSwapletDir, network)
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
return "", err
}
return dir, nil
}

89
cmd/swapd/view.go Normal file
View file

@ -0,0 +1,89 @@
package main
import (
"fmt"
"strconv"
"github.com/lightninglabs/nautilus/utils"
"github.com/urfave/cli"
)
var viewCommand = cli.Command{
Name: "view",
Usage: `view all swaps in the database. This command can only be
executed when swapd is not running.`,
Description: `
Show all pending and completed swaps.`,
Action: view,
}
// view prints all swaps currently in the database.
func view(ctx *cli.Context) error {
network := ctx.GlobalString("network")
chainParams, err := utils.ChainParamsFromNetwork(network)
if err != nil {
return err
}
lnd, err := getLnd(ctx)
if err != nil {
return err
}
defer lnd.Close()
swapClient, cleanup, err := getClient(ctx, &lnd.LndServices)
if err != nil {
return err
}
defer cleanup()
swaps, err := swapClient.GetUnchargeSwaps()
if err != nil {
return err
}
for _, s := range swaps {
htlc, err := utils.NewHtlc(
s.Contract.CltvExpiry,
s.Contract.SenderKey,
s.Contract.ReceiverKey,
s.Hash,
)
if err != nil {
return err
}
htlcAddress, err := htlc.Address(chainParams)
if err != nil {
return err
}
fmt.Printf("%v\n", s.Hash)
fmt.Printf(" Created: %v (height %v)\n",
s.Contract.InitiationTime, s.Contract.InitiationHeight,
)
fmt.Printf(" Preimage: %v\n", s.Contract.Preimage)
fmt.Printf(" Htlc address: %v\n", htlcAddress)
unchargeChannel := "any"
if s.Contract.UnchargeChannel != nil {
unchargeChannel = strconv.FormatUint(
*s.Contract.UnchargeChannel, 10,
)
}
fmt.Printf(" Uncharge channel: %v\n", unchargeChannel)
fmt.Printf(" Dest: %v\n", s.Contract.DestAddr)
fmt.Printf(" Amt: %v, Expiry: %v\n",
s.Contract.AmountRequested, s.Contract.CltvExpiry,
)
for i, e := range s.Events {
fmt.Printf(" Update %v, Time %v, State: %v\n",
i, e.Time, e.State,
)
}
fmt.Println()
}
return nil
}