forked from TrueCloudLab/neoneo-go
Merge pull request #1434 from nspcc-dev/cli-wallet-usability
CLI wallet usability improvements
This commit is contained in:
commit
b1ca3c2c19
6 changed files with 129 additions and 48 deletions
|
@ -127,9 +127,14 @@ func (e *executor) GetTransaction(t *testing.T, h util.Uint256) (*transaction.Tr
|
||||||
return tx, height
|
return tx, height
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *executor) checkNextLine(t *testing.T, expected string) {
|
func (e *executor) getNextLine(t *testing.T) string {
|
||||||
line, err := e.Out.ReadString('\n')
|
line, err := e.Out.ReadString('\n')
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
return line
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *executor) checkNextLine(t *testing.T, expected string) {
|
||||||
|
line := e.getNextLine(t)
|
||||||
e.checkLine(t, line, expected)
|
e.checkLine(t, line, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,7 +74,7 @@ func TestSignMultisigTx(t *testing.T) {
|
||||||
e.In.WriteString("pass\r")
|
e.In.WriteString("pass\r")
|
||||||
e.Run(t, "neo-go", "wallet", "multisig", "sign",
|
e.Run(t, "neo-go", "wallet", "multisig", "sign",
|
||||||
"--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr,
|
"--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr,
|
||||||
"--wallet", wallet2Path, "--addr", multisigAddr,
|
"--wallet", wallet2Path, "--address", multisigAddr,
|
||||||
"--in", txPath, "--out", txPath)
|
"--in", txPath, "--out", txPath)
|
||||||
e.checkTxPersisted(t)
|
e.checkTxPersisted(t)
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
|
@ -18,16 +19,17 @@ import (
|
||||||
func TestNEP5Balance(t *testing.T) {
|
func TestNEP5Balance(t *testing.T) {
|
||||||
e := newExecutor(t, true)
|
e := newExecutor(t, true)
|
||||||
defer e.Close(t)
|
defer e.Close(t)
|
||||||
cmd := []string{
|
cmdbalance := []string{"neo-go", "wallet", "nep5", "balance"}
|
||||||
"neo-go", "wallet", "nep5", "balance",
|
cmdbase := append(cmdbalance,
|
||||||
"--rpc-endpoint", "http://" + e.RPC.Addr,
|
"--rpc-endpoint", "http://"+e.RPC.Addr,
|
||||||
"--wallet", validatorWallet,
|
"--wallet", validatorWallet,
|
||||||
"--addr", validatorAddr,
|
)
|
||||||
}
|
cmd := append(cmdbase, "--address", validatorAddr)
|
||||||
t.Run("NEO", func(t *testing.T) {
|
t.Run("NEO", func(t *testing.T) {
|
||||||
b, index := e.Chain.GetGoverningTokenBalance(validatorHash)
|
b, index := e.Chain.GetGoverningTokenBalance(validatorHash)
|
||||||
checkResult := func(t *testing.T) {
|
checkResult := func(t *testing.T) {
|
||||||
e.checkNextLine(t, "^\\s*TokenHash:\\s*"+e.Chain.GoverningTokenHash().StringLE())
|
e.checkNextLine(t, "^\\s*Account\\s+"+validatorAddr)
|
||||||
|
e.checkNextLine(t, "^\\s*NEO:\\s+NEO \\("+e.Chain.GoverningTokenHash().StringLE()+"\\)")
|
||||||
e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+b.String())
|
e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+b.String())
|
||||||
e.checkNextLine(t, "^\\s*Updated\\s*:\\s*"+strconv.FormatUint(uint64(index), 10))
|
e.checkNextLine(t, "^\\s*Updated\\s*:\\s*"+strconv.FormatUint(uint64(index), 10))
|
||||||
e.checkEOF(t)
|
e.checkEOF(t)
|
||||||
|
@ -43,12 +45,57 @@ func TestNEP5Balance(t *testing.T) {
|
||||||
})
|
})
|
||||||
t.Run("GAS", func(t *testing.T) {
|
t.Run("GAS", func(t *testing.T) {
|
||||||
e.Run(t, append(cmd, "--token", "gas")...)
|
e.Run(t, append(cmd, "--token", "gas")...)
|
||||||
e.checkNextLine(t, "^\\s*TokenHash:\\s*"+e.Chain.UtilityTokenHash().StringLE())
|
e.checkNextLine(t, "^\\s*Account\\s+"+validatorAddr)
|
||||||
|
e.checkNextLine(t, "^\\s*GAS:\\s+GAS \\("+e.Chain.UtilityTokenHash().StringLE()+"\\)")
|
||||||
b := e.Chain.GetUtilityTokenBalance(validatorHash)
|
b := e.Chain.GetUtilityTokenBalance(validatorHash)
|
||||||
e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+util.Fixed8(b.Int64()).String())
|
e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+util.Fixed8(b.Int64()).String())
|
||||||
})
|
})
|
||||||
t.Run("Invalid", func(t *testing.T) {
|
t.Run("all accounts", func(t *testing.T) {
|
||||||
e.RunWithError(t, append(cmd, "--token", "kek")...)
|
e.Run(t, cmdbase...)
|
||||||
|
addr1, err := address.StringToUint160("NbTiM6h8r99kpRtb428XcsUk1TzKed2gTc")
|
||||||
|
require.NoError(t, err)
|
||||||
|
e.checkNextLine(t, "^Account "+address.Uint160ToString(addr1))
|
||||||
|
e.checkNextLine(t, "^\\s*GAS:\\s+GAS \\("+e.Chain.UtilityTokenHash().StringLE()+"\\)")
|
||||||
|
balance := e.Chain.GetUtilityTokenBalance(addr1)
|
||||||
|
e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+util.Fixed8(balance.Int64()).String())
|
||||||
|
e.checkNextLine(t, "^\\s*Updated:")
|
||||||
|
e.checkNextLine(t, "^\\s*$")
|
||||||
|
|
||||||
|
addr2, err := address.StringToUint160("NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY")
|
||||||
|
require.NoError(t, err)
|
||||||
|
e.checkNextLine(t, "^Account "+address.Uint160ToString(addr2))
|
||||||
|
e.checkNextLine(t, "^\\s*$")
|
||||||
|
|
||||||
|
addr3, err := address.StringToUint160("NVNvVRW5Q5naSx2k2iZm7xRgtRNGuZppAK")
|
||||||
|
require.NoError(t, err)
|
||||||
|
e.checkNextLine(t, "^Account "+address.Uint160ToString(addr3))
|
||||||
|
// The order of assets is undefined.
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
line := e.getNextLine(t)
|
||||||
|
if strings.Contains(line, "GAS") {
|
||||||
|
e.checkLine(t, line, "^\\s*GAS:\\s+GAS \\("+e.Chain.UtilityTokenHash().StringLE()+"\\)")
|
||||||
|
balance = e.Chain.GetUtilityTokenBalance(addr3)
|
||||||
|
e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+util.Fixed8(balance.Int64()).String())
|
||||||
|
e.checkNextLine(t, "^\\s*Updated:")
|
||||||
|
} else {
|
||||||
|
balance, index := e.Chain.GetGoverningTokenBalance(validatorHash)
|
||||||
|
e.checkLine(t, line, "^\\s*NEO:\\s+NEO \\("+e.Chain.GoverningTokenHash().StringLE()+"\\)")
|
||||||
|
e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+balance.String())
|
||||||
|
e.checkNextLine(t, "^\\s*Updated\\s*:\\s*"+strconv.FormatUint(uint64(index), 10))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e.checkEOF(t)
|
||||||
|
})
|
||||||
|
t.Run("Bad token", func(t *testing.T) {
|
||||||
|
e.Run(t, append(cmd, "--token", "kek")...)
|
||||||
|
e.checkNextLine(t, "^\\s*Account\\s+"+validatorAddr)
|
||||||
|
e.checkEOF(t)
|
||||||
|
})
|
||||||
|
t.Run("Bad wallet", func(t *testing.T) {
|
||||||
|
e.RunWithError(t, append(cmdbalance, "--wallet", "/dev/null")...)
|
||||||
|
})
|
||||||
|
t.Run("Bad address", func(t *testing.T) {
|
||||||
|
e.RunWithError(t, append(cmdbalance, "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", validatorWallet, "--address", "xxx")...)
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ func newMultisigCommands() []cli.Command {
|
||||||
outFlag,
|
outFlag,
|
||||||
inFlag,
|
inFlag,
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "addr",
|
Name: "address, a",
|
||||||
Usage: "Address to use",
|
Usage: "Address to use",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ func newMultisigCommands() []cli.Command {
|
||||||
{
|
{
|
||||||
Name: "sign",
|
Name: "sign",
|
||||||
Usage: "sign a transaction",
|
Usage: "sign a transaction",
|
||||||
UsageText: "multisig sign --wallet <path> --addr <addr> --in <file.in> --out <file.out>",
|
UsageText: "multisig sign --wallet <path> --address <address> --in <file.in> --out <file.out>",
|
||||||
Action: signMultisig,
|
Action: signMultisig,
|
||||||
Flags: signFlags,
|
Flags: signFlags,
|
||||||
},
|
},
|
||||||
|
@ -45,7 +45,7 @@ func signMultisig(ctx *cli.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.NewExitError(err, 1)
|
return cli.NewExitError(err, 1)
|
||||||
}
|
}
|
||||||
addr := ctx.String("addr")
|
addr := ctx.String("address")
|
||||||
sh, err := address.StringToUint160(addr)
|
sh, err := address.StringToUint160(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.NewExitError(fmt.Errorf("invalid address: %w", err), 1)
|
return cli.NewExitError(fmt.Errorf("invalid address: %w", err), 1)
|
||||||
|
|
|
@ -28,7 +28,7 @@ var (
|
||||||
var (
|
var (
|
||||||
tokenFlag = cli.StringFlag{
|
tokenFlag = cli.StringFlag{
|
||||||
Name: "token",
|
Name: "token",
|
||||||
Usage: "Token to use",
|
Usage: "Token to use (hash or name (for NEO/GAS or imported tokens))",
|
||||||
}
|
}
|
||||||
gasFlag = flags.Fixed8Flag{
|
gasFlag = flags.Fixed8Flag{
|
||||||
Name: "gas",
|
Name: "gas",
|
||||||
|
@ -41,7 +41,7 @@ func newNEP5Commands() []cli.Command {
|
||||||
walletPathFlag,
|
walletPathFlag,
|
||||||
tokenFlag,
|
tokenFlag,
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "addr",
|
Name: "address, a",
|
||||||
Usage: "Address to use",
|
Usage: "Address to use",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,7 @@ func newNEP5Commands() []cli.Command {
|
||||||
{
|
{
|
||||||
Name: "balance",
|
Name: "balance",
|
||||||
Usage: "get address balance",
|
Usage: "get address balance",
|
||||||
UsageText: "balance --wallet <path> --rpc-endpoint <node> --timeout <time> --addr <addr> [--token <hash-or-name>]",
|
UsageText: "balance --wallet <path> --rpc-endpoint <node> [--timeout <time>] [--address <address>] [--token <hash-or-name>]",
|
||||||
Action: getNEP5Balance,
|
Action: getNEP5Balance,
|
||||||
Flags: balanceFlags,
|
Flags: balanceFlags,
|
||||||
},
|
},
|
||||||
|
@ -135,20 +135,30 @@ func newNEP5Commands() []cli.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNEP5Balance(ctx *cli.Context) error {
|
func getNEP5Balance(ctx *cli.Context) error {
|
||||||
|
var accounts []*wallet.Account
|
||||||
|
|
||||||
wall, err := openWallet(ctx.String("wallet"))
|
wall, err := openWallet(ctx.String("wallet"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.NewExitError(err, 1)
|
return cli.NewExitError(fmt.Errorf("bad wallet: %w", err), 1)
|
||||||
}
|
}
|
||||||
defer wall.Close()
|
defer wall.Close()
|
||||||
|
|
||||||
addr := ctx.String("addr")
|
addr := ctx.String("address")
|
||||||
addrHash, err := address.StringToUint160(addr)
|
if addr != "" {
|
||||||
if err != nil {
|
addrHash, err := address.StringToUint160(addr)
|
||||||
return cli.NewExitError(fmt.Errorf("invalid address: %w", err), 1)
|
if err != nil {
|
||||||
}
|
return cli.NewExitError(fmt.Errorf("invalid address: %w", err), 1)
|
||||||
acc := wall.GetAccount(addrHash)
|
}
|
||||||
if acc == nil {
|
acc := wall.GetAccount(addrHash)
|
||||||
return cli.NewExitError(fmt.Errorf("can't find account for the address: %s", addr), 1)
|
if acc == nil {
|
||||||
|
return cli.NewExitError(fmt.Errorf("can't find account for the address: %s", addr), 1)
|
||||||
|
}
|
||||||
|
accounts = append(accounts, acc)
|
||||||
|
} else {
|
||||||
|
if len(wall.Accounts) == 0 {
|
||||||
|
return cli.NewExitError(errors.New("no accounts in the wallet"), 1)
|
||||||
|
}
|
||||||
|
accounts = wall.Accounts
|
||||||
}
|
}
|
||||||
|
|
||||||
gctx, cancel := options.GetTimeoutContext(ctx)
|
gctx, cancel := options.GetTimeoutContext(ctx)
|
||||||
|
@ -159,40 +169,56 @@ func getNEP5Balance(ctx *cli.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var token *wallet.Token
|
|
||||||
name := ctx.String("token")
|
name := ctx.String("token")
|
||||||
if name != "" {
|
|
||||||
token, err = getMatchingToken(ctx, wall, name)
|
for k, acc := range accounts {
|
||||||
|
addrHash, err := address.StringToUint160(acc.Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
token, err = getMatchingTokenRPC(ctx, c, addrHash, name)
|
return cli.NewExitError(fmt.Errorf("invalid account address: %w", err), 1)
|
||||||
|
}
|
||||||
|
balances, err := c.GetNEP5Balances(addrHash)
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if k != 0 {
|
||||||
|
fmt.Fprintln(ctx.App.Writer)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(ctx.App.Writer, "Account %s\n", acc.Address)
|
||||||
|
|
||||||
|
for i := range balances.Balances {
|
||||||
|
var tokenName, tokenSymbol string
|
||||||
|
|
||||||
|
asset := balances.Balances[i].Asset
|
||||||
|
token, err := getMatchingToken(ctx, wall, asset.StringLE())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.NewExitError(err, 1)
|
token, err = c.NEP5TokenInfo(asset)
|
||||||
}
|
}
|
||||||
|
if err == nil {
|
||||||
|
if name != "" && !(token.Name == name || token.Symbol == name || token.Address() == name || token.Hash.StringLE() == name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tokenName = token.Name
|
||||||
|
tokenSymbol = token.Symbol
|
||||||
|
} else {
|
||||||
|
if name != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tokenSymbol = "UNKNOWN"
|
||||||
|
}
|
||||||
|
fmt.Fprintf(ctx.App.Writer, "%s: %s (%s)\n", strings.ToUpper(tokenSymbol), tokenName, asset.StringLE())
|
||||||
|
fmt.Fprintf(ctx.App.Writer, "\tAmount : %s\n", balances.Balances[i].Amount)
|
||||||
|
fmt.Fprintf(ctx.App.Writer, "\tUpdated: %d\n", balances.Balances[i].LastUpdated)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
balances, err := c.GetNEP5Balances(addrHash)
|
|
||||||
if err != nil {
|
|
||||||
return cli.NewExitError(err, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range balances.Balances {
|
|
||||||
asset := balances.Balances[i].Asset
|
|
||||||
if name != "" && !token.Hash.Equals(asset) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fmt.Fprintf(ctx.App.Writer, "TokenHash: %s\n", asset.StringLE())
|
|
||||||
fmt.Fprintf(ctx.App.Writer, "\tAmount : %s\n", balances.Balances[i].Amount)
|
|
||||||
fmt.Fprintf(ctx.App.Writer, "\tUpdated: %d\n", balances.Balances[i].LastUpdated)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMatchingToken(ctx *cli.Context, w *wallet.Wallet, name string) (*wallet.Token, error) {
|
func getMatchingToken(ctx *cli.Context, w *wallet.Wallet, name string) (*wallet.Token, error) {
|
||||||
switch strings.ToLower(name) {
|
switch strings.ToLower(name) {
|
||||||
case "neo":
|
case "neo", client.NeoContractHash.StringLE():
|
||||||
return neoToken, nil
|
return neoToken, nil
|
||||||
case "gas":
|
case "gas", client.GasContractHash.StringLE():
|
||||||
return gasToken, nil
|
return gasToken, nil
|
||||||
}
|
}
|
||||||
return getMatchingTokenAux(ctx, func(i int) *wallet.Token {
|
return getMatchingTokenAux(ctx, func(i int) *wallet.Token {
|
||||||
|
|
|
@ -446,6 +446,9 @@ func (c *Client) SignAndPushInvocationTx(script []byte, acc *wallet.Account, sys
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
tx, err := c.CreateTxFromScript(script, acc, sysfee, int64(netfee), cosigners...)
|
tx, err := c.CreateTxFromScript(script, acc, sysfee, int64(netfee), cosigners...)
|
||||||
|
if err != nil {
|
||||||
|
return txHash, fmt.Errorf("failed to create tx: %w", err)
|
||||||
|
}
|
||||||
if err = acc.SignTx(tx); err != nil {
|
if err = acc.SignTx(tx); err != nil {
|
||||||
return txHash, fmt.Errorf("failed to sign tx: %w", err)
|
return txHash, fmt.Errorf("failed to sign tx: %w", err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue