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