commit
cc8ab18ff8
@ -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)
|
||||
```
|
@ -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
|
||||
)
|
@ -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…
Reference in new issue