2
0
mirror of https://github.com/lightninglabs/loop synced 2024-11-11 13:11:12 +00:00
loop/cmd/loop/main.go
2020-01-31 13:57:29 +01:00

251 lines
5.8 KiB
Go

package main
import (
"bytes"
"errors"
"fmt"
"os"
"strconv"
"time"
"github.com/lightninglabs/loop"
"github.com/lightninglabs/loop/looprpc"
"github.com/lightninglabs/loop/swap"
"github.com/lightninglabs/protobuf-hex-display/json"
"github.com/lightninglabs/protobuf-hex-display/jsonpb"
"github.com/lightninglabs/protobuf-hex-display/proto"
"github.com/btcsuite/btcutil"
"github.com/urfave/cli"
"google.golang.org/grpc"
)
var (
// 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(20000)
defaultSwapWaitTime = 30 * time.Minute
// maxMsgRecvSize is the largest message our client will receive. We
// set this to 200MiB atm.
maxMsgRecvSize = grpc.MaxCallRecvMsgSize(1 * 1024 * 1024 * 200)
)
func printJSON(resp interface{}) {
b, err := json.Marshal(resp)
if err != nil {
fatal(err)
}
var out bytes.Buffer
err = json.Indent(&out, b, "", "\t")
if err != nil {
fatal(err)
}
out.WriteString("\n")
_, _ = out.WriteTo(os.Stdout)
}
func printRespJSON(resp proto.Message) {
jsonMarshaler := &jsonpb.Marshaler{
OrigName: true,
EmitDefaults: true,
Indent: " ",
}
jsonStr, err := jsonMarshaler.MarshalToString(resp)
if err != nil {
fmt.Println("unable to decode response: ", err)
return
}
fmt.Println(jsonStr)
}
func fatal(err error) {
fmt.Fprintf(os.Stderr, "[loop] %v\n", err)
os.Exit(1)
}
func main() {
app := cli.NewApp()
app.Version = loop.Version()
app.Name = "loop"
app.Usage = "control plane for your loopd"
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "rpcserver",
Value: "localhost:11010",
Usage: "loopd daemon address host:port",
},
}
app.Commands = []cli.Command{
loopOutCommand, loopInCommand, termsCommand,
monitorCommand, quoteCommand, listAuthCommand,
listSwapsCommand, swapInfoCommand,
}
err := app.Run(os.Args)
if err != nil {
fatal(err)
}
}
func getClient(ctx *cli.Context) (looprpc.SwapClientClient, func(), error) {
rpcServer := ctx.GlobalString("rpcserver")
conn, err := getClientConn(rpcServer)
if err != nil {
return nil, nil, err
}
cleanup := func() { conn.Close() }
loopClient := looprpc.NewSwapClientClient(conn)
return loopClient, cleanup, nil
}
func getMaxRoutingFee(amt btcutil.Amount) btcutil.Amount {
return swap.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 *looprpc.QuoteResponse) *limits {
maxSwapRoutingFee := getMaxRoutingFee(amt)
maxPrepayRoutingFee := getMaxRoutingFee(btcutil.Amount(
quote.PrepayAmt,
))
maxPrepayAmt := btcutil.Amount(quote.PrepayAmt)
return &limits{
maxSwapRoutingFee: &maxSwapRoutingFee,
maxPrepayRoutingFee: &maxPrepayRoutingFee,
// 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: &maxPrepayAmt,
}
}
func displayLimits(swapType swap.Type, amt btcutil.Amount, l *limits,
externalHtlc bool, warning string) error {
totalSuccessMax := l.maxMinerFee + l.maxSwapFee
if l.maxSwapRoutingFee != nil {
totalSuccessMax += *l.maxSwapRoutingFee
}
if l.maxPrepayRoutingFee != nil {
totalSuccessMax += *l.maxPrepayRoutingFee
}
if swapType == swap.TypeIn && externalHtlc {
fmt.Printf("On-chain fee for external loop in is not " +
"included.\nSufficient fees will need to be paid " +
"when constructing the transaction in the external " +
"wallet.\n\n")
}
fmt.Printf("Max swap fees for %d Loop %v: %d\n",
amt, swapType, totalSuccessMax,
)
if warning != "" {
fmt.Println(warning)
}
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()
if swapType != swap.TypeIn || !externalHtlc {
fmt.Printf("Max on-chain fee: %d\n",
l.maxMinerFee)
}
if l.maxSwapRoutingFee != nil {
fmt.Printf("Max off-chain swap routing fee: %d\n",
*l.maxSwapRoutingFee)
}
if l.maxPrepayRoutingFee != nil {
fmt.Printf("Max off-chain prepay routing fee: %d\n",
*l.maxPrepayRoutingFee)
}
fmt.Printf("Max swap fee: %d\n", l.maxSwapFee)
if l.maxPrepayAmt != nil {
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 logSwap(swap *looprpc.SwapStatus) {
fmt.Printf("%v %v %v %v - %v",
time.Unix(0, swap.LastUpdateTime).Format(time.RFC3339),
swap.Type, swap.State, btcutil.Amount(swap.Amt),
swap.HtlcAddress,
)
if swap.State != looprpc.SwapState_INITIATED &&
swap.State != looprpc.SwapState_HTLC_PUBLISHED &&
swap.State != looprpc.SwapState_PREIMAGE_REVEALED {
fmt.Printf(" (cost: server %v, onchain %v, offchain %v)",
swap.CostServer, swap.CostOnchain, swap.CostOffchain,
)
}
fmt.Println()
}
func getClientConn(address string) (*grpc.ClientConn, error) {
opts := []grpc.DialOption{
grpc.WithInsecure(),
grpc.WithDefaultCallOptions(maxMsgRecvSize),
}
conn, err := grpc.Dial(address, opts...)
if err != nil {
return nil, fmt.Errorf("unable to connect to RPC server: %v", err)
}
return conn, nil
}