diff --git a/cmd/client_gnfd.go b/cmd/client_gnfd.go index 6466592..3e351fc 100644 --- a/cmd/client_gnfd.go +++ b/cmd/client_gnfd.go @@ -13,12 +13,31 @@ import ( const iso8601DateFormatSecond = "2006-01-02T15:04:05Z" +func cmdShowVersion() *cli.Command { + return &cli.Command{ + Name: "version", + Action: showVersion, + Usage: "print version info", + ArgsUsage: "", + Description: ` + +Examples: +$ gnfd-cmd version `, + } +} + +func showVersion(ctx *cli.Context) error { + fmt.Println("Greenfield Cmd Version:", Version) + return nil +} + // NewClient returns a new greenfield client func NewClient(ctx *cli.Context) (client.Client, error) { - privateKey, err := parseKeystore(ctx) + privateKey, _, err := parseKeystore(ctx) if err != nil { return nil, err } + account, err := sdktypes.NewAccountFromPrivateKey("gnfd-account", privateKey) if err != nil { fmt.Println("new account err", err.Error()) diff --git a/cmd/cmd_account.go b/cmd/cmd_account.go index fa30b7d..616e93b 100644 --- a/cmd/cmd_account.go +++ b/cmd/cmd_account.go @@ -2,243 +2,92 @@ package main import ( "context" + "encoding/json" + "errors" "fmt" - "strings" + "os" + "path/filepath" "cosmossdk.io/math" + sdktypes "github.com/bnb-chain/greenfield-go-sdk/types" "github.com/bnb-chain/greenfield/sdk/types" - gnfdsdktypes "github.com/bnb-chain/greenfield/sdk/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/urfave/cli/v2" ) -// cmdCreatePaymentAccount creates a payment account under the owner -func cmdCreatePaymentAccount() *cli.Command { +// cmdImportAccount import the account by private key file +func cmdImportAccount() *cli.Command { return &cli.Command{ - Name: "create", - Action: CreatePaymentAccount, - Usage: "create a payment account", - ArgsUsage: "", + Name: "import", + Action: importKey, + Usage: "import the account by the private key file", + ArgsUsage: " ", Description: ` -Create a payment account +Import account info from private key file and generate a keystore file to manage user's private key information. +If no keyfile is specified by --keystore or -k flag, a keystore will be generated at the default path (homedir/.gnfd-cmd/keystore/key.json) +Users need to set the private key file path which contain the origin private hex string . Examples: -# Create a payment account -$ gnfd-cmd payment-account create`, - } -} - -func CreatePaymentAccount(ctx *cli.Context) error { - client, err := NewClient(ctx) - if err != nil { - return toCmdErr(err) - } - c, createPaymentAccount := context.WithCancel(globalContext) - defer createPaymentAccount() - acc, err := client.GetDefaultAccount() - if err != nil { - return toCmdErr(err) +// key.txt contains the origin private hex string +$ gnfd-cmd -k key.json account import key.txt `, } - txHash, err := client.CreatePaymentAccount(c, acc.GetAddress().String(), types.TxOption{}) - if err != nil { - return toCmdErr(err) - } - - err = waitTxnStatus(client, c, txHash, "CreatePaymentAccount") - if err != nil { - return toCmdErr(err) - } - - fmt.Printf("create payment account for %s succ, txHash: %s\n", acc.GetAddress().String(), txHash) - return nil } -// cmdPaymentDeposit makes deposit from the owner account to the payment account -func cmdPaymentDeposit() *cli.Command { +func cmdListAccount() *cli.Command { return &cli.Command{ - Name: "deposit", - Action: Deposit, - Usage: "deposit into stream(payment) account", + Name: "ls", + Action: listAccounts, + Usage: "list account info", + ArgsUsage: " ", Description: ` -Make a deposit into stream(payment) account +list the account info, if the user needs to print the privateKey info, set privateKey flag as true Examples: -# deposit a stream account -$ gnfd-cmd payment-account deposit --toAddress 0x.. --amount 12345`, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: toAddressFlag, - Value: "", - Usage: "the stream account", - Required: true, - }, - &cli.StringFlag{ - Name: amountFlag, - Value: "", - Usage: "the amount to be deposited", - }, - }, - } -} - -func Deposit(ctx *cli.Context) error { - client, err := NewClient(ctx) - if err != nil { - return toCmdErr(err) - } - - toAddr := ctx.String(toAddressFlag) - _, err = sdk.AccAddressFromHexUnsafe(toAddr) - if err != nil { - return toCmdErr(err) - } - amountStr := ctx.String(amountFlag) - amount, ok := math.NewIntFromString(amountStr) - if !ok { - return toCmdErr(fmt.Errorf("invalid amount %s", amountStr)) - } - c, deposit := context.WithCancel(globalContext) - defer deposit() - - txHash, err := client.Deposit(c, toAddr, amount, types.TxOption{}) - if err != nil { - return toCmdErr(err) - } - - err = waitTxnStatus(client, c, txHash, "Deposit") - if err != nil { - return toCmdErr(err) +$ gnfd-cmd account ls `, } - fmt.Printf("Deposit %s BNB to payment account %s succ, txHash=%s\n", amount.String(), toAddr, txHash) - return nil } -// cmdPaymentWithdraw makes a withdrawal from payment account to owner account -func cmdPaymentWithdraw() *cli.Command { +func cmdCreateAccount() *cli.Command { return &cli.Command{ - Name: "withdraw", - Action: Withdraw, - Usage: "withdraw from stream(payment) account", + Name: "new", + Action: createAccount, + Usage: "create a new account", + ArgsUsage: "", Description: ` -Make a withdrawal from stream(payment) account +create a new account and store the private key in a keystore file Examples: -# withdraw from a stream account back to the creator account -$ gnfd-cmd payment-account withdraw --fromAddress 0x.. --amount 12345`, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: fromAddressFlag, - Value: "", - Usage: "the stream account", - Required: true, - }, - &cli.StringFlag{ - Name: amountFlag, - Value: "", - Usage: "the amount to be withdrew", - }, - }, +$ gnfd-cmd account new `, } } -func Withdraw(ctx *cli.Context) error { - client, err := NewClient(ctx) - if err != nil { - return toCmdErr(err) - } - - fromAddr := ctx.String(fromAddressFlag) - _, err = sdk.AccAddressFromHexUnsafe(fromAddr) - if err != nil { - return toCmdErr(err) - } - amountStr := ctx.String(amountFlag) - amount, ok := math.NewIntFromString(amountStr) - if !ok { - return toCmdErr(fmt.Errorf("invalid amount %s", amountStr)) - } - c, deposit := context.WithCancel(globalContext) - defer deposit() - - txHash, err := client.Withdraw(c, fromAddr, amount, types.TxOption{}) - if err != nil { - return toCmdErr(err) - } - - err = waitTxnStatus(client, c, txHash, "Withdraw") - if err != nil { - return toCmdErr(err) - } - - fmt.Printf("Withdraw %s from %s succ, txHash=%s\n", amount.String(), fromAddr, txHash) - return nil -} - -// cmdListPaymentAccounts list the payment accounts belong to the owner -func cmdListPaymentAccounts() *cli.Command { +func cmdExportAccount() *cli.Command { return &cli.Command{ - Name: "ls", - Action: listPaymentAccounts, - Usage: "list payment accounts of the owner", - ArgsUsage: "address of owner", + Name: "export", + Action: exportAccount, + Usage: "export private key info ", + ArgsUsage: "", Description: ` -List payment accounts of the owner. +Export a private key from the local keyring file in a encrypted format. +When both the --unarmored-hex and --unsafe flags are selected, cryptographic +private key material is exported in an INSECURE fashion that is designed to +allow users to import their keys in hot wallets. Examples: -$ gnfd-cmd payment-account ls `, +$ gnfd-cmd account export --unarmoredHex --unsafe`, Flags: []cli.Flag{ - &cli.StringFlag{ - Name: ownerAddressFlag, - Value: "", - Usage: "indicate a owner's payment accounts to be list, account address can be omitted for current user's accounts listing", + &cli.BoolFlag{ + Name: unsafeFlag, + Usage: "indicate export private key in plain text", + }, + &cli.BoolFlag{ + Name: unarmoredFlag, + Usage: "indicate export private key in plain text", }, }, } } -func listPaymentAccounts(ctx *cli.Context) error { - client, err := NewClient(ctx) - if err != nil { - return toCmdErr(err) - } - - c, cancelCreateBucket := context.WithCancel(globalContext) - defer cancelCreateBucket() - - var ownerAddr string - ownerAddrStr := ctx.String(ownerAddressFlag) - if ownerAddrStr != "" { - _, err = sdk.AccAddressFromHexUnsafe(ownerAddrStr) - if err != nil { - return toCmdErr(err) - } - ownerAddr = ownerAddrStr - } else { - acct, err := client.GetDefaultAccount() - if err != nil { - return toCmdErr(err) - } - ownerAddr = acct.GetAddress().String() - } - accounts, err := client.GetPaymentAccountsByOwner(c, ownerAddr) - if err != nil { - if strings.Contains(err.Error(), "not found") { - fmt.Println("Accounts not exist") - return nil - } - return toCmdErr(err) - } - if len(accounts) == 0 { - fmt.Println("Accounts not exist") - return nil - } - fmt.Println("payment accounts list:") - for i, a := range accounts { - fmt.Printf("%d: %s \n", i+1, a) - } - return nil -} - func cmdGetAccountBalance() *cli.Command { return &cli.Command{ Name: "balance", @@ -289,7 +138,7 @@ func getAccountBalance(ctx *cli.Context) error { if err != nil { return toCmdErr(err) } - fmt.Printf("balance: %s wei%s\n", resp.Amount.String(), gnfdsdktypes.Denom) + fmt.Printf("balance: %s wei%s\n", resp.Amount.String(), types.Denom) return nil } @@ -321,38 +170,6 @@ $ gnfd-cmd bank transfer --toAddress 0x.. --amount 12345`, } } -func Transfer(ctx *cli.Context) error { - client, err := NewClient(ctx) - if err != nil { - return toCmdErr(err) - } - - c, transfer := context.WithCancel(globalContext) - defer transfer() - - toAddr := ctx.String(toAddressFlag) - _, err = sdk.AccAddressFromHexUnsafe(toAddr) - if err != nil { - return toCmdErr(err) - } - amountStr := ctx.String(amountFlag) - amount, ok := math.NewIntFromString(amountStr) - if !ok { - return toCmdErr(fmt.Errorf("%s is not valid amount", amount)) - } - txHash, err := client.Transfer(c, toAddr, amount, types.TxOption{}) - if err != nil { - return toCmdErr(err) - } - - err = waitTxnStatus(client, c, txHash, "Transfer") - if err != nil { - return toCmdErr(err) - } - fmt.Printf("transfer %s BNB to address %s succ, txHash: %s\n", amountStr, toAddr, txHash) - return nil -} - // cmdBridge makes a transfer from Greenfield to BSC func cmdBridge() *cli.Command { return &cli.Command{ @@ -383,6 +200,160 @@ $ gnfd-cmd bank bridge --toAddress 0x.. --amount 12345`, } } +func importKey(ctx *cli.Context) error { + keyFilePath := ctx.String("keystore") + if keyFilePath == "" { + homeDir, err := getHomeDir(ctx) + if err != nil { + return toCmdErr(err) + } + keyFilePath = filepath.Join(homeDir, DefaultKeyStorePath) + } + + if _, err := os.Stat(keyFilePath); err == nil { + return toCmdErr(errors.New("key already exists at :" + keyFilePath)) + } else if !os.IsNotExist(err) { + return toCmdErr(err) + } + + privateKeyFile := ctx.Args().First() + if privateKeyFile == "" { + return toCmdErr(errors.New("fail to get the private key file info")) + } + + // Load private key from file. + privateKey, addr, err := loadKey(privateKeyFile) + if err != nil { + return toCmdErr(errors.New("failed to load private key: %v" + err.Error())) + } + + key := &Key{ + Address: addr, + PrivateKey: privateKey, + } + + // fetch password content + password, err := getPassword(ctx) + if err != nil { + return toCmdErr(err) + } + + // encrypt the private key + encryptContent, err := EncryptKey(key, password, EncryptScryptN, EncryptScryptP) + if err != nil { + return toCmdErr(err) + } + + if err := os.MkdirAll(filepath.Dir(keyFilePath), 0700); err != nil { + return toCmdErr(errors.New("failed to create directory %s" + filepath.Dir(keyFilePath))) + } + + // store the keystore file + if err := os.WriteFile(keyFilePath, encryptContent, 0600); err != nil { + return toCmdErr(fmt.Errorf("failed to write keyfile to the path%s: %v", keyFilePath, err)) + } + + fmt.Printf("import account successfully, key address: %s, encrypted key file: %s \n", key.Address, keyFilePath) + + return nil +} + +func listAccounts(ctx *cli.Context) error { + keyJson, keyFile, err := loadKeyStoreFile(ctx) + if err != nil { + return toCmdErr(err) + } + + k := new(encryptedKey) + if err = json.Unmarshal(keyJson, k); err != nil { + return toCmdErr(err) + } + + fmt.Printf("Account: { %s }, Keystore : %s \n", k.Address, keyFile) + return nil +} + +func exportAccount(ctx *cli.Context) error { + unsafe := ctx.Bool(unsafeFlag) + unarmored := ctx.Bool(unarmoredFlag) + + if unarmored && unsafe { + privateKey, _, err := parseKeystore(ctx) + if err != nil { + return toCmdErr(err) + } + fmt.Println("Private key: ", privateKey) + return nil + } else if unarmored || unsafe { + return fmt.Errorf("the flags %s and %s must be used together", unsafeFlag, unarmoredFlag) + } + + keyContent, _, err := loadKeyStoreFile(ctx) + if err != nil { + return toCmdErr(err) + } + + keyJson := new(encryptedKey) + if err = json.Unmarshal(keyContent, keyJson); err != nil { + return toCmdErr(err) + } + + fmt.Println("Armored key: ", keyJson.Crypto.CipherText) + + return nil +} + +func createAccount(ctx *cli.Context) error { + keyFilePath := ctx.String("keystore") + if keyFilePath == "" { + homeDirname, err := getHomeDir(ctx) + if err != nil { + return toCmdErr(err) + } + keyFilePath = filepath.Join(homeDirname, DefaultKeyStorePath) + } + + if _, err := os.Stat(keyFilePath); err == nil { + return toCmdErr(errors.New("key already exists at :" + keyFilePath)) + } else if !os.IsNotExist(err) { + return toCmdErr(err) + } + + account, privateKey, err := sdktypes.NewAccount("gnfd-account") + if err != nil { + return toCmdErr(err) + } + + key := &Key{ + Address: account.GetAddress(), + PrivateKey: privateKey, + } + + // fetch password content + password, err := getPassword(ctx) + if err != nil { + return toCmdErr(err) + } + + // encrypt the private key + encryptContent, err := EncryptKey(key, password, EncryptScryptN, EncryptScryptP) + if err != nil { + return toCmdErr(err) + } + + if err := os.MkdirAll(filepath.Dir(keyFilePath), 0700); err != nil { + return toCmdErr(errors.New("failed to create directory %s" + filepath.Dir(keyFilePath))) + } + + // store the keystore file + if err := os.WriteFile(keyFilePath, encryptContent, 0600); err != nil { + return toCmdErr(fmt.Errorf("failed to write keyfile to the path%s: %v", keyFilePath, err)) + } + + fmt.Printf("create new account: {%s} successfully \n", account.GetAddress()) + return nil +} + func Bridge(ctx *cli.Context) error { client, err := NewClient(ctx) if err != nil { @@ -402,7 +373,7 @@ func Bridge(ctx *cli.Context) error { if !ok { return toCmdErr(fmt.Errorf("%s is not valid amount", amount)) } - txResp, err := client.TransferOut(c, toAddr, amount, gnfdsdktypes.TxOption{}) + txResp, err := client.TransferOut(c, toAddr, amount, types.TxOption{}) if err != nil { return toCmdErr(err) } @@ -415,3 +386,55 @@ func Bridge(ctx *cli.Context) error { fmt.Printf("transfer out %s BNB to %s succ, txHash: %s\n", amountStr, toAddr, txResp.TxHash) return nil } + +func Transfer(ctx *cli.Context) error { + client, err := NewClient(ctx) + if err != nil { + return toCmdErr(err) + } + + c, transfer := context.WithCancel(globalContext) + defer transfer() + + toAddr := ctx.String(toAddressFlag) + _, err = sdk.AccAddressFromHexUnsafe(toAddr) + if err != nil { + return toCmdErr(err) + } + amountStr := ctx.String(amountFlag) + amount, ok := math.NewIntFromString(amountStr) + if !ok { + return toCmdErr(fmt.Errorf("%s is not valid amount", amount)) + } + txHash, err := client.Transfer(c, toAddr, amount, types.TxOption{}) + if err != nil { + return toCmdErr(err) + } + + err = waitTxnStatus(client, c, txHash, "Transfer") + if err != nil { + return toCmdErr(err) + } + fmt.Printf("transfer %s BNB to address %s succ, txHash: %s\n", amountStr, toAddr, txHash) + return nil +} + +func parseKeystore(ctx *cli.Context) (string, string, error) { + keyjson, keyFile, err := loadKeyStoreFile(ctx) + if err != nil { + return "", "", toCmdErr(err) + } + + // fetch password content + password, err := getPassword(ctx) + if err != nil { + return "", "", toCmdErr(err) + } + + privateKey, err := DecryptKey(keyjson, password) + if err != nil { + return "", "", fmt.Errorf("failed to decrypting key: %v \n", err) + } + + return privateKey, keyFile, nil +} diff --git a/cmd/cmd_keystore.go b/cmd/cmd_keystore.go deleted file mode 100644 index 8f917dc..0000000 --- a/cmd/cmd_keystore.go +++ /dev/null @@ -1,207 +0,0 @@ -package main - -import ( - "encoding/hex" - "errors" - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/cosmos/cosmos-sdk/crypto/hd" - "github.com/cosmos/cosmos-sdk/crypto/keys/eth/ethsecp256k1" - "github.com/urfave/cli/v2" -) - -// cmdGenerateKey generate keystore file -func cmdGenerateKey() *cli.Command { - return &cli.Command{ - Name: "generate", - Action: generateKey, - Usage: "create a new keystore file", - ArgsUsage: "[ ] ", - Description: ` -generate a keystore file to manage user's private key information. -Examples: -$ gnfd-cmd keystore generate --privKeyFile key.txt `, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: privKeyFileFlag, - Value: "", - Usage: "the private key file path which contain the origin private hex string", - Required: true, - }, - &cli.StringFlag{ - Name: passwordFileFlag, - Value: "", - Usage: "the file which contains the password for the keyfile", - }, - }, - } -} - -func cmdPrintKey() *cli.Command { - return &cli.Command{ - Name: "inspect", - Action: inspectKey, - Usage: "inspect a keystore file", - ArgsUsage: "[ ] ", - Description: ` -print the private key related information - -Examples: -$ gnfd-cmd keystore inspect --privateKey true `, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: privKeyFlag, - Value: "", - Usage: "include the private key in the output", - }, - &cli.StringFlag{ - Name: passwordFileFlag, - Value: "", - Usage: "the file which contains the password for the keyfile", - }, - }, - } -} - -func generateKey(ctx *cli.Context) error { - keyFilePath := ctx.Args().First() - if keyFilePath == "" { - homeDirname, err := getHomeDir(ctx) - if err != nil { - return toCmdErr(err) - } - keyFilePath = filepath.Join(homeDirname, DefaultKeyStorePath) - } - - if _, err := os.Stat(keyFilePath); err == nil { - return toCmdErr(errors.New("key already exists at :" + keyFilePath)) - } else if !os.IsNotExist(err) { - return toCmdErr(err) - } - - privKeyFile := ctx.String(privKeyFileFlag) - if privKeyFile == "" { - return toCmdErr(errors.New("fail to get private key file path, please set it by --privKeyFile")) - } - - // Load private key from file. - privateKey, addr, err := loadKey(privKeyFile) - if err != nil { - return toCmdErr(errors.New("failed to load private key: %v" + err.Error())) - } - - key := &Key{ - Address: addr, - PrivateKey: privateKey, - } - - // fetch password content - password, err := getPassword(ctx) - if err != nil { - return toCmdErr(err) - } - - // write password content to default password file path - err = writeDefaultPassword(ctx, password) - if err != nil { - return toCmdErr(err) - } - - // encrypt the private key - encryptContent, err := EncryptKey(key, password, EncryptScryptN, EncryptScryptP) - if err != nil { - return toCmdErr(err) - } - - if err := os.MkdirAll(filepath.Dir(keyFilePath), 0700); err != nil { - return toCmdErr(errors.New("failed to create directory %s" + filepath.Dir(keyFilePath))) - } - - // store the keystore file - if err := os.WriteFile(keyFilePath, encryptContent, 0600); err != nil { - return toCmdErr(fmt.Errorf("failed to write keyfile to the path%s: %v", keyFilePath, err)) - } - - fmt.Printf("generate keystore %s successfully, key address: %s \n", keyFilePath, key.Address) - - return nil -} - -func inspectKey(ctx *cli.Context) error { - privateKey, err := parseKeystore(ctx) - if err != nil { - return nil - } - printPrivate := ctx.Bool(privKeyFlag) - - priBytes, err := hex.DecodeString(privateKey) - if err != nil { - return err - } - - var keyBytesArray [32]byte - copy(keyBytesArray[:], priBytes[:32]) - priKey := hd.EthSecp256k1.Generate()(keyBytesArray[:]).(*ethsecp256k1.PrivKey) - pubKey := priKey.PubKey() - - fmt.Println("Address: ", pubKey.Address()) - fmt.Println("Public key: ", pubKey.String()) - if printPrivate { - fmt.Println("Private key: ", privateKey) - } - - return nil -} - -func writeDefaultPassword(ctx *cli.Context, password string) error { - homeDir, err := getHomeDir(ctx) - if err != nil { - return err - } - filePath := filepath.Join(homeDir, DefaultPasswordPath) - - if err := os.MkdirAll(filepath.Dir(filePath), 0700); err != nil { - return errors.New("failed to create password directory :%s" + filepath.Dir(filePath)) - } - - // store the password - if err := os.WriteFile(filePath, []byte(password), 0600); err != nil { - return fmt.Errorf("failed to write password to the path: %s: %v", filePath, err) - } - - fmt.Printf("\ngenerate password file: %s successfully \n", filePath) - return nil -} - -func parseKeystore(ctx *cli.Context) (string, error) { - keyjson, err := loadKeyStoreFile(ctx) - if err != nil { - return "", toCmdErr(err) - } - - var password string - if passwordFile := ctx.String(passwordFileFlag); passwordFile != "" { - // load password from password flag - readContent, err := os.ReadFile(passwordFile) - if err != nil { - return "", errors.New("failed to read password file" + err.Error()) - } - password = strings.TrimRight(string(readContent), "\r\n") - } else { - // load password from default password file path - password, err = loadPassWordFile(ctx) - if err != nil { - return "", toCmdErr(err) - } - } - - privateKey, err := DecryptKey(keyjson, password) - if err != nil { - return "", fmt.Errorf("failed to decrypting key: %v \n", err) - } - - return privateKey, nil -} diff --git a/cmd/cmd_paymentAccount.go b/cmd/cmd_paymentAccount.go new file mode 100644 index 0000000..adfea6a --- /dev/null +++ b/cmd/cmd_paymentAccount.go @@ -0,0 +1,239 @@ +package main + +import ( + "context" + "fmt" + "strings" + + "cosmossdk.io/math" + "github.com/bnb-chain/greenfield/sdk/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/urfave/cli/v2" +) + +// cmdCreatePaymentAccount creates a payment account under the owner +func cmdCreatePaymentAccount() *cli.Command { + return &cli.Command{ + Name: "create", + Action: CreatePaymentAccount, + Usage: "create a payment account", + ArgsUsage: "", + Description: ` +Create a payment account + +Examples: +# Create a payment account +$ gnfd-cmd payment-account create`, + } +} + +func CreatePaymentAccount(ctx *cli.Context) error { + client, err := NewClient(ctx) + if err != nil { + return toCmdErr(err) + } + c, createPaymentAccount := context.WithCancel(globalContext) + defer createPaymentAccount() + acc, err := client.GetDefaultAccount() + if err != nil { + return toCmdErr(err) + } + txHash, err := client.CreatePaymentAccount(c, acc.GetAddress().String(), types.TxOption{}) + if err != nil { + return toCmdErr(err) + } + + err = waitTxnStatus(client, c, txHash, "CreatePaymentAccount") + if err != nil { + return toCmdErr(err) + } + + fmt.Printf("create payment account for %s succ, txHash: %s\n", acc.GetAddress().String(), txHash) + return nil +} + +// cmdPaymentDeposit makes deposit from the owner account to the payment account +func cmdPaymentDeposit() *cli.Command { + return &cli.Command{ + Name: "deposit", + Action: Deposit, + Usage: "deposit into stream(payment) account", + Description: ` +Make a deposit into stream(payment) account + +Examples: +# deposit a stream account +$ gnfd-cmd payment-account deposit --toAddress 0x.. --amount 12345`, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: toAddressFlag, + Value: "", + Usage: "the stream account", + Required: true, + }, + &cli.StringFlag{ + Name: amountFlag, + Value: "", + Usage: "the amount to be deposited", + }, + }, + } +} + +func Deposit(ctx *cli.Context) error { + client, err := NewClient(ctx) + if err != nil { + return toCmdErr(err) + } + + toAddr := ctx.String(toAddressFlag) + _, err = sdk.AccAddressFromHexUnsafe(toAddr) + if err != nil { + return toCmdErr(err) + } + amountStr := ctx.String(amountFlag) + amount, ok := math.NewIntFromString(amountStr) + if !ok { + return toCmdErr(fmt.Errorf("invalid amount %s", amountStr)) + } + c, deposit := context.WithCancel(globalContext) + defer deposit() + + txHash, err := client.Deposit(c, toAddr, amount, types.TxOption{}) + if err != nil { + return toCmdErr(err) + } + + err = waitTxnStatus(client, c, txHash, "Deposit") + if err != nil { + return toCmdErr(err) + } + fmt.Printf("Deposit %s BNB to payment account %s succ, txHash=%s\n", amount.String(), toAddr, txHash) + return nil +} + +// cmdPaymentWithdraw makes a withdrawal from payment account to owner account +func cmdPaymentWithdraw() *cli.Command { + return &cli.Command{ + Name: "withdraw", + Action: Withdraw, + Usage: "withdraw from stream(payment) account", + Description: ` +Make a withdrawal from stream(payment) account + +Examples: +# withdraw from a stream account back to the creator account +$ gnfd-cmd payment-account withdraw --fromAddress 0x.. --amount 12345`, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: fromAddressFlag, + Value: "", + Usage: "the stream account", + Required: true, + }, + &cli.StringFlag{ + Name: amountFlag, + Value: "", + Usage: "the amount to be withdrew", + }, + }, + } +} + +func Withdraw(ctx *cli.Context) error { + client, err := NewClient(ctx) + if err != nil { + return toCmdErr(err) + } + + fromAddr := ctx.String(fromAddressFlag) + _, err = sdk.AccAddressFromHexUnsafe(fromAddr) + if err != nil { + return toCmdErr(err) + } + amountStr := ctx.String(amountFlag) + amount, ok := math.NewIntFromString(amountStr) + if !ok { + return toCmdErr(fmt.Errorf("invalid amount %s", amountStr)) + } + c, deposit := context.WithCancel(globalContext) + defer deposit() + + txHash, err := client.Withdraw(c, fromAddr, amount, types.TxOption{}) + if err != nil { + return toCmdErr(err) + } + + err = waitTxnStatus(client, c, txHash, "Withdraw") + if err != nil { + return toCmdErr(err) + } + + fmt.Printf("Withdraw %s from %s succ, txHash=%s\n", amount.String(), fromAddr, txHash) + return nil +} + +// cmdListPaymentAccounts list the payment accounts belong to the owner +func cmdListPaymentAccounts() *cli.Command { + return &cli.Command{ + Name: "ls", + Action: listPaymentAccounts, + Usage: "list payment accounts of the owner", + ArgsUsage: "address of owner", + Description: ` +List payment accounts of the owner. + +Examples: +$ gnfd-cmd payment-account ls `, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: ownerAddressFlag, + Value: "", + Usage: "indicate a owner's payment accounts to be list, account address can be omitted for current user's accounts listing", + }, + }, + } +} + +func listPaymentAccounts(ctx *cli.Context) error { + client, err := NewClient(ctx) + if err != nil { + return toCmdErr(err) + } + + c, cancelCreateBucket := context.WithCancel(globalContext) + defer cancelCreateBucket() + + var ownerAddr string + ownerAddrStr := ctx.String(ownerAddressFlag) + if ownerAddrStr != "" { + _, err = sdk.AccAddressFromHexUnsafe(ownerAddrStr) + if err != nil { + return toCmdErr(err) + } + ownerAddr = ownerAddrStr + } else { + acct, err := client.GetDefaultAccount() + if err != nil { + return toCmdErr(err) + } + ownerAddr = acct.GetAddress().String() + } + accounts, err := client.GetPaymentAccountsByOwner(c, ownerAddr) + if err != nil { + if strings.Contains(err.Error(), "not found") { + fmt.Println("Accounts not exist") + return nil + } + return toCmdErr(err) + } + if len(accounts) == 0 { + fmt.Println("Accounts not exist") + return nil + } + fmt.Println("payment accounts list:") + for i, a := range accounts { + fmt.Printf("%d: %s \n", i+1, a) + } + return nil +} diff --git a/cmd/cmd_sp.go b/cmd/cmd_sp.go index 8d149d6..0dc8539 100644 --- a/cmd/cmd_sp.go +++ b/cmd/cmd_sp.go @@ -55,7 +55,6 @@ $ gnfd-cmd sp get-price https://gnfd-testnet-sp-1.nodereal.io`, func ListSP(ctx *cli.Context) error { client, err := NewClient(ctx) if err != nil { - fmt.Println("new client err:", err.Error()) return toCmdErr(err) } diff --git a/cmd/key.go b/cmd/key.go index fb23efa..e8d20d7 100644 --- a/cmd/key.go +++ b/cmd/key.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "fmt" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/accounts/keystore" @@ -38,6 +39,7 @@ func DecryptKey(keyJson []byte, auth string) (string, error) { if err := json.Unmarshal(keyJson, k); err != nil { return "", err } + keyBytes, err := decryptKey(k, auth) if err != nil { return "", err @@ -51,5 +53,9 @@ func decryptKey(key *encryptedKey, auth string) (keyBytes []byte, err error) { if err != nil { return nil, err } - return plainText, err + + if len(plainText) == 0 { + return nil, fmt.Errorf("decrypt content empty") + } + return plainText, nil } diff --git a/cmd/main.go b/cmd/main.go index 8de8e4c..96cc30e 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -152,13 +152,16 @@ func main() { }, { - Name: "keystore", + Name: "account", Usage: "support the keystore operation functions", Subcommands: []*cli.Command{ - cmdGenerateKey(), - cmdPrintKey(), + cmdImportAccount(), + cmdListAccount(), + cmdCreateAccount(), + cmdExportAccount(), }, }, + cmdShowVersion(), }, } app.Before = altsrc.InitInputSourceWithContext(flags, altsrc.NewTomlSourceFromFlagFunc("config")) diff --git a/cmd/utils.go b/cmd/utils.go index a30dc61..9bc476c 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -27,6 +27,7 @@ import ( ) const ( + Version = "v0.0.9-alpha.1" maxFileSize = 2 * 1024 * 1024 * 1024 maxListObjects = 100 publicReadType = "public-read" @@ -63,7 +64,8 @@ const ( folderFlag = "folder" privKeyFileFlag = "privKeyFile" - privKeyFlag = "privateKey" + unsafeFlag = "unsafe" + unarmoredFlag = "unarmoredHex" passwordFileFlag = "passwordfile" homeFlag = "home" keyStoreFlag = "keystore" @@ -83,7 +85,6 @@ const ( DefaultConfigPath = "config/config.toml" DefaultConfigDir = ".gnfd-cmd" DefaultKeyStorePath = "keystore/key.json" - DefaultPasswordPath = "keystore/password/password.txt" rpcAddrConfigField = "rpcAddr" chainIdConfigField = "chainId" @@ -343,14 +344,14 @@ func getPassword(ctx *cli.Context) (string, error) { return strings.TrimRight(string(readContent), "\r\n"), nil } - fmt.Print("Input Password:") + fmt.Print("Please enter a passphrase now:") bytePassword, err := term.ReadPassword(int(os.Stdin.Fd())) if err != nil { - fmt.Println("read password ", err) + fmt.Println("read password err:", err) return "", err } password := string(bytePassword) - + fmt.Println() return password, nil } @@ -474,12 +475,12 @@ func getConfig(ctx *cli.Context) (string, string, string, error) { return config.RpcAddr, config.ChainId, config.Host, nil } -func loadKeyStoreFile(ctx *cli.Context) ([]byte, error) { +func loadKeyStoreFile(ctx *cli.Context) ([]byte, string, error) { keyfilepath := ctx.String("keystore") if keyfilepath == "" { homeDir, err := getHomeDir(ctx) if err != nil { - return nil, err + return nil, "", err } keyfilepath = filepath.Join(homeDir, DefaultKeyStorePath) } @@ -487,29 +488,10 @@ func loadKeyStoreFile(ctx *cli.Context) ([]byte, error) { // fetch private key from keystore content, err := os.ReadFile(keyfilepath) if err != nil { - return nil, fmt.Errorf("failed to read the keyfile at '%s': %v \n", keyfilepath, err) - } - - return content, nil -} - -func loadPassWordFile(ctx *cli.Context) (string, error) { - passwordFilepath := ctx.String(passwordFileFlag) - if passwordFilepath == "" { - homeDir, err := getHomeDir(ctx) - if err != nil { - return "", err - } - passwordFilepath = filepath.Join(homeDir, DefaultPasswordPath) - } - - // fetch password from password file - content, err := os.ReadFile(passwordFilepath) - if err != nil { - return "", fmt.Errorf("failed to read the password at '%s': %v \n", passwordFilepath, err) + return nil, "", fmt.Errorf("failed to read the keyfile at '%s': %v \n", keyfilepath, err) } - return string(content), nil + return content, keyfilepath, nil } func getHomeDir(ctx *cli.Context) (string, error) {