From cc8ab18ff8089994fbd731e578d5469c28bc743c Mon Sep 17 00:00:00 2001 From: Onion Limited Date: Sun, 25 Oct 2020 16:11:44 +0000 Subject: [PATCH] Initial commit --- .gitignore | 1 + Makefile | 10 + README.md | 31 ++++ app.go | 380 ++++++++++++++++++++++++++++++++++++++ clients/api/client.go | 114 ++++++++++++ clients/api/messages.go | 104 +++++++++++ clients/api/response.go | 28 +++ clients/rpc/client.go | 101 ++++++++++ commands.go | 63 +++++++ go.mod | 10 + go.sum | 122 ++++++++++++ main.go | 24 +++ utils/start_wallet_rpc.sh | 8 + 13 files changed, 996 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 app.go create mode 100644 clients/api/client.go create mode 100644 clients/api/messages.go create mode 100644 clients/api/response.go create mode 100644 clients/rpc/client.go create mode 100644 commands.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100755 utils/start_wallet_rpc.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..61f529d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +mmc diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..caa8f92 --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +TARGET=mmc +LDFLAGS=-w -s + +.PHONY: all +all: + go build -v -ldflags="$(LDFLAGS)" -o "$(TARGET)" + +.PHONY: clean +clean: + $(RM) $(TARGET) diff --git a/README.md b/README.md new file mode 100644 index 0000000..0fe302f --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# Monero Multisig Client + +A proof-of-concept client for Monero Multisig Broker. + +## Installation + +``` +$ go get https://git.wownero.com/onionltd/monero-multisig-client-poc +``` + +## Usage + +``` +NAME: + mmc - Monero Multisig client (proof-of-concept) + +USAGE: + mmc [global options] command [command options] [arguments...] + +VERSION: + 0.1 + +COMMANDS: + prepare Prepare a multisig wallet + transfer Issue a transfer from the multisig wallet + +GLOBAL OPTIONS: + --wallet-rpc value Monero Wallet RPC URI (default: "http://localhost:18082/json_rpc") + --help, -h show help (default: false) + --version, -v print the version (default: false) +``` diff --git a/app.go b/app.go new file mode 100644 index 0000000..fe2ff6f --- /dev/null +++ b/app.go @@ -0,0 +1,380 @@ +package main + +import ( + "context" + "encoding/base32" + "encoding/json" + "errors" + "fmt" + "git.wownero.com/onionltd/monero-multisig-client-poc/clients/api" + "git.wownero.com/onionltd/monero-multisig-client-poc/clients/rpc" + "github.com/urfave/cli/v2" + "log" + "strconv" + "time" +) + +const Version = "0.1" + +type secret struct { + Host string `json:"host"` + Topic string `json:"topic"` + Token string `json:"token"` + Nickname string `json:"nickname"` +} + +type Application struct { + app *cli.App + + secret secret + peersMultisigInfo []string + + rpcCli *rpc.Client + apiCli *api.Client +} + +func (a *Application) prepare() cli.BeforeFunc { + decodeMemberSecret := func(s string) error { + b, err := base32.StdEncoding.DecodeString(s) + if err != nil { + return err + } + + if err := json.Unmarshal(b, &a.secret); err != nil { + return err + } + + return nil + } + + setupWallet := func(language string) error { + name := a.secret.Topic + if err := a.rpcCli.OpenWallet(name); err != nil { + if err := a.rpcCli.CreateWallet(name, language); err != nil { + return err + } + } + return nil + } + + setupAPIClient := func() *api.Client { + c := api.NewClient("http://"+a.secret.Host, 15*time.Second) + c.SetToken(a.secret.Token) + return c + } + + setupRPCClient := func(uri string) *rpc.Client { + return rpc.NewClient(uri) + } + + return func(c *cli.Context) error { + if err := decodeMemberSecret(c.String("secret")); err != nil { + return fmt.Errorf("failed to decode the secret: %s", err) + } + + a.apiCli = setupAPIClient() + a.rpcCli = setupRPCClient(c.String("wallet-rpc")) + + if err := setupWallet(c.String("wallet-language")); err != nil { + return fmt.Errorf("failed to setup wallet: %s", err) + } + return nil + } +} + +func (a *Application) handleTransfer() cli.ActionFunc { + isMultisigWallet := func() error { + isMs, err := a.rpcCli.IsMultisig() + if err != nil { + return err + } + if !isMs { + return errors.New("wallet is not multisig wallet") + } + return nil + } + + return func(c *cli.Context) error { + if c.Args().Len() < 2 { + return errors.New("Not enough arguments") + } + + amount := c.Args().First() + address := c.Args().Slice()[1] + + amountXmr, err := strconv.ParseUint(amount, 10, 64) + if err != nil { + return fmt.Errorf("failed to parse the amount argument: %s", err) + } + + state := "init" + + loop: + for { + switch state { + case "init": + if err := isMultisigWallet(); err != nil { + return err + } + state = "push_prepare_multisig" + + case "done": + break loop + + default: + return errors.New("undefined state") + } + } + + message := api.NewTransferMessage("") + if err := a.rpcCli.Transfer(address, uint(amountXmr), &message); err != nil { + return err + } + + return nil + } +} + +func (a *Application) handlePrepareWallet() cli.ActionFunc { + isMultisigWallet := func() error { + isMs, err := a.rpcCli.IsMultisig() + if err != nil { + return err + } + if !isMs { + return errors.New("wallet is not multisig wallet") + } + return nil + } + + pushPrepareMultisig := func() error { + log.Println("pushPrepareMultisig") + + message := api.NewPrepareMultisig("") + if err := a.rpcCli.PrepareMultisig(&message); err != nil { + return err + } + + ctx := context.TODO() + if err := a.apiCli.PushMessage(ctx, a.secret.Topic, message); err != nil { + return err + } + + return nil + } + + waitPeersPrepareMultisig := func() error { + log.Println("waitPeersPrepareMultisig") + + ctx := context.TODO() + peersMultisigInfo := map[string]string{} + + for { + messages, err := a.apiCli.ListMessages(ctx, a.secret.Topic) + if err != nil { + return err + } + + for _, message := range messages { + if message.RPCName != api.PrepareMultisig { + continue + } + peersMultisigInfo[message.Sender] = message.MultisigInfo + } + + if len(peersMultisigInfo) == 3 { + break + } + + log.Printf("%+v", peersMultisigInfo) + time.Sleep(10 * time.Second) + } + + a.peersMultisigInfo = make([]string, 0, 2) + for sender, multisigInfo := range peersMultisigInfo { + if sender == a.secret.Nickname { + continue + } + a.peersMultisigInfo = append(a.peersMultisigInfo, multisigInfo) + } + + return nil + } + + pushMakeMultisig := func() error { + log.Println("pushMakeMultisig") + + message := api.NewMakeMultisig("") + if err := a.rpcCli.MakeMultisig(2, a.peersMultisigInfo, &message); err != nil { + return err + } + + ctx := context.TODO() + if err := a.apiCli.PushMessage(ctx, a.secret.Topic, message); err != nil { + return err + } + + return nil + } + + waitPeersMakeMultisig := func() error { + log.Println("waitPeersMakeMultisig") + + ctx := context.TODO() + peersMultisigInfo := map[string]string{} + + for { + messages, err := a.apiCli.ListMessages(ctx, a.secret.Topic) + if err != nil { + return err + } + + for _, message := range messages { + if message.RPCName != api.MakeMultisig { + continue + } + peersMultisigInfo[message.Sender] = message.MultisigInfo + } + + if len(peersMultisigInfo) == 3 { + break + } + + log.Printf("%+v", peersMultisigInfo) + time.Sleep(10 * time.Second) + } + + a.peersMultisigInfo = make([]string, 0, 2) + for sender, multisigInfo := range peersMultisigInfo { + if sender == a.secret.Nickname { + continue + } + a.peersMultisigInfo = append(a.peersMultisigInfo, multisigInfo) + } + + return nil + } + + pushMultisigAddress := func() error { + log.Println("pushMultisigAddress") + + message := api.NewFinalizeMultisig("") + if err := a.rpcCli.FinalizeMultisig(a.peersMultisigInfo, &message); err != nil { + return err + } + + ctx := context.TODO() + if err := a.apiCli.PushMessage(ctx, a.secret.Topic, message); err != nil { + return err + } + + return nil + } + + waitPeersMultisigAddress := func() error { + log.Println("waitPeersMultisigAddress") + + ctx := context.TODO() + peersMultisigAddress := map[string]string{} + + for { + messages, err := a.apiCli.ListMessages(ctx, a.secret.Topic) + if err != nil { + return err + } + + for _, message := range messages { + if message.RPCName != api.FinalizeMultisig { + continue + } + peersMultisigAddress[message.Sender] = message.Address + } + + if len(peersMultisigAddress) == 3 { + break + } + + log.Printf("%+v", peersMultisigAddress) + time.Sleep(10 * time.Second) + } + + previousAddress := "" + for _, address := range peersMultisigAddress { + if previousAddress == "" { + previousAddress = address + continue + } + if address != previousAddress { + return errors.New("multisig addresses do not match") + } + } + + return nil + } + + return func(c *cli.Context) error { + state := "init" + + loop: + for { + switch state { + case "init": + if err := isMultisigWallet(); err == nil { + return nil + } + state = "push_prepare_multisig" + + case "push_prepare_multisig": + if err := pushPrepareMultisig(); err != nil { + return err + } + state = "wait_peers_prepare_multisig" + + case "wait_peers_prepare_multisig": + if err := waitPeersPrepareMultisig(); err != nil { + return err + } + state = "push_make_multisig" + + case "push_make_multisig": + if err := pushMakeMultisig(); err != nil { + return err + } + state = "wait_peers_make_multisig" + + case "wait_peers_make_multisig": + if err := waitPeersMakeMultisig(); err != nil { + return err + } + state = "push_multisig_address" + + case "push_multisig_address": + if err := pushMultisigAddress(); err != nil { + return err + } + state = "wait_peers_multisig_address" + + case "wait_peers_multisig_address": + if err := waitPeersMultisigAddress(); err != nil { + return err + } + state = "done" + + case "done": + if err := isMultisigWallet(); err != nil { + return err + } + break loop + + default: + return errors.New("undefined state") + } + } + + return nil + } +} + +func (a *Application) Run(args []string) error { + return a.app.Run(args) +} diff --git a/clients/api/client.go b/clients/api/client.go new file mode 100644 index 0000000..d136a39 --- /dev/null +++ b/clients/api/client.go @@ -0,0 +1,114 @@ +package api + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "git.wownero.com/onionltd/monero-multisig-broker/messagelog" + "net/http" + "time" +) + +type Client struct { + host string + token string + offset int + httpClient *http.Client +} + +func (c *Client) SetToken(token string) { + c.token = token +} + +func (c *Client) SetOffset(offset int) { + c.offset = offset +} + +func (c *Client) PushMessage(ctx context.Context, topic string, message Message) error { + b, err := json.Marshal(convertToLogMessage(message)) + if err != nil { + return err + } + + url := formatApiUri(c.host, topic) + req, err := http.NewRequestWithContext(ctx, http.MethodPut, url, bytes.NewReader(b)) + if err != nil { + return err + } + req.Header.Set("Authorization", formatAuthToken(c.token)) + req.Header.Set("Accept", "application/json") + req.Header.Set("Content-Type", "application/json") + req.Header.Set("User-Agent", "monero-multisig-client/POC-0.1") + + res, err := c.httpClient.Do(req) + if err != nil { + return err + } + defer res.Body.Close() + + if res.StatusCode != http.StatusNoContent { + return fmt.Errorf("api: invalid response code: %d", res.StatusCode) + } + + return nil +} + +func (c *Client) ListMessages(ctx context.Context, topic string) ([]Message, error) { + url := formatApiUri(c.host, topic) + url = setOffset(url, c.offset) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, err + } + req.Header.Set("Authorization", formatAuthToken(c.token)) + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", "monero-multisig-client/POC-0.1") + + res, err := c.httpClient.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + return nil, fmt.Errorf("api: invalid response code: %d", res.StatusCode) + } + + messages := []messagelog.Message{} + if err := json.NewDecoder(res.Body).Decode(&messages); err != nil { + return nil, err + } + + multisigMessages := make([]Message, 0, len(messages)) + for i := range messages { + message, err := convertFromLogMessage(messages[i]) + if err != nil { + continue + } + multisigMessages = append(multisigMessages, message) + } + + return multisigMessages, nil +} + +func formatApiUri(host, orderID string) string { + return fmt.Sprintf("%s/api/v1/multisig/%s", host, orderID) +} + +func setOffset(url string, offset int) string { + return fmt.Sprintf("%s?offset=%d", url, offset) +} + +func formatAuthToken(token string) string { + return fmt.Sprintf("Bearer %s", token) +} + +func NewClient(host string, timeout time.Duration) *Client { + return &Client{ + host: host, + httpClient: &http.Client{ + Timeout: timeout, + }, + } +} diff --git a/clients/api/messages.go b/clients/api/messages.go new file mode 100644 index 0000000..3d7ee08 --- /dev/null +++ b/clients/api/messages.go @@ -0,0 +1,104 @@ +package api + +import ( + "errors" + "fmt" + "git.wownero.com/onionltd/monero-multisig-broker/messagelog" +) + +type Message struct { + RPCName string `json:"rpc_name"` + Sender string `json:"sender"` + MultisigInfo string `json:"multisig_info"` + MultisigTxSet string `json:"multisig_txset"` + Address string `json:"address"` + TxDataHex string `json:"tx_data_hex"` + TxHashList []string `json:"tx_hash_list"` +} + +const PrepareMultisig = "prepare_multisig" + +func NewPrepareMultisig(multisigInfo string) Message { + return Message{ + RPCName: PrepareMultisig, + MultisigInfo: multisigInfo, + } +} + +const MakeMultisig = "make_multisig" + +func NewMakeMultisig(multisigInfo string) Message { + return Message{ + RPCName: MakeMultisig, + MultisigInfo: multisigInfo, + } +} + +const FinalizeMultisig = "finalize_multisig" + +func NewFinalizeMultisig(address string) Message { + return Message{ + RPCName: FinalizeMultisig, + Address: address, + } +} + +const Transfer = "transfer" + +func NewTransferMessage(multisigTxSet string) Message { + return Message{ + RPCName: Transfer, + MultisigTxSet: multisigTxSet, + } +} + +func convertToLogMessage(m Message) messagelog.Message { + return messagelog.Message{ + ContentType: "application/json", + Content: m, + } +} + +func convertFromLogMessage(m messagelog.Message) (Message, error) { + content, ok := m.Content.(map[string]interface{}) + if !ok { + return Message{}, errors.New("content is not an object") + } + + messageType, ok := content["rpc_name"].(string) + if !ok { + return Message{}, errors.New("unexpected object format") + } + + var message Message + + switch messageType { + case PrepareMultisig: + msInfo, ok := content["multisig_info"].(string) + if !ok { + return Message{}, fmt.Errorf("missing `%s` for message type %s", "multisig_info", PrepareMultisig) + } + message = NewPrepareMultisig(msInfo) + + case MakeMultisig: + msInfo, ok := content["multisig_info"].(string) + if !ok { + return Message{}, fmt.Errorf("missing `%s` for message type %s", "multisig_info", MakeMultisig) + } + message = NewMakeMultisig(msInfo) + + case FinalizeMultisig: + address, ok := content["address"].(string) + if !ok { + return Message{}, fmt.Errorf("missing `%s` for message type %s", "address", FinalizeMultisig) + } + message = NewFinalizeMultisig(address) + + default: + return Message{}, errors.New("unknown message type") + } + + message.Sender = m.Sender + + return message, nil +} diff --git a/clients/api/response.go b/clients/api/response.go new file mode 100644 index 0000000..9d465ce --- /dev/null +++ b/clients/api/response.go @@ -0,0 +1,28 @@ +package api + +type ResponseMessage struct { + Status string `json:"status"` + MultisigAddress string `json:"multisig_address"` + MultisigInfo string `json:"multisig_info"` + MultisigInfoPeers []string `json:"multisig_info_peers"` +} + +type Response struct { + Message ResponseMessage `json:"message"` +} + +func (r Response) IsCreated() bool { + return r.Message.Status == "created" +} + +func (r Response) IsPrepared() bool { + return r.Message.Status == "prepared" +} + +func (r Response) IsMade() bool { + return r.Message.Status == "made" +} + +func (r Response) IsFinalized() bool { + return r.Message.Status == "finalized" +} diff --git a/clients/rpc/client.go b/clients/rpc/client.go new file mode 100644 index 0000000..d736501 --- /dev/null +++ b/clients/rpc/client.go @@ -0,0 +1,101 @@ +package rpc + +import ( + "github.com/ybbus/jsonrpc" +) + +type Client struct { + rpcClient jsonrpc.RPCClient +} + +func (c *Client) CreateWallet(name, language string) error { + type params struct { + Filename string `json:"filename"` + Password string `json:"password"` + Language string `json:"language"` + } + resp, err := c.rpcClient.Call("create_wallet", params{ + Filename: name, + Language: language, + }) + if err != nil { + return err + } + if err := resp.Error; err != nil { + return err + } + return nil +} + +func (c *Client) OpenWallet(name string) error { + type params struct { + Filename string `json:"filename"` + Password string `json:"password"` + } + resp, err := c.rpcClient.Call("open_wallet", params{ + Filename: name, + }) + if err != nil { + return err + } + if err := resp.Error; err != nil { + return err + } + return nil +} + +// TODO: close wallet! + +func (c *Client) IsMultisig() (bool, error) { + resp, err := c.rpcClient.Call("is_multisig") + if err != nil { + return false, err + } + if err := resp.Error; err != nil { + return false, err + } + isMultisig := resp.Result.(map[string]interface{})["multisig"].(bool) + isReady := resp.Result.(map[string]interface{})["ready"].(bool) + return isMultisig && isReady, nil +} + +func (c *Client) PrepareMultisig(out interface{}) error { + return c.rpcClient.CallFor(&out, "prepare_multisig") +} + +func (c *Client) MakeMultisig(threshold uint, multiSigInfo []string, out interface{}) error { + type params struct { + MultisigInfo []string `json:"multisig_info"` + Threshold uint `json:"threshold"` + Password string `json:"password"` + } + return c.rpcClient.CallFor(&out, "make_multisig", params{ + MultisigInfo: multiSigInfo, + Threshold: threshold, + }) +} + +func (c *Client) FinalizeMultisig(multiSigInfo []string, out interface{}) error { + type params struct { + MultisigInfo []string `json:"multisig_info"` + Password string `json:"password"` + } + return c.rpcClient.CallFor(&out, "finalize_multisig", params{ + MultisigInfo: multiSigInfo, + }) +} + +func (c *Client) Transfer(address string, amount uint, out interface{}) error { + type params struct { + Destinations []interface{} `json:"destinations"` + } + return c.rpcClient.CallFor(&out, "transfer", params{ + Destinations: []interface{}{amount, address}, + }) +} + +func NewClient(uri string) *Client { + return &Client{ + rpcClient: jsonrpc.NewClient(uri), + } +} diff --git a/commands.go b/commands.go new file mode 100644 index 0000000..5ea0abd --- /dev/null +++ b/commands.go @@ -0,0 +1,63 @@ +package main + +import ( + "github.com/urfave/cli/v2" +) + +func (a *Application) commands() { + a.app = &cli.App{ + Name: "mmc", + Version: Version, + Usage: "Monero Multisig client (proof-of-concept)", + HideHelpCommand: true, + Commands: cli.Commands{ + &cli.Command{ + Name: "prepare", + Usage: "Prepare a multisig wallet", + ArgsUsage: "", + Before: a.prepare(), + Action: a.handlePrepareWallet(), + HideHelpCommand: true, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "secret", + Required: true, + Usage: "set secret string", + }, + &cli.StringFlag{ + Name: "wallet-language", + Value: "English", + Usage: "set wallet language", + }, + }, + }, + &cli.Command{ + Name: "transfer", + Usage: "Issue a transfer from the multisig wallet", + ArgsUsage: "
", + Before: a.prepare(), + Action: a.handleTransfer(), + HideHelpCommand: true, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "secret", + Required: true, + Usage: "set secret string", + }, + &cli.StringFlag{ + Name: "wallet-language", + Value: "English", + Usage: "set wallet language", + }, + }, + }, + }, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "wallet-rpc", + Value: "http://localhost:18082/json_rpc", + Usage: "Monero Wallet RPC URI", + }, + }, + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4f60dca --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module git.wownero.com/onionltd/monero-multisig-client-poc + +go 1.14 + +require ( + git.wownero.com/onionltd/monero-multisig-broker v0.0.0-20201022175225-8bafb83c3a20 + github.com/onsi/gomega v1.10.3 // indirect + github.com/urfave/cli/v2 v2.2.0 + github.com/ybbus/jsonrpc v2.1.2+incompatible +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..4d64d34 --- /dev/null +++ b/go.sum @@ -0,0 +1,122 @@ +git.wownero.com/onionltd/monero-multisig-broker v0.0.0-20201022175225-8bafb83c3a20 h1:d9/sG87voh/CvH/X2BcCRAJ9BlJzztn0pc4vcnhRE8o= +git.wownero.com/onionltd/monero-multisig-broker v0.0.0-20201022175225-8bafb83c3a20/go.mod h1:H1w6rOPjKZRt76kJp9pupywhnbAbCkmz6krsUAeTDTo= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/labstack/echo/v4 v4.1.17/go.mod h1:Tn2yRQL/UclUalpb5rPdXDevbkJ+lp/2svdyFBg6CHQ= +github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= +github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/ybbus/jsonrpc v2.1.2+incompatible/go.mod h1:XJrh1eMSzdIYFbM08flv0wp5G35eRniyeGut1z+LSiE= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0 h1:wBouT66WTYFXdxfVdz9sVWARVd/2vfGcmI45D2gj45M= +golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 h1:9UQO31fZ+0aKQOFldThf7BKPMJTiBfWycGh/u3UoO88= +golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/validator.v2 v2.0.0-20200605151824-2b28d334fa05 h1:l9eKDCWy9n7C5NAiQAMvDePh0vyLAweR6LcSUVXFUGg= +gopkg.in/validator.v2 v2.0.0-20200605151824-2b28d334fa05/go.mod h1:o4V0GXN9/CAmCsvJ0oXYZvrZOe7syiDZSN1GWGZTGzc= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/main.go b/main.go new file mode 100644 index 0000000..cb4d8e6 --- /dev/null +++ b/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + "os" +) + +func run() error { + app := setupApplication() + return app.Run(os.Args) +} + +func setupApplication() *Application { + a := &Application{} + a.commands() + return a +} + +func main() { + if err := run(); err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + os.Exit(1) + } +} diff --git a/utils/start_wallet_rpc.sh b/utils/start_wallet_rpc.sh new file mode 100755 index 0000000..3cb5bcb --- /dev/null +++ b/utils/start_wallet_rpc.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +port="18082" +if [ $# -ge 1 ]; then + port="$1" +fi + +monero-wallet-rpc --stagenet --daemon-address 18.133.55.120:38081 --wallet-dir /tmp/mmc-tests.$port --disable-rpc-login --rpc-bind-port "$port"