mirror of
https://github.com/AthanorLabs/atomic-swap.git
synced 2026-01-09 14:18:03 -05:00
501 lines
10 KiB
Go
501 lines
10 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/noot/atomic-swap/common/types"
|
|
"github.com/noot/atomic-swap/rpcclient"
|
|
"github.com/noot/atomic-swap/rpcclient/wsclient"
|
|
|
|
logging "github.com/ipfs/go-log"
|
|
"github.com/urfave/cli"
|
|
)
|
|
|
|
const (
|
|
defaultSwapdAddress = "http://localhost:5001"
|
|
)
|
|
|
|
var log = logging.Logger("cmd")
|
|
|
|
var (
|
|
app = &cli.App{
|
|
Name: "swapcli",
|
|
Usage: "Client for swapd",
|
|
Commands: []cli.Command{
|
|
{
|
|
Name: "addresses",
|
|
Aliases: []string{"a"},
|
|
Usage: "list our daemon's libp2p listening addresses",
|
|
Action: runAddresses,
|
|
Flags: []cli.Flag{
|
|
daemonAddrFlag,
|
|
},
|
|
},
|
|
{
|
|
Name: "discover",
|
|
Aliases: []string{"d"},
|
|
Usage: "discover peers who provide a certain coin",
|
|
Action: runDiscover,
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "provides",
|
|
Usage: "coin to find providers for: one of [ETH, XMR]",
|
|
},
|
|
&cli.UintFlag{
|
|
Name: "search-time",
|
|
Usage: "duration of time to search for, in seconds",
|
|
},
|
|
daemonAddrFlag,
|
|
},
|
|
},
|
|
{
|
|
Name: "query",
|
|
Aliases: []string{"q"},
|
|
Usage: "query a peer for details on what they provide",
|
|
Action: runQuery,
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "multiaddr",
|
|
Usage: "peer's multiaddress, as provided by discover",
|
|
},
|
|
daemonAddrFlag,
|
|
},
|
|
},
|
|
{
|
|
Name: "make",
|
|
Aliases: []string{"m"},
|
|
Usage: "mke a swap offer; currently monero holders must be the makers",
|
|
Action: runMake,
|
|
Flags: []cli.Flag{
|
|
&cli.Float64Flag{
|
|
Name: "min-amount",
|
|
Usage: "minimum amount to be swapped, in XMR",
|
|
},
|
|
&cli.Float64Flag{
|
|
Name: "max-amount",
|
|
Usage: "maximum amount to be swapped, in XMR",
|
|
},
|
|
&cli.Float64Flag{
|
|
Name: "exchange-rate",
|
|
Usage: "desired exchange rate of XMR:ETH, eg. --exchange-rate=0.1 means 10XMR = 1ETH",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "subscribe",
|
|
Usage: "subscribe to push notifications about the swap's status",
|
|
},
|
|
daemonAddrFlag,
|
|
},
|
|
},
|
|
{
|
|
Name: "take",
|
|
Aliases: []string{"t"},
|
|
Usage: "initiate a swap by taking an offer; currently only eth holders can be the takers",
|
|
Action: runTake,
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "multiaddr",
|
|
Usage: "peer's multiaddress, as provided by discover",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "offer-id",
|
|
Usage: "ID of the offer being taken",
|
|
},
|
|
&cli.Float64Flag{
|
|
Name: "provides-amount",
|
|
Usage: "amount of coin to send in the swap",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "subscribe",
|
|
Usage: "subscribe to push notifications about the swap's status",
|
|
},
|
|
daemonAddrFlag,
|
|
},
|
|
},
|
|
{
|
|
Name: "get-past-swap-ids",
|
|
Usage: "get past swap IDs",
|
|
Action: runGetPastSwapIDs,
|
|
Flags: []cli.Flag{daemonAddrFlag},
|
|
},
|
|
{
|
|
Name: "get-ongoing-swap",
|
|
Usage: "get information about ongoing swap, if there is one",
|
|
Action: runGetOngoingSwap,
|
|
Flags: []cli.Flag{daemonAddrFlag},
|
|
},
|
|
{
|
|
Name: "get-past-swap",
|
|
Usage: "get information about a past swap with the given ID",
|
|
Action: runGetPastSwap,
|
|
Flags: []cli.Flag{
|
|
&cli.UintFlag{
|
|
Name: "id",
|
|
Usage: "ID of swap to retrieve info for",
|
|
},
|
|
daemonAddrFlag,
|
|
},
|
|
},
|
|
{
|
|
Name: "refund",
|
|
Usage: "if we are the ETH provider for an ongoing swap, refund it if possible.",
|
|
Action: runRefund,
|
|
Flags: []cli.Flag{daemonAddrFlag},
|
|
},
|
|
{
|
|
Name: "cancel",
|
|
Usage: "cancel the ongoing swap if possible.",
|
|
Action: runCancel,
|
|
Flags: []cli.Flag{daemonAddrFlag},
|
|
},
|
|
{
|
|
Name: "get-stage",
|
|
Usage: "get the stage of the current swap.",
|
|
Action: runGetStage,
|
|
Flags: []cli.Flag{daemonAddrFlag},
|
|
},
|
|
{
|
|
Name: "set-swap-timeout",
|
|
Usage: "set the duration between swap initiation and t0 and t0 and t1, in seconds",
|
|
Action: runSetSwapTimeout,
|
|
Flags: []cli.Flag{
|
|
&cli.UintFlag{
|
|
Name: "duration",
|
|
Usage: "duration of timeout, in seconds",
|
|
},
|
|
daemonAddrFlag,
|
|
},
|
|
},
|
|
},
|
|
Flags: []cli.Flag{daemonAddrFlag},
|
|
}
|
|
|
|
daemonAddrFlag = &cli.StringFlag{
|
|
Name: "daemon-addr",
|
|
Usage: "address of swap daemon; default http://localhost:5001",
|
|
}
|
|
)
|
|
|
|
func main() {
|
|
if err := app.Run(os.Args); err != nil {
|
|
log.Error(err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func runAddresses(ctx *cli.Context) error {
|
|
endpoint := ctx.String("daemon-addr")
|
|
if endpoint == "" {
|
|
endpoint = defaultSwapdAddress
|
|
}
|
|
|
|
c := rpcclient.NewClient(endpoint)
|
|
addrs, err := c.Addresses()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("Listening addresses: %v\n", addrs)
|
|
return nil
|
|
}
|
|
|
|
func runDiscover(ctx *cli.Context) error {
|
|
provides, err := types.NewProvidesCoin(ctx.String("provides"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if provides == "" {
|
|
provides = types.ProvidesXMR
|
|
}
|
|
|
|
endpoint := ctx.String("daemon-addr")
|
|
if endpoint == "" {
|
|
endpoint = defaultSwapdAddress
|
|
}
|
|
|
|
searchTime := ctx.Uint("search-time")
|
|
|
|
c := rpcclient.NewClient(endpoint)
|
|
peers, err := c.Discover(provides, uint64(searchTime))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for i, peer := range peers {
|
|
fmt.Printf("Peer %d: %v\n", i, peer)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func runQuery(ctx *cli.Context) error {
|
|
maddr := ctx.String("multiaddr")
|
|
if maddr == "" {
|
|
return errNoMultiaddr
|
|
}
|
|
|
|
endpoint := ctx.String("daemon-addr")
|
|
if endpoint == "" {
|
|
endpoint = defaultSwapdAddress
|
|
}
|
|
|
|
c := rpcclient.NewClient(endpoint)
|
|
res, err := c.Query(maddr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, o := range res.Offers {
|
|
fmt.Printf("%v\n", o)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func runMake(ctx *cli.Context) error {
|
|
min := ctx.Float64("min-amount")
|
|
if min == 0 {
|
|
return errNoMinAmount
|
|
}
|
|
|
|
max := ctx.Float64("max-amount")
|
|
if max == 0 {
|
|
return errNoMaxAmount
|
|
}
|
|
|
|
exchangeRate := ctx.Float64("exchange-rate")
|
|
if exchangeRate == 0 {
|
|
return errNoExchangeRate
|
|
}
|
|
|
|
endpoint := ctx.String("daemon-addr")
|
|
if endpoint == "" {
|
|
endpoint = defaultSwapdAddress
|
|
}
|
|
|
|
if ctx.Bool("subscribe") {
|
|
c, err := wsclient.NewWsClient(context.Background(), endpoint)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
id, takenCh, statusCh, err := c.MakeOfferAndSubscribe(min, max, types.ExchangeRate(exchangeRate))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("Made offer with ID=%s\n", id)
|
|
|
|
taken := <-takenCh
|
|
if taken == nil {
|
|
fmt.Printf("connection closed\n")
|
|
return nil
|
|
}
|
|
|
|
fmt.Printf("Offer taken! Swap ID=%d\n", taken.ID)
|
|
|
|
for stage := range statusCh {
|
|
fmt.Printf("> Stage updated: %s\n", stage)
|
|
if !stage.IsOngoing() {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
c := rpcclient.NewClient(endpoint)
|
|
id, err := c.MakeOffer(min, max, exchangeRate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("Published offer with ID %s\n", id)
|
|
return nil
|
|
}
|
|
|
|
func runTake(ctx *cli.Context) error {
|
|
maddr := ctx.String("multiaddr")
|
|
if maddr == "" {
|
|
return errNoMultiaddr
|
|
}
|
|
|
|
offerID := ctx.String("offer-id")
|
|
if offerID == "" {
|
|
return errNoOfferID
|
|
}
|
|
|
|
providesAmount := ctx.Float64("provides-amount")
|
|
if providesAmount == 0 {
|
|
return errNoProvidesAmount
|
|
}
|
|
|
|
endpoint := ctx.String("daemon-addr")
|
|
if endpoint == "" {
|
|
endpoint = defaultSwapdAddress
|
|
}
|
|
|
|
if ctx.Bool("subscribe") {
|
|
c, err := wsclient.NewWsClient(context.Background(), endpoint)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
id, statusCh, err := c.TakeOfferAndSubscribe(maddr, offerID, providesAmount)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("Initiated swap with ID=%d\n", id)
|
|
|
|
for stage := range statusCh {
|
|
fmt.Printf("> Stage updated: %s\n", stage)
|
|
if !stage.IsOngoing() {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
c := rpcclient.NewClient(endpoint)
|
|
id, err := c.TakeOffer(maddr, offerID, providesAmount)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("Initiated swap with ID=%d\n", id)
|
|
return nil
|
|
}
|
|
|
|
func runGetPastSwapIDs(ctx *cli.Context) error {
|
|
endpoint := ctx.String("daemon-addr")
|
|
if endpoint == "" {
|
|
endpoint = defaultSwapdAddress
|
|
}
|
|
|
|
c := rpcclient.NewClient(endpoint)
|
|
ids, err := c.GetPastSwapIDs()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("Past swap IDs: %v\n", ids)
|
|
return nil
|
|
}
|
|
|
|
func runGetOngoingSwap(ctx *cli.Context) error {
|
|
endpoint := ctx.String("daemon-addr")
|
|
if endpoint == "" {
|
|
endpoint = defaultSwapdAddress
|
|
}
|
|
|
|
c := rpcclient.NewClient(endpoint)
|
|
info, err := c.GetOngoingSwap()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("ID: %d\n Provided: %s\n ProvidedAmount: %v\n ReceivedAmount: %v\n ExchangeRate: %v\n Status: %s\n",
|
|
info.ID,
|
|
info.Provided,
|
|
info.ProvidedAmount,
|
|
info.ReceivedAmount,
|
|
info.ExchangeRate,
|
|
info.Status,
|
|
)
|
|
return nil
|
|
}
|
|
|
|
func runGetPastSwap(ctx *cli.Context) error {
|
|
id := ctx.Uint("id")
|
|
|
|
endpoint := ctx.String("daemon-addr")
|
|
if endpoint == "" {
|
|
endpoint = defaultSwapdAddress
|
|
}
|
|
|
|
c := rpcclient.NewClient(endpoint)
|
|
info, err := c.GetPastSwap(uint64(id))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("ID: %d\n Provided: %s\n ProvidedAmount: %v\n ReceivedAmount: %v\n ExchangeRate: %v\n Status: %s\n",
|
|
id,
|
|
info.Provided,
|
|
info.ProvidedAmount,
|
|
info.ReceivedAmount,
|
|
info.ExchangeRate,
|
|
info.Status,
|
|
)
|
|
return nil
|
|
}
|
|
|
|
func runRefund(ctx *cli.Context) error {
|
|
endpoint := ctx.String("daemon-addr")
|
|
if endpoint == "" {
|
|
endpoint = defaultSwapdAddress
|
|
}
|
|
|
|
c := rpcclient.NewClient(endpoint)
|
|
resp, err := c.Refund()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("Refunded successfully, transaction hash: %s\n", resp.TxHash)
|
|
return nil
|
|
}
|
|
|
|
func runCancel(ctx *cli.Context) error {
|
|
endpoint := ctx.String("daemon-addr")
|
|
if endpoint == "" {
|
|
endpoint = defaultSwapdAddress
|
|
}
|
|
|
|
c := rpcclient.NewClient(endpoint)
|
|
resp, err := c.Cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("Cancelled successfully, exit status: %s\n", resp)
|
|
return nil
|
|
}
|
|
|
|
func runGetStage(ctx *cli.Context) error {
|
|
endpoint := ctx.String("daemon-addr")
|
|
if endpoint == "" {
|
|
endpoint = defaultSwapdAddress
|
|
}
|
|
|
|
c := rpcclient.NewClient(endpoint)
|
|
resp, err := c.GetStage()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("Stage=%s: %s\n", resp.Stage, resp.Info)
|
|
return nil
|
|
}
|
|
|
|
func runSetSwapTimeout(ctx *cli.Context) error {
|
|
duration := ctx.Uint("duration")
|
|
|
|
endpoint := ctx.String("daemon-addr")
|
|
if endpoint == "" {
|
|
endpoint = defaultSwapdAddress
|
|
}
|
|
|
|
c := rpcclient.NewClient(endpoint)
|
|
err := c.SetSwapTimeout(uint64(duration))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("Set timeout duration to %ds", duration)
|
|
return nil
|
|
}
|