From f3a005f176372ff291dfa7c02ee1c87d18e9c788 Mon Sep 17 00:00:00 2001 From: Joseph Cook <33655003+jmcook1186@users.noreply.github.com> Date: Wed, 2 Nov 2022 18:02:32 +0000 Subject: [PATCH] cmd/clef: add `list-accounts` and `list-wallets` to CLI (#26080) This commit adds support for two new commands to clef, making it possible to list accounts / wallets from the command-line-interface. Co-authored-by: Martin Holst Swende --- cmd/clef/main.go | 125 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 97 insertions(+), 28 deletions(-) diff --git a/cmd/clef/main.go b/cmd/clef/main.go index a3e4815ed..188a11500 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -203,9 +203,8 @@ The delpw command removes a password for a given address (keyfile). }, Description: ` The newaccount command creates a new keystore-backed account. It is a convenience-method -which can be used in lieu of an external UI.`, - } - +which can be used in lieu of an external UI. +`} gendocCommand = &cli.Command{ Action: GenDoc, Name: "gendoc", @@ -213,6 +212,32 @@ which can be used in lieu of an external UI.`, Description: ` The gendoc generates example structures of the json-rpc communication types. `} + listAccountsCommand = &cli.Command{ + Action: listAccounts, + Name: "list-accounts", + Usage: "List accounts in the keystore", + Flags: []cli.Flag{ + logLevelFlag, + keystoreFlag, + utils.LightKDFFlag, + acceptFlag, + }, + Description: ` + Lists the accounts in the keystore. + `} + listWalletsCommand = &cli.Command{ + Action: listWallets, + Name: "list-wallets", + Usage: "List wallets known to Clef", + Flags: []cli.Flag{ + logLevelFlag, + keystoreFlag, + utils.LightKDFFlag, + acceptFlag, + }, + Description: ` + Lists the wallets known to Clef. + `} ) var app = flags.NewApp("Manage Ethereum account operations") @@ -249,6 +274,8 @@ func init() { delCredentialCommand, newAccountCommand, gendocCommand, + listAccountsCommand, + listWalletsCommand, } } @@ -351,6 +378,22 @@ func attestFile(ctx *cli.Context) error { return nil } +func initInternalApi(c *cli.Context) (*core.UIServerAPI, error) { + if err := initialize(c); err != nil { + return nil, err + } + var ( + ui = core.NewCommandlineUI() + pwStorage storage.Storage = &storage.NoStorage{} + ksLoc = c.String(keystoreFlag.Name) + lightKdf = c.Bool(utils.LightKDFFlag.Name) + ) + am := core.StartClefAccountManager(ksLoc, true, lightKdf, "") + api := core.NewSignerAPI(am, 0, true, ui, nil, false, pwStorage) + internalApi := core.NewUIServerAPI(api) + return internalApi, nil +} + func setCredential(ctx *cli.Context) error { if ctx.NArg() < 1 { utils.Fatalf("This command requires an address to be passed as an argument") @@ -409,31 +452,6 @@ func removeCredential(ctx *cli.Context) error { return nil } -func newAccount(c *cli.Context) error { - if err := initialize(c); err != nil { - return err - } - // The newaccount is meant for users using the CLI, since 'real' external - // UIs can use the UI-api instead. So we'll just use the native CLI UI here. - var ( - ui = core.NewCommandlineUI() - pwStorage storage.Storage = &storage.NoStorage{} - ksLoc = c.String(keystoreFlag.Name) - lightKdf = c.Bool(utils.LightKDFFlag.Name) - ) - log.Info("Starting clef", "keystore", ksLoc, "light-kdf", lightKdf) - am := core.StartClefAccountManager(ksLoc, true, lightKdf, "") - // This gives is us access to the external API - apiImpl := core.NewSignerAPI(am, 0, true, ui, nil, false, pwStorage) - // This gives us access to the internal API - internalApi := core.NewUIServerAPI(apiImpl) - addr, err := internalApi.New(context.Background()) - if err == nil { - fmt.Printf("Generated account %v\n", addr.String()) - } - return err -} - func initialize(c *cli.Context) error { // Set up the logger to print everything logOutput := os.Stdout @@ -459,6 +477,57 @@ func initialize(c *cli.Context) error { return nil } +func newAccount(c *cli.Context) error { + internalApi, err := initInternalApi(c) + if err != nil { + return err + } + addr, err := internalApi.New(context.Background()) + if err == nil { + fmt.Printf("Generated account %v\n", addr.String()) + } + return err +} + +func listAccounts(c *cli.Context) error { + internalApi, err := initInternalApi(c) + if err != nil { + return err + } + accs, err := internalApi.ListAccounts(context.Background()) + if err != nil { + return err + } + if len(accs) == 0 { + fmt.Println("\nThe keystore is empty.") + } + fmt.Println() + for _, account := range accs { + fmt.Printf("%v (%v)\n", account.Address, account.URL) + } + return err +} + +func listWallets(c *cli.Context) error { + internalApi, err := initInternalApi(c) + if err != nil { + return err + } + wallets := internalApi.ListWallets() + if len(wallets) == 0 { + fmt.Println("\nThere are no wallets.") + } + fmt.Println() + for i, wallet := range wallets { + fmt.Printf("- Wallet %d at %v (%v %v)\n", i, wallet.URL, wallet.Status, wallet.Failure) + for j, acc := range wallet.Accounts { + fmt.Printf(" -Account %d: %v (%v)\n", j, acc.Address, acc.URL) + } + fmt.Println() + } + return nil +} + // ipcEndpoint resolves an IPC endpoint based on a configured value, taking into // account the set data folders as well as the designated platform we're currently // running on.