Initial commit

master
Onion Ltd 4 years ago
commit cc8ab18ff8
Signed by: onionltd
GPG Key ID: E4B6CAC49B242A44

1
.gitignore vendored

@ -0,0 +1 @@
mmc

@ -0,0 +1,10 @@
TARGET=mmc
LDFLAGS=-w -s
.PHONY: all
all:
go build -v -ldflags="$(LDFLAGS)" -o "$(TARGET)"
.PHONY: clean
clean:
$(RM) $(TARGET)

@ -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)
```

380
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)
}

@ -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,
},
}
}

@ -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
}

@ -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"
}

@ -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),
}
}

@ -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: "<secret>",
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: "<amount> <address>",
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",
},
},
}
}

@ -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
)

122
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=

@ -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)
}
}

@ -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"
Loading…
Cancel
Save