diff --git a/cmd/root.go b/cmd/root.go index 7f2b6c7e..c324b2a0 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -13,6 +13,7 @@ import ( "github.com/oasisprotocol/cli/cmd/account" "github.com/oasisprotocol/cli/cmd/network" "github.com/oasisprotocol/cli/cmd/paratime" + "github.com/oasisprotocol/cli/cmd/wallet" "github.com/oasisprotocol/cli/config" "github.com/oasisprotocol/cli/version" _ "github.com/oasisprotocol/cli/wallet/file" // Register file wallet backend. @@ -98,7 +99,7 @@ func init() { rootCmd.AddCommand(network.Cmd) rootCmd.AddCommand(paratime.Cmd) - rootCmd.AddCommand(walletCmd) + rootCmd.AddCommand(wallet.Cmd) rootCmd.AddCommand(account.Cmd) rootCmd.AddCommand(addressBookCmd) rootCmd.AddCommand(contractCmd) diff --git a/cmd/wallet.go b/cmd/wallet.go deleted file mode 100644 index 5641c6f4..00000000 --- a/cmd/wallet.go +++ /dev/null @@ -1,399 +0,0 @@ -package cmd - -import ( - "fmt" - "io" - "sort" - "strings" - - "github.com/AlecAivazis/survey/v2" - "github.com/spf13/cobra" - flag "github.com/spf13/pflag" - - "github.com/oasisprotocol/oasis-core/go/common/crypto/signature" - "github.com/oasisprotocol/oasis-core/go/common/crypto/signature/signers/remote" - "github.com/oasisprotocol/oasis-core/go/common/grpc" - "github.com/oasisprotocol/oasis-core/go/common/identity" - "github.com/oasisprotocol/oasis-core/go/common/logging" - cmdBackground "github.com/oasisprotocol/oasis-core/go/oasis-node/cmd/common/background" - - "github.com/oasisprotocol/cli/cmd/common" - "github.com/oasisprotocol/cli/config" - "github.com/oasisprotocol/cli/table" - "github.com/oasisprotocol/cli/wallet" - walletFile "github.com/oasisprotocol/cli/wallet/file" -) - -var ( - accKind string - - walletCmd = &cobra.Command{ - Use: "wallet", - Short: "Manage accounts in the local wallet", - Aliases: []string{"w"}, - } - - walletListCmd = &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List configured accounts", - Args: cobra.NoArgs, - Run: func(cmd *cobra.Command, args []string) { - cfg := config.Global() - table := table.New() - table.SetHeader([]string{"Account", "Kind", "Address"}) - - var output [][]string - for name, acc := range cfg.Wallet.All { - if cfg.Wallet.Default == name { - name += common.DefaultMarker - } - output = append(output, []string{ - name, - acc.PrettyKind(), - acc.Address, - }) - } - - // Sort output by name. - sort.Slice(output, func(i, j int) bool { - return output[i][0] < output[j][0] - }) - - table.AppendBulk(output) - table.Render() - }, - } - - walletCreateCmd = &cobra.Command{ - Use: "create ", - Short: "Create a new account", - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - cfg := config.Global() - name := args[0] - - af, err := wallet.Load(accKind) - cobra.CheckErr(err) - - // Ask for passphrase to encrypt the wallet with. - var passphrase string - if af.RequiresPassphrase() { - passphrase = common.AskNewPassphrase() - } - - accCfg := &config.Account{ - Kind: accKind, - } - err = accCfg.SetConfigFromFlags() - cobra.CheckErr(err) - - if _, exists := cfg.AddressBook.All[name]; exists { - cobra.CheckErr(fmt.Errorf("address named '%s' already exists in address book", name)) - } - err = cfg.Wallet.Create(name, passphrase, accCfg) - cobra.CheckErr(err) - - err = cfg.Save() - cobra.CheckErr(err) - }, - } - - walletShowCmd = &cobra.Command{ - Use: "show ", - Short: "Show public account information", - Aliases: []string{"s"}, - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - name := args[0] - - acc := common.LoadAccount(config.Global(), name) - showPublicWalletInfo(name, acc) - }, - } - - walletRmCmd = &cobra.Command{ - Use: "remove ", - Aliases: []string{"rm"}, - Short: "Remove an existing account", - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - cfg := config.Global() - name := args[0] - - // Early check for whether the wallet exists so that we don't ask for confirmation first. - if _, exists := cfg.Wallet.All[name]; !exists { - cobra.CheckErr(fmt.Errorf("account '%s' does not exist", name)) - } - - fmt.Printf("WARNING: Removing the account will ERASE secret key material!\n") - fmt.Printf("WARNING: THIS ACTION IS IRREVERSIBLE!\n") - - var result string - confirmText := fmt.Sprintf("I really want to remove account %s", name) - prompt := &survey.Input{ - Message: fmt.Sprintf("Enter '%s' (without quotes) to confirm removal:", confirmText), - } - err := survey.AskOne(prompt, &result) - cobra.CheckErr(err) - - if result != confirmText { - cobra.CheckErr("Aborted.") - } - - err = cfg.Wallet.Remove(name) - cobra.CheckErr(err) - - err = cfg.Save() - cobra.CheckErr(err) - }, - } - - walletRenameCmd = &cobra.Command{ - Use: "rename ", - Short: "Rename an existing account", - Aliases: []string{"mv"}, - Args: cobra.ExactArgs(2), - Run: func(cmd *cobra.Command, args []string) { - cfg := config.Global() - oldName, newName := args[0], args[1] - - if _, exists := cfg.AddressBook.All[newName]; exists { - cobra.CheckErr(fmt.Errorf("address named '%s' already exists in the address book", newName)) - } - err := cfg.Wallet.Rename(oldName, newName) - cobra.CheckErr(err) - - err = cfg.Save() - cobra.CheckErr(err) - }, - } - - walletSetDefaultCmd = &cobra.Command{ - Use: "set-default ", - Short: "Sets the given account as the default account", - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - cfg := config.Global() - name := args[0] - - err := cfg.Wallet.SetDefault(name) - cobra.CheckErr(err) - - err = cfg.Save() - cobra.CheckErr(err) - }, - } - - walletImportCmd = &cobra.Command{ - Use: "import ", - Short: "Import an existing account", - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - cfg := config.Global() - name := args[0] - - if _, exists := cfg.Wallet.All[name]; exists { - cobra.CheckErr(fmt.Errorf("account '%s' already exists in the wallet", name)) - } - if _, exists := cfg.AddressBook.All[name]; exists { - cobra.CheckErr(fmt.Errorf("address named '%s' already exists in the address book", name)) - } - - // NOTE: We only support importing into the file-based wallet for now. - af, err := wallet.Load(walletFile.Kind) - cobra.CheckErr(err) - - // Ask for import kind. - var supportedKinds []string - for _, kind := range af.SupportedImportKinds() { - supportedKinds = append(supportedKinds, string(kind)) - } - - var kindRaw string - err = survey.AskOne(&survey.Select{ - Message: "Kind:", - Options: supportedKinds, - }, &kindRaw) - cobra.CheckErr(err) - - var kind wallet.ImportKind - err = kind.UnmarshalText([]byte(kindRaw)) - cobra.CheckErr(err) - - // Ask for wallet configuration. - afCfg, err := af.GetConfigFromSurvey(&kind) - cobra.CheckErr(err) - - // Ask for import data. - var answers struct { - Data string - } - questions := []*survey.Question{ - { - Name: "data", - Prompt: af.DataPrompt(kind, afCfg), - Validate: af.DataValidator(kind, afCfg), - }, - } - err = survey.Ask(questions, &answers) - cobra.CheckErr(err) - - // Ask for passphrase. - passphrase := common.AskNewPassphrase() - - accCfg := &config.Account{ - Kind: af.Kind(), - Config: afCfg, - } - src := &wallet.ImportSource{ - Kind: kind, - Data: answers.Data, - } - - err = cfg.Wallet.Import(name, passphrase, accCfg, src) - cobra.CheckErr(err) - - err = cfg.Save() - cobra.CheckErr(err) - }, - } - - walletExportCmd = &cobra.Command{ - Use: "export ", - Short: "Export secret account information", - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - name := args[0] - - fmt.Printf("WARNING: Exporting the account will expose secret key material!\n") - acc := common.LoadAccount(config.Global(), name) - - showPublicWalletInfo(name, acc) - - fmt.Printf("Export:\n") - fmt.Println(acc.UnsafeExport()) - }, - } - - walletRemoteSignerCmd = &cobra.Command{ - Use: "remote-signer ", - Short: "Act as a oasis-node remote entity signer over AF_LOCAL", - Args: cobra.ExactArgs(2), - Run: func(cmd *cobra.Command, args []string) { - name, socketPath := args[0], args[1] - - acc := common.LoadAccount(config.Global(), name) - - sf := &accountEntitySignerFactory{ - signer: acc.ConsensusSigner(), - } - if sf.signer == nil { - cobra.CheckErr("account not compatible with consensus layer usage") - } - - // The domain separation is entirely handled on the client side. - signature.UnsafeAllowUnregisteredContexts() - - // Suppress oasis-core logging. - err := logging.Initialize( - nil, - logging.FmtLogfmt, - logging.LevelInfo, - nil, - ) - cobra.CheckErr(err) - - // Setup the gRPC service. - srvCfg := &grpc.ServerConfig{ - Name: "remote-signer", - Path: socketPath, // XXX: Maybe fix this up to be nice. - Identity: &identity.Identity{}, - } - srv, err := grpc.NewServer(srvCfg) - cobra.CheckErr(err) - remote.RegisterService(srv.Server(), sf) - - // Start the service and wait for graceful termination. - err = srv.Start() - cobra.CheckErr(err) - - fmt.Printf("Address: %s\n", acc.Address()) - fmt.Printf("Node Args:\n --signer.backend=remote \\\n --signer.remote.address=unix:%s\n", socketPath) - fmt.Printf("\n*** REMOTE SIGNER READY ***\n") - - sm := cmdBackground.NewServiceManager(logging.GetLogger("remote-signer")) - sm.Register(srv) - defer sm.Cleanup() - sm.Wait() - }, - } -) - -type accountEntitySignerFactory struct { - signer signature.Signer -} - -func (sf *accountEntitySignerFactory) EnsureRole( - role signature.SignerRole, -) error { - if role != signature.SignerEntity { - return signature.ErrInvalidRole - } - return nil -} - -func (sf *accountEntitySignerFactory) Generate( - _ signature.SignerRole, - _ io.Reader, -) (signature.Signer, error) { - // The remote signer should never require this. - return nil, fmt.Errorf("refusing to generate new signing keys") -} - -func (sf *accountEntitySignerFactory) Load( - role signature.SignerRole, -) (signature.Signer, error) { - if err := sf.EnsureRole(role); err != nil { - return nil, err - } - return sf.signer, nil -} - -func showPublicWalletInfo(name string, wallet wallet.Account) { - fmt.Printf("Name: %s\n", name) - if signer := wallet.Signer(); signer != nil { - fmt.Printf("Public Key: %s\n", signer.Public()) - } - if ethAddr := wallet.EthAddress(); ethAddr != nil { - fmt.Printf("Ethereum address: %s\n", ethAddr.Hex()) - } - fmt.Printf("Native address: %s\n", wallet.Address()) -} - -func init() { - walletCmd.AddCommand(walletListCmd) - - walletFlags := flag.NewFlagSet("", flag.ContinueOnError) - kinds := make([]string, 0, len(wallet.AvailableKinds())) - for _, w := range wallet.AvailableKinds() { - kinds = append(kinds, w.Kind()) - } - walletFlags.StringVar(&accKind, "kind", "file", fmt.Sprintf("Account kind [%s]", strings.Join(kinds, ", "))) - - // TODO: Group flags in usage by tweaking the usage template/function. - for _, af := range wallet.AvailableKinds() { - walletFlags.AddFlagSet(af.Flags()) - } - - walletCreateCmd.Flags().AddFlagSet(walletFlags) - - walletCmd.AddCommand(walletCreateCmd) - walletCmd.AddCommand(walletShowCmd) - walletCmd.AddCommand(walletRmCmd) - walletCmd.AddCommand(walletRenameCmd) - walletCmd.AddCommand(walletSetDefaultCmd) - walletCmd.AddCommand(walletImportCmd) - walletCmd.AddCommand(walletExportCmd) - walletCmd.AddCommand(walletRemoteSignerCmd) -} diff --git a/cmd/wallet/create.go b/cmd/wallet/create.go new file mode 100644 index 00000000..db709406 --- /dev/null +++ b/cmd/wallet/create.go @@ -0,0 +1,65 @@ +package wallet + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + flag "github.com/spf13/pflag" + + "github.com/oasisprotocol/cli/cmd/common" + "github.com/oasisprotocol/cli/config" + "github.com/oasisprotocol/cli/wallet" +) + +var accKind string + +var createCmd = &cobra.Command{ + Use: "create ", + Short: "Create a new account", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + cfg := config.Global() + name := args[0] + + af, err := wallet.Load(accKind) + cobra.CheckErr(err) + + // Ask for passphrase to encrypt the wallet with. + var passphrase string + if af.RequiresPassphrase() { + passphrase = common.AskNewPassphrase() + } + + accCfg := &config.Account{ + Kind: accKind, + } + err = accCfg.SetConfigFromFlags() + cobra.CheckErr(err) + + if _, exists := cfg.AddressBook.All[name]; exists { + cobra.CheckErr(fmt.Errorf("address named '%s' already exists in address book", name)) + } + err = cfg.Wallet.Create(name, passphrase, accCfg) + cobra.CheckErr(err) + + err = cfg.Save() + cobra.CheckErr(err) + }, +} + +func init() { + flags := flag.NewFlagSet("", flag.ContinueOnError) + kinds := make([]string, 0, len(wallet.AvailableKinds())) + for _, w := range wallet.AvailableKinds() { + kinds = append(kinds, w.Kind()) + } + flags.StringVar(&accKind, "kind", "file", fmt.Sprintf("Account kind [%s]", strings.Join(kinds, ", "))) + + // TODO: Group flags in usage by tweaking the usage template/function. + for _, af := range wallet.AvailableKinds() { + flags.AddFlagSet(af.Flags()) + } + + createCmd.Flags().AddFlagSet(flags) +} diff --git a/cmd/wallet/export.go b/cmd/wallet/export.go new file mode 100644 index 00000000..67381c12 --- /dev/null +++ b/cmd/wallet/export.go @@ -0,0 +1,27 @@ +package wallet + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/oasisprotocol/cli/cmd/common" + "github.com/oasisprotocol/cli/config" +) + +var exportCmd = &cobra.Command{ + Use: "export ", + Short: "Export secret account information", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + name := args[0] + + fmt.Printf("WARNING: Exporting the account will expose secret key material!\n") + acc := common.LoadAccount(config.Global(), name) + + showPublicWalletInfo(name, acc) + + fmt.Printf("Export:\n") + fmt.Println(acc.UnsafeExport()) + }, +} diff --git a/cmd/wallet/import.go b/cmd/wallet/import.go new file mode 100644 index 00000000..1184236a --- /dev/null +++ b/cmd/wallet/import.go @@ -0,0 +1,87 @@ +package wallet + +import ( + "fmt" + + "github.com/AlecAivazis/survey/v2" + "github.com/spf13/cobra" + + "github.com/oasisprotocol/cli/cmd/common" + "github.com/oasisprotocol/cli/config" + "github.com/oasisprotocol/cli/wallet" + walletFile "github.com/oasisprotocol/cli/wallet/file" +) + +var importCmd = &cobra.Command{ + Use: "import ", + Short: "Import an existing account", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + cfg := config.Global() + name := args[0] + + if _, exists := cfg.Wallet.All[name]; exists { + cobra.CheckErr(fmt.Errorf("account '%s' already exists in the wallet", name)) + } + if _, exists := cfg.AddressBook.All[name]; exists { + cobra.CheckErr(fmt.Errorf("address named '%s' already exists in the address book", name)) + } + + // NOTE: We only support importing into the file-based wallet for now. + af, err := wallet.Load(walletFile.Kind) + cobra.CheckErr(err) + + // Ask for import kind. + var supportedKinds []string + for _, kind := range af.SupportedImportKinds() { + supportedKinds = append(supportedKinds, string(kind)) + } + + var kindRaw string + err = survey.AskOne(&survey.Select{ + Message: "Kind:", + Options: supportedKinds, + }, &kindRaw) + cobra.CheckErr(err) + + var kind wallet.ImportKind + err = kind.UnmarshalText([]byte(kindRaw)) + cobra.CheckErr(err) + + // Ask for wallet configuration. + afCfg, err := af.GetConfigFromSurvey(&kind) + cobra.CheckErr(err) + + // Ask for import data. + var answers struct { + Data string + } + questions := []*survey.Question{ + { + Name: "data", + Prompt: af.DataPrompt(kind, afCfg), + Validate: af.DataValidator(kind, afCfg), + }, + } + err = survey.Ask(questions, &answers) + cobra.CheckErr(err) + + // Ask for passphrase. + passphrase := common.AskNewPassphrase() + + accCfg := &config.Account{ + Kind: af.Kind(), + Config: afCfg, + } + src := &wallet.ImportSource{ + Kind: kind, + Data: answers.Data, + } + + err = cfg.Wallet.Import(name, passphrase, accCfg, src) + cobra.CheckErr(err) + + err = cfg.Save() + cobra.CheckErr(err) + }, +} diff --git a/cmd/wallet/list.go b/cmd/wallet/list.go new file mode 100644 index 00000000..49b470c0 --- /dev/null +++ b/cmd/wallet/list.go @@ -0,0 +1,43 @@ +package wallet + +import ( + "sort" + + "github.com/spf13/cobra" + + "github.com/oasisprotocol/cli/cmd/common" + "github.com/oasisprotocol/cli/config" + "github.com/oasisprotocol/cli/table" +) + +var listCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List configured accounts", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + cfg := config.Global() + table := table.New() + table.SetHeader([]string{"Account", "Kind", "Address"}) + + var output [][]string + for name, acc := range cfg.Wallet.All { + if cfg.Wallet.Default == name { + name += common.DefaultMarker + } + output = append(output, []string{ + name, + acc.PrettyKind(), + acc.Address, + }) + } + + // Sort output by name. + sort.Slice(output, func(i, j int) bool { + return output[i][0] < output[j][0] + }) + + table.AppendBulk(output) + table.Render() + }, +} diff --git a/cmd/wallet/remoteSigner.go b/cmd/wallet/remoteSigner.go new file mode 100644 index 00000000..b020e454 --- /dev/null +++ b/cmd/wallet/remoteSigner.go @@ -0,0 +1,70 @@ +package wallet + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/oasisprotocol/oasis-core/go/common/crypto/signature" + "github.com/oasisprotocol/oasis-core/go/common/crypto/signature/signers/remote" + "github.com/oasisprotocol/oasis-core/go/common/grpc" + "github.com/oasisprotocol/oasis-core/go/common/identity" + "github.com/oasisprotocol/oasis-core/go/common/logging" + "github.com/oasisprotocol/oasis-core/go/oasis-node/cmd/common/background" + + "github.com/oasisprotocol/cli/cmd/common" + "github.com/oasisprotocol/cli/config" +) + +var remoteSignerCmd = &cobra.Command{ + Use: "remote-signer ", + Short: "Act as a oasis-node remote entity signer over AF_LOCAL", + Args: cobra.ExactArgs(2), + Run: func(cmd *cobra.Command, args []string) { + name, socketPath := args[0], args[1] + + acc := common.LoadAccount(config.Global(), name) + + sf := &accountEntitySignerFactory{ + signer: acc.ConsensusSigner(), + } + if sf.signer == nil { + cobra.CheckErr("account not compatible with consensus layer usage") + } + + // The domain separation is entirely handled on the client side. + signature.UnsafeAllowUnregisteredContexts() + + // Suppress oasis-core logging. + err := logging.Initialize( + nil, + logging.FmtLogfmt, + logging.LevelInfo, + nil, + ) + cobra.CheckErr(err) + + // Setup the gRPC service. + srvCfg := &grpc.ServerConfig{ + Name: "remote-signer", + Path: socketPath, // XXX: Maybe fix this up to be nice. + Identity: &identity.Identity{}, + } + srv, err := grpc.NewServer(srvCfg) + cobra.CheckErr(err) + remote.RegisterService(srv.Server(), sf) + + // Start the service and wait for graceful termination. + err = srv.Start() + cobra.CheckErr(err) + + fmt.Printf("Address: %s\n", acc.Address()) + fmt.Printf("Node Args:\n --signer.backend=remote \\\n --signer.remote.address=unix:%s\n", socketPath) + fmt.Printf("\n*** REMOTE SIGNER READY ***\n") + + sm := background.NewServiceManager(logging.GetLogger("remote-signer")) + sm.Register(srv) + defer sm.Cleanup() + sm.Wait() + }, +} diff --git a/cmd/wallet/remove.go b/cmd/wallet/remove.go new file mode 100644 index 00000000..04e6bb34 --- /dev/null +++ b/cmd/wallet/remove.go @@ -0,0 +1,47 @@ +package wallet + +import ( + "fmt" + + "github.com/AlecAivazis/survey/v2" + "github.com/spf13/cobra" + + "github.com/oasisprotocol/cli/config" +) + +var rmCmd = &cobra.Command{ + Use: "remove ", + Aliases: []string{"rm"}, + Short: "Remove an existing account", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + cfg := config.Global() + name := args[0] + + // Early check for whether the wallet exists so that we don't ask for confirmation first. + if _, exists := cfg.Wallet.All[name]; !exists { + cobra.CheckErr(fmt.Errorf("account '%s' does not exist", name)) + } + + fmt.Printf("WARNING: Removing the account will ERASE secret key material!\n") + fmt.Printf("WARNING: THIS ACTION IS IRREVERSIBLE!\n") + + var result string + confirmText := fmt.Sprintf("I really want to remove account %s", name) + prompt := &survey.Input{ + Message: fmt.Sprintf("Enter '%s' (without quotes) to confirm removal:", confirmText), + } + err := survey.AskOne(prompt, &result) + cobra.CheckErr(err) + + if result != confirmText { + cobra.CheckErr("Aborted.") + } + + err = cfg.Wallet.Remove(name) + cobra.CheckErr(err) + + err = cfg.Save() + cobra.CheckErr(err) + }, +} diff --git a/cmd/wallet/rename.go b/cmd/wallet/rename.go new file mode 100644 index 00000000..f8da4655 --- /dev/null +++ b/cmd/wallet/rename.go @@ -0,0 +1,29 @@ +package wallet + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/oasisprotocol/cli/config" +) + +var renameCmd = &cobra.Command{ + Use: "rename ", + Short: "Rename an existing account", + Aliases: []string{"mv"}, + Args: cobra.ExactArgs(2), + Run: func(cmd *cobra.Command, args []string) { + cfg := config.Global() + oldName, newName := args[0], args[1] + + if _, exists := cfg.AddressBook.All[newName]; exists { + cobra.CheckErr(fmt.Errorf("address named '%s' already exists in the address book", newName)) + } + err := cfg.Wallet.Rename(oldName, newName) + cobra.CheckErr(err) + + err = cfg.Save() + cobra.CheckErr(err) + }, +} diff --git a/cmd/wallet/set_default.go b/cmd/wallet/set_default.go new file mode 100644 index 00000000..5121bc05 --- /dev/null +++ b/cmd/wallet/set_default.go @@ -0,0 +1,23 @@ +package wallet + +import ( + "github.com/spf13/cobra" + + "github.com/oasisprotocol/cli/config" +) + +var setDefaultCmd = &cobra.Command{ + Use: "set-default ", + Short: "Sets the given account as the default account", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + cfg := config.Global() + name := args[0] + + err := cfg.Wallet.SetDefault(name) + cobra.CheckErr(err) + + err = cfg.Save() + cobra.CheckErr(err) + }, +} diff --git a/cmd/wallet/show.go b/cmd/wallet/show.go new file mode 100644 index 00000000..6ce45335 --- /dev/null +++ b/cmd/wallet/show.go @@ -0,0 +1,35 @@ +package wallet + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/oasisprotocol/cli/cmd/common" + "github.com/oasisprotocol/cli/config" + "github.com/oasisprotocol/cli/wallet" +) + +var showCmd = &cobra.Command{ + Use: "show ", + Short: "Show public account information", + Aliases: []string{"s"}, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + name := args[0] + + acc := common.LoadAccount(config.Global(), name) + showPublicWalletInfo(name, acc) + }, +} + +func showPublicWalletInfo(name string, wallet wallet.Account) { + fmt.Printf("Name: %s\n", name) + if signer := wallet.Signer(); signer != nil { + fmt.Printf("Public Key: %s\n", signer.Public()) + } + if ethAddr := wallet.EthAddress(); ethAddr != nil { + fmt.Printf("Ethereum address: %s\n", ethAddr.Hex()) + } + fmt.Printf("Native address: %s\n", wallet.Address()) +} diff --git a/cmd/wallet/wallet.go b/cmd/wallet/wallet.go new file mode 100644 index 00000000..43f80229 --- /dev/null +++ b/cmd/wallet/wallet.go @@ -0,0 +1,58 @@ +package wallet + +import ( + "fmt" + "io" + + "github.com/spf13/cobra" + + "github.com/oasisprotocol/oasis-core/go/common/crypto/signature" +) + +var Cmd = &cobra.Command{ + Use: "wallet", + Short: "Manage accounts in the local wallet", + Aliases: []string{"w"}, +} + +type accountEntitySignerFactory struct { + signer signature.Signer +} + +func (sf *accountEntitySignerFactory) EnsureRole( + role signature.SignerRole, +) error { + if role != signature.SignerEntity { + return signature.ErrInvalidRole + } + return nil +} + +func (sf *accountEntitySignerFactory) Generate( + _ signature.SignerRole, + _ io.Reader, +) (signature.Signer, error) { + // The remote signer should never require this. + return nil, fmt.Errorf("refusing to generate new signing keys") +} + +func (sf *accountEntitySignerFactory) Load( + role signature.SignerRole, +) (signature.Signer, error) { + if err := sf.EnsureRole(role); err != nil { + return nil, err + } + return sf.signer, nil +} + +func init() { + Cmd.AddCommand(listCmd) + Cmd.AddCommand(createCmd) + Cmd.AddCommand(showCmd) + Cmd.AddCommand(rmCmd) + Cmd.AddCommand(renameCmd) + Cmd.AddCommand(setDefaultCmd) + Cmd.AddCommand(importCmd) + Cmd.AddCommand(exportCmd) + Cmd.AddCommand(remoteSignerCmd) +}