cmd/aergocli/cmd/accounts.go
package cmd
import (
"context"
"errors"
"fmt"
"io"
"log"
"os"
"path/filepath"
"syscall"
"github.com/aergoio/aergo/v2/account/key"
"github.com/aergoio/aergo/v2/types"
"github.com/spf13/cobra"
"golang.org/x/crypto/ssh/terminal"
)
func init() {
accountCmd := &cobra.Command{
Use: "account [flags] subcommand",
Short: "Account command",
PersistentPreRun: preConnectAergo,
PersistentPostRun: disconnectAergo,
}
newCmd.Flags().StringVar(&pw, "password", "", "password (optional, will be asked on the terminal if not given)")
unlockCmd.Flags().StringVar(&address, "address", "", "address of account")
unlockCmd.MarkFlagRequired("address")
unlockCmd.Flags().StringVar(&pw, "password", "", "password (optional, will be asked on the terminal if not given)")
lockCmd.Flags().StringVar(&address, "address", "", "address of account")
lockCmd.MarkFlagRequired("address")
lockCmd.Flags().StringVar(&pw, "password", "", "password (optional, will be asked on the terminal if not given)")
importCmd.Flags().StringVar(&importFormat, "if", "", "Base58 import format string")
importCmd.Flags().StringVar(&pw, "password", "", "password used when exporting")
importCmd.Flags().StringVar(&to, "newpassword", "", "new password for storing account")
importCmd.Flags().StringVar(&importFilePath, "path", "", "path to import keystore file")
exportCmd.Flags().StringVar(&address, "address", "", "Address of account")
exportCmd.MarkFlagRequired("address")
exportCmd.Flags().BoolVar(&exportAsWif, "wif", false, "export as encrypted string instead of keystore format")
exportCmd.Flags().StringVar(&pw, "password", "", "password (optional, will be asked on the terminal if not given)")
voteCmd.Flags().StringVar(&address, "address", "", "account address of voter")
voteCmd.MarkFlagRequired("address")
voteCmd.Flags().StringVar(&to, "to", "", "json string array which has candidates, or input file path")
voteCmd.MarkFlagRequired("to")
voteCmd.Flags().StringVar(&voteId, "id", types.OpvoteBP.Cmd(), "id to vote (e.g. bpcount, stakingmin, gasprice, nameprice)")
voteCmd.Flags().StringVar(&pw, "password", "", "password (optional, will be asked on the terminal if not given)")
stakeCmd.Flags().StringVar(&address, "address", "", "account address")
stakeCmd.MarkFlagRequired("address")
stakeCmd.Flags().StringVar(&amount, "amount", "0", "amount to stake")
stakeCmd.MarkFlagRequired("amount")
stakeCmd.Flags().StringVar(&pw, "password", "", "password (optional, will be asked on the terminal if not given)")
unstakeCmd.Flags().StringVar(&address, "address", "", "account address")
unstakeCmd.MarkFlagRequired("address")
unstakeCmd.Flags().StringVar(&amount, "amount", "0", "amount to unstake")
unstakeCmd.MarkFlagRequired("amount")
unstakeCmd.Flags().StringVar(&pw, "password", "", "password (optional, will be asked on the terminal if not given)")
accountCmd.AddCommand(newCmd, listCmd, unlockCmd, lockCmd, importCmd, exportCmd, voteCmd, stakeCmd, unstakeCmd)
rootCmd.AddCommand(accountCmd)
}
var newCmd = &cobra.Command{
Use: "new [flags]",
Short: "Create new account in the node or cli",
Run: func(cmd *cobra.Command, args []string) {
var param types.Personal
var err error
if pw != "" {
param.Passphrase = pw
} else {
param.Passphrase, err = getPasswd(cmd, true)
if err != nil {
cmd.PrintErrf("Failed get password: %s\n", err.Error())
return
}
}
var msg *types.Account
var addr []byte
if rootConfig.KeyStorePath == "" {
msg, err = client.CreateAccount(context.Background(), ¶m)
if msg != nil {
addr = msg.GetAddress()
}
} else {
dataEnvPath := os.ExpandEnv(rootConfig.KeyStorePath)
ks := key.NewStore(dataEnvPath, 0)
defer ks.CloseStore()
addr, err = ks.CreateKey(param.Passphrase)
if err != nil {
cmd.PrintErrf("Failed: %s\n", err.Error())
return
}
}
if err != nil {
cmd.PrintErrf("Failed: %s\n", err.Error())
return
}
cmd.Println(types.EncodeAddress(addr))
},
}
var listCmd = &cobra.Command{
Use: "list [flags]",
Short: "Get account list in the node or cli",
Run: func(cmd *cobra.Command, args []string) {
var err error
var msg *types.AccountList
var addrs [][]byte
if rootConfig.KeyStorePath == "" {
msg, err = client.GetAccounts(context.Background(), &types.Empty{})
} else {
dataEnvPath := os.ExpandEnv(rootConfig.KeyStorePath)
ks := key.NewStore(dataEnvPath, 0)
defer ks.CloseStore()
addrs, err = ks.GetAddresses()
}
if err != nil {
cmd.PrintErrf("Failed: %s\n", err.Error())
return
}
out := fmt.Sprintf("%s", "[")
if msg != nil {
addresslist := msg.GetAccounts()
for _, a := range addresslist {
out = fmt.Sprintf("%s\"%s\", ", out, types.EncodeAddress(a.Address))
}
if addresslist != nil {
out = out[:len(out)-2]
}
} else if addrs != nil && len(addrs) > 0 {
for _, a := range addrs {
out = fmt.Sprintf("%s\"%s\", ", out, types.EncodeAddress(a))
}
out = out[:len(out)-2]
}
out = fmt.Sprintf("%s%s", out, "]")
cmd.Println(out)
},
}
var lockCmd = &cobra.Command{
Use: "lock [flags]",
Short: "Lock account in the node",
Run: func(cmd *cobra.Command, args []string) {
if rootConfig.KeyStorePath != "" {
cmd.PrintErrf("There is no lock on the local keystore.\n")
return
}
param, err := parsePersonalParam(cmd)
if err != nil {
cmd.PrintErrf("Failed: %s\n", err.Error())
return
}
msg, err := client.LockAccount(context.Background(), param)
if err != nil {
cmd.PrintErrf("Failed: %s\n", err.Error())
return
}
cmd.Println(types.EncodeAddress(msg.GetAddress()))
},
}
var unlockCmd = &cobra.Command{
Use: "unlock [flags]",
Short: "Unlock account in the node",
Run: func(cmd *cobra.Command, args []string) {
if rootConfig.KeyStorePath != "" {
cmd.PrintErrf("Local keystore do not need to be unlocked.\n")
return
}
param, err := parsePersonalParam(cmd)
msg, err := client.UnlockAccount(context.Background(), param)
if err != nil {
cmd.PrintErrf("Failed: %s\n", err.Error())
return
}
cmd.Println(types.EncodeAddress(msg.GetAddress()))
},
}
// import account using WIF (legacy)
func importWif(cmd *cobra.Command) ([]byte, error) {
var err error
var address []byte
importBuf, err := types.DecodePrivKey(importFormat)
if err != nil {
return nil, fmt.Errorf("failed to decode input: %s", err.Error())
}
wif := &types.ImportFormat{Wif: &types.SingleBytes{Value: importBuf}}
if pw != "" {
wif.Oldpass = pw
} else {
wif.Oldpass, err = getPasswd(cmd, false)
if err != nil {
return nil, err
}
}
if to != "" {
wif.Newpass = to
} else {
wif.Newpass = wif.Oldpass
}
if rootConfig.KeyStorePath == "" {
msg, errRemote := client.ImportAccount(context.Background(), wif)
address = msg.GetAddress()
if errRemote != nil {
return address, fmt.Errorf("node request returned error: %s", errRemote.Error())
}
} else {
dataEnvPath := os.ExpandEnv(rootConfig.KeyStorePath)
ks := key.NewStore(dataEnvPath, 0)
defer ks.CloseStore()
address, err = ks.ImportKey(importBuf, wif.Oldpass, wif.Newpass)
if err != nil {
return address, err
}
}
return address, nil
}
// import account using keystore
func importKeystore(cmd *cobra.Command) ([]byte, error) {
var err error
var address []byte
absPath, err := filepath.Abs(importFilePath)
if err != nil {
return nil, err
}
keystore, err := os.ReadFile(absPath)
if err != nil {
return nil, err
}
wif := &types.ImportFormat{Keystore: &types.SingleBytes{Value: keystore}}
if pw != "" {
wif.Oldpass = pw
} else {
wif.Oldpass, err = getPasswd(cmd, false)
if err != nil {
return nil, err
}
}
if to != "" {
wif.Newpass = to
} else {
wif.Newpass = wif.Oldpass
}
if rootConfig.KeyStorePath == "" {
msg, errRemote := client.ImportAccount(context.Background(), wif)
if errRemote != nil {
return nil, errRemote
}
address = msg.GetAddress()
} else {
dataEnvPath := os.ExpandEnv(rootConfig.KeyStorePath)
ks := key.NewStore(dataEnvPath, 0)
defer ks.CloseStore()
privateKey, err := key.LoadKeystore(keystore, wif.Oldpass)
if err != nil {
return nil, err
}
address, err = ks.AddKey(privateKey, wif.Newpass)
if err != nil {
return nil, err
}
}
return address, nil
}
var importCmd = &cobra.Command{
Use: "import [flags]",
Short: "Import account",
Run: func(cmd *cobra.Command, args []string) {
var address []byte
var err error
if importFormat != "" {
address, err = importWif(cmd)
} else if importFilePath != "" {
address, err = importKeystore(cmd)
} else {
cmd.Help()
}
if err != nil {
cmd.PrintErrln(err)
}
if address != nil {
cmd.Println(types.EncodeAddress(address))
}
},
}
// export account as WIF (legacy)
func exportWif(cmd *cobra.Command, param *types.Personal) ([]byte, error) {
if rootConfig.KeyStorePath == "" {
msg, err := client.ExportAccount(context.Background(), param)
if err != nil {
return nil, fmt.Errorf("node request returned error: %s", err.Error())
}
return msg.Value, nil
} else {
dataEnvPath := os.ExpandEnv(rootConfig.KeyStorePath)
ks := key.NewStore(dataEnvPath, 0)
defer ks.CloseStore()
wif, err := ks.ExportKey(param.Account.Address, param.Passphrase)
if err != nil {
return nil, err
}
return wif, nil
}
}
// export account as keystore
func exportKeystore(cmd *cobra.Command, param *types.Personal) ([]byte, error) {
if rootConfig.KeyStorePath == "" {
msg, err := client.ExportAccountKeystore(context.Background(), param)
if err != nil {
return nil, fmt.Errorf("node request returned error: %s", err.Error())
}
return msg.Value, nil
} else {
dataEnvPath := os.ExpandEnv(rootConfig.KeyStorePath)
ks := key.NewStore(dataEnvPath, 0)
defer ks.CloseStore()
privateKey, err := ks.GetKey(param.Account.Address, param.Passphrase)
if err != nil {
return nil, err
}
wif, err := key.GetKeystore(privateKey, param.Passphrase)
if err != nil {
return nil, err
}
return wif, nil
}
}
var exportCmd = &cobra.Command{
Use: "export [flags]",
Short: "Export account",
Run: func(cmd *cobra.Command, args []string) {
var result []byte
var err error
param, err := parsePersonalParam(cmd)
if err != nil {
cmd.PrintErr(err.Error())
return
}
if exportAsWif {
result, err = exportWif(cmd, param)
if result != nil {
cmd.Println(types.EncodePrivKey(result))
}
} else {
result, err = exportKeystore(cmd, param)
if result != nil {
cmd.Println(string(result))
}
}
if err != nil {
cmd.PrintErrln(err)
}
},
}
func parsePersonalParam(cmd *cobra.Command) (*types.Personal, error) {
var err error
param := &types.Personal{Account: &types.Account{}}
if address != "" {
param.Account.Address, err = types.DecodeAddress(address)
if err != nil {
return nil, err
}
if pw != "" {
param.Passphrase = pw
} else {
param.Passphrase, err = getPasswd(cmd, false)
if err != nil {
return nil, err
}
}
}
return param, nil
}
// Try to open /dev/tty for read/write, with stdin/stdout as fallback
// This is so we can use aergocli in a pipe and still read passwords from tty
func getTerminalReaderWriter() (struct {
io.Reader
io.Writer
}, int) {
path := "/dev/tty"
fallback := struct {
io.Reader
io.Writer
}{os.Stdin, os.Stdout}
in, err := os.Open(path)
if err != nil {
return fallback, 0
}
out, err := os.OpenFile(path, syscall.O_WRONLY, 0)
if err != nil {
return fallback, 0
}
return struct {
io.Reader
io.Writer
}{in, out}, int(out.Fd())
}
func getPasswd(cmd *cobra.Command, isNew bool) (string, error) {
screen, fd := getTerminalReaderWriter()
oldState, err := terminal.MakeRaw(fd)
if err != nil {
return "", err
}
defer terminal.Restore(0, oldState)
term := terminal.NewTerminal(screen, "")
password, err := term.ReadPassword("Enter password: ")
if err != nil {
return "", err
}
if len(password) == 0 {
return "", errors.New("empty password")
}
if isNew {
repeat, err := term.ReadPassword("Repeat password: ")
if err != nil {
return "", err
}
if password != repeat {
return "", errors.New("Passwords don't match")
}
}
return password, err
}
func preConnectAergo(cmd *cobra.Command, args []string) {
if rootConfig.KeyStorePath == "" {
connectAergo(cmd, args)
} else {
client = nil
}
if len(sock) > 0 {
if admClient = newAergoAdminClient(sock); admClient == nil {
log.Fatal("can't create admin connection")
}
}
}