forked from TrueCloudLab/neoneo-go
cli/wallet: modernize nep11 balances command, unify with nep17
Make NEP-11 code use getnep11balances the same way NEP-17 code uses getnep17balances. This command was introduced well before getnep11balances appeared, so it required always specifying contract explicitly. Now this constraint can be relaxed somewhat in most cases.
This commit is contained in:
parent
7cfcf072b8
commit
1da4b333f6
4 changed files with 175 additions and 156 deletions
|
@ -167,19 +167,35 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
||||||
"--rpc-endpoint", "http://" + e.RPC.Addr,
|
"--rpc-endpoint", "http://" + e.RPC.Addr,
|
||||||
"--wallet", wall,
|
"--wallet", wall,
|
||||||
"--address", nftOwnerAddr}
|
"--address", nftOwnerAddr}
|
||||||
checkBalanceResult := func(t *testing.T, acc string, amount string) {
|
checkBalanceResult := func(t *testing.T, acc string, ids ...[]byte) {
|
||||||
e.checkNextLine(t, "^\\s*Account\\s+"+acc)
|
e.checkNextLine(t, "^\\s*Account\\s+"+acc)
|
||||||
e.checkNextLine(t, "^\\s*HASHY:\\s+HASHY NFT \\("+h.StringLE()+"\\)")
|
e.checkNextLine(t, "^\\s*HASHY:\\s+HASHY NFT \\("+h.StringLE()+"\\)")
|
||||||
e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+amount+"$")
|
|
||||||
|
// Hashes can be ordered in any way, so make a regexp for them.
|
||||||
|
var tokstring = "("
|
||||||
|
for i, id := range ids {
|
||||||
|
if i > 0 {
|
||||||
|
tokstring += "|"
|
||||||
|
}
|
||||||
|
tokstring += hex.EncodeToString(id)
|
||||||
|
}
|
||||||
|
tokstring += ")"
|
||||||
|
|
||||||
|
for range ids {
|
||||||
|
e.checkNextLine(t, "^\\s*Token: "+tokstring+"\\s*$")
|
||||||
|
e.checkNextLine(t, "^\\s*Amount: 1\\s*$")
|
||||||
|
e.checkNextLine(t, "^\\s*Updated: [0-9]+\\s*$")
|
||||||
|
}
|
||||||
e.checkEOF(t)
|
e.checkEOF(t)
|
||||||
}
|
}
|
||||||
// balance check: by symbol, token is not imported
|
// balance check: by symbol, token is not imported
|
||||||
e.RunWithError(t, append(cmdCheckBalance, "--token", "HASHY")...)
|
e.Run(t, append(cmdCheckBalance, "--token", "HASHY")...)
|
||||||
|
checkBalanceResult(t, nftOwnerAddr, tokenID)
|
||||||
// balance check: excessive parameters
|
// balance check: excessive parameters
|
||||||
e.RunWithError(t, append(cmdCheckBalance, "--token", h.StringLE(), "neo-go")...)
|
e.RunWithError(t, append(cmdCheckBalance, "--token", h.StringLE(), "neo-go")...)
|
||||||
// balance check: by hash, ok
|
// balance check: by hash, ok
|
||||||
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
||||||
checkBalanceResult(t, nftOwnerAddr, "1")
|
checkBalanceResult(t, nftOwnerAddr, tokenID)
|
||||||
|
|
||||||
// import token
|
// import token
|
||||||
e.Run(t, "neo-go", "wallet", "nep11", "import",
|
e.Run(t, "neo-go", "wallet", "nep11", "import",
|
||||||
|
@ -189,14 +205,14 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
||||||
|
|
||||||
// balance check: by symbol, ok
|
// balance check: by symbol, ok
|
||||||
e.Run(t, append(cmdCheckBalance, "--token", "HASHY")...)
|
e.Run(t, append(cmdCheckBalance, "--token", "HASHY")...)
|
||||||
checkBalanceResult(t, nftOwnerAddr, "1")
|
checkBalanceResult(t, nftOwnerAddr, tokenID)
|
||||||
|
|
||||||
// balance check: all accounts
|
// balance check: all accounts
|
||||||
e.Run(t, "neo-go", "wallet", "nep11", "balance",
|
e.Run(t, "neo-go", "wallet", "nep11", "balance",
|
||||||
"--rpc-endpoint", "http://"+e.RPC.Addr,
|
"--rpc-endpoint", "http://"+e.RPC.Addr,
|
||||||
"--wallet", wall,
|
"--wallet", wall,
|
||||||
"--token", h.StringLE())
|
"--token", h.StringLE())
|
||||||
checkBalanceResult(t, nftOwnerAddr, "1")
|
checkBalanceResult(t, nftOwnerAddr, tokenID)
|
||||||
|
|
||||||
// remove token from wallet
|
// remove token from wallet
|
||||||
e.In.WriteString("y\r")
|
e.In.WriteString("y\r")
|
||||||
|
@ -276,7 +292,7 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
||||||
|
|
||||||
// balance check: several tokens, ok
|
// balance check: several tokens, ok
|
||||||
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
||||||
checkBalanceResult(t, nftOwnerAddr, "2")
|
checkBalanceResult(t, nftOwnerAddr, tokenID, tokenID1)
|
||||||
|
|
||||||
cmdTransfer := []string{
|
cmdTransfer := []string{
|
||||||
"neo-go", "wallet", "nep11", "transfer",
|
"neo-go", "wallet", "nep11", "transfer",
|
||||||
|
@ -304,7 +320,7 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
||||||
|
|
||||||
// check balance after transfer
|
// check balance after transfer
|
||||||
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
||||||
checkBalanceResult(t, nftOwnerAddr, "1") // tokenID1
|
checkBalanceResult(t, nftOwnerAddr, tokenID1)
|
||||||
|
|
||||||
// transfer: good, to NEP-11-Payable contract, with data
|
// transfer: good, to NEP-11-Payable contract, with data
|
||||||
verifyH := deployVerifyContract(t, e)
|
verifyH := deployVerifyContract(t, e)
|
||||||
|
@ -341,7 +357,7 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
||||||
|
|
||||||
// check balance after transfer
|
// check balance after transfer
|
||||||
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
||||||
checkBalanceResult(t, nftOwnerAddr, "0")
|
checkBalanceResult(t, nftOwnerAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
||||||
|
@ -406,31 +422,42 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
||||||
require.Equal(t, base64.StdEncoding.EncodeToString(object1ID.BytesBE()), props["objectID"])
|
require.Equal(t, base64.StdEncoding.EncodeToString(object1ID.BytesBE()), props["objectID"])
|
||||||
e.checkEOF(t)
|
e.checkEOF(t)
|
||||||
|
|
||||||
|
type idAmount struct {
|
||||||
|
id string
|
||||||
|
amount string
|
||||||
|
}
|
||||||
|
|
||||||
// check the balance
|
// check the balance
|
||||||
cmdCheckBalance := []string{"neo-go", "wallet", "nep11", "balance",
|
cmdCheckBalance := []string{"neo-go", "wallet", "nep11", "balance",
|
||||||
"--rpc-endpoint", "http://" + e.RPC.Addr,
|
"--rpc-endpoint", "http://" + e.RPC.Addr,
|
||||||
"--wallet", wall,
|
"--wallet", wall,
|
||||||
"--address", validatorAddr}
|
"--address", validatorAddr}
|
||||||
checkBalanceResult := func(t *testing.T, acc string, amount string, id []byte) {
|
checkBalanceResult := func(t *testing.T, acc string, objs ...idAmount) {
|
||||||
e.checkNextLine(t, "^\\s*Account\\s+"+acc)
|
e.checkNextLine(t, "^\\s*Account\\s+"+acc)
|
||||||
if id == nil {
|
e.checkNextLine(t, "^\\s*NFSO:\\s+NeoFS Object NFT \\("+h.StringLE()+"\\)")
|
||||||
e.checkNextLine(t, "^\\s*NFSO:\\s+NeoFS Object NFT \\("+h.StringLE()+"\\)")
|
|
||||||
} else {
|
for _, o := range objs {
|
||||||
e.checkNextLine(t, "^\\s*NFSO:\\s+NeoFS Object NFT \\("+h.StringLE()+", "+hex.EncodeToString(id)+"\\)")
|
e.checkNextLine(t, "^\\s*Token: "+o.id+"\\s*$")
|
||||||
|
e.checkNextLine(t, "^\\s*Amount: "+o.amount+"\\s*$")
|
||||||
|
e.checkNextLine(t, "^\\s*Updated: [0-9]+\\s*$")
|
||||||
}
|
}
|
||||||
e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+amount+"$")
|
|
||||||
e.checkEOF(t)
|
e.checkEOF(t)
|
||||||
}
|
}
|
||||||
|
tokz := []idAmount{
|
||||||
|
{hex.EncodeToString(token1ID), "1"},
|
||||||
|
{hex.EncodeToString(token2ID), "1"},
|
||||||
|
}
|
||||||
// balance check: by symbol, token is not imported
|
// balance check: by symbol, token is not imported
|
||||||
e.RunWithError(t, append(cmdCheckBalance, "--token", "NFSO")...)
|
e.Run(t, append(cmdCheckBalance, "--token", "NFSO")...)
|
||||||
|
checkBalanceResult(t, validatorAddr, tokz...)
|
||||||
|
|
||||||
// overall NFSO balance check: by hash, ok
|
// overall NFSO balance check: by hash, ok
|
||||||
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
||||||
checkBalanceResult(t, validatorAddr, "2", nil)
|
checkBalanceResult(t, validatorAddr, tokz...)
|
||||||
|
|
||||||
// particular NFSO balance check: by hash, ok
|
// particular NFSO balance check: by hash, ok
|
||||||
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE(), "--id", hex.EncodeToString(token2ID))...)
|
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE(), "--id", hex.EncodeToString(token2ID))...)
|
||||||
checkBalanceResult(t, validatorAddr, "1", token2ID)
|
checkBalanceResult(t, validatorAddr, tokz[1])
|
||||||
|
|
||||||
// import token
|
// import token
|
||||||
e.Run(t, "neo-go", "wallet", "nep11", "import",
|
e.Run(t, "neo-go", "wallet", "nep11", "import",
|
||||||
|
@ -440,11 +467,11 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
||||||
|
|
||||||
// overall balance check: by symbol, ok
|
// overall balance check: by symbol, ok
|
||||||
e.Run(t, append(cmdCheckBalance, "--token", "NFSO")...)
|
e.Run(t, append(cmdCheckBalance, "--token", "NFSO")...)
|
||||||
checkBalanceResult(t, validatorAddr, "2", nil)
|
checkBalanceResult(t, validatorAddr, tokz...)
|
||||||
|
|
||||||
// particular balance check: by symbol, ok
|
// particular balance check: by symbol, ok
|
||||||
e.Run(t, append(cmdCheckBalance, "--token", "NFSO", "--id", hex.EncodeToString(token1ID))...)
|
e.Run(t, append(cmdCheckBalance, "--token", "NFSO", "--id", hex.EncodeToString(token1ID))...)
|
||||||
checkBalanceResult(t, validatorAddr, "1", token1ID)
|
checkBalanceResult(t, validatorAddr, tokz[0])
|
||||||
|
|
||||||
// remove token from wallet
|
// remove token from wallet
|
||||||
e.In.WriteString("y\r")
|
e.In.WriteString("y\r")
|
||||||
|
@ -531,7 +558,7 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
||||||
|
|
||||||
// balance check: several tokens, ok
|
// balance check: several tokens, ok
|
||||||
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
||||||
checkBalanceResult(t, validatorAddr, "2", nil)
|
checkBalanceResult(t, validatorAddr, tokz...)
|
||||||
|
|
||||||
cmdTransfer := []string{
|
cmdTransfer := []string{
|
||||||
"neo-go", "wallet", "nep11", "transfer",
|
"neo-go", "wallet", "nep11", "transfer",
|
||||||
|
@ -559,7 +586,7 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
||||||
|
|
||||||
// check balance after transfer
|
// check balance after transfer
|
||||||
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
||||||
checkBalanceResult(t, validatorAddr, "1", nil) // only token2ID expected to be on the balance
|
checkBalanceResult(t, validatorAddr, tokz[1]) // only token2ID expected to be on the balance
|
||||||
|
|
||||||
// transfer: good, 1/4 of the balance, to NEP-11-Payable contract, with data
|
// transfer: good, 1/4 of the balance, to NEP-11-Payable contract, with data
|
||||||
verifyH := deployVerifyContract(t, e)
|
verifyH := deployVerifyContract(t, e)
|
||||||
|
@ -597,7 +624,8 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
||||||
|
|
||||||
// check balance after transfer
|
// check balance after transfer
|
||||||
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
||||||
checkBalanceResult(t, validatorAddr, "0.75", nil)
|
tokz[1].amount = "0.75"
|
||||||
|
checkBalanceResult(t, validatorAddr, tokz[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
func deployNFSContract(t *testing.T, e *executor) util.Uint160 {
|
func deployNFSContract(t *testing.T, e *executor) util.Uint160 {
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
||||||
|
@ -12,10 +11,12 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/cli/options"
|
"github.com/nspcc-dev/neo-go/cli/options"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep11"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep11"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
|
@ -48,9 +49,24 @@ func newNEP11Commands() []cli.Command {
|
||||||
{
|
{
|
||||||
Name: "balance",
|
Name: "balance",
|
||||||
Usage: "get address balance",
|
Usage: "get address balance",
|
||||||
UsageText: "balance -w wallet [--wallet-config path] --rpc-endpoint <node> [--timeout <time>] [--address <address>] --token <hash-or-name> [--id <token-id>]",
|
UsageText: "balance -w wallet [--wallet-config path] --rpc-endpoint <node> [--timeout <time>] [--address <address>] [--token <hash-or-name>] [--id <token-id>]",
|
||||||
Action: getNEP11Balance,
|
Description: `Prints NEP-11 balances for address and assets/IDs specified. By default (no
|
||||||
Flags: balanceFlags,
|
address or token parameter) all tokens (NFT contracts) for all accounts in
|
||||||
|
the specified wallet are listed with all tokens (actual NFTs) insied. A
|
||||||
|
single account can be chosen with the address option and/or a single NFT
|
||||||
|
contract can be selected with the token option. Further, you can specify a
|
||||||
|
particular NFT ID (hex-encoded) to display (which is mostly useful for
|
||||||
|
divisible NFTs). Tokens can be specified by hash, address, name or symbol.
|
||||||
|
Hashes and addresses always work (as long as they belong to a correct NEP-11
|
||||||
|
contract), while names or symbols are matched against the token data
|
||||||
|
stored in the wallet (see import command) or balance data returned from the
|
||||||
|
server. If the token is not specified directly (with hash/address) and is
|
||||||
|
not found in the wallet then depending on the balances data from the server
|
||||||
|
this command can print no data at all or print multiple tokens for one
|
||||||
|
account (if they use the same names/symbols).
|
||||||
|
`,
|
||||||
|
Action: getNEP11Balance,
|
||||||
|
Flags: balanceFlags,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "import",
|
Name: "import",
|
||||||
|
@ -162,95 +178,48 @@ func removeNEP11Token(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNEP11Balance(ctx *cli.Context) error {
|
func getNEP11Balance(ctx *cli.Context) error {
|
||||||
var accounts []*wallet.Account
|
return getNEPBalance(ctx, manifest.NEP11StandardName, func(ctx *cli.Context, c *rpcclient.Client, addrHash util.Uint160, name string, token *wallet.Token, nftID string) error {
|
||||||
|
balances, err := c.GetNEP11Balances(addrHash)
|
||||||
if err := cmdargs.EnsureNone(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
wall, _, err := readWallet(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return cli.NewExitError(fmt.Errorf("bad wallet: %w", err), 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
addrFlag := ctx.Generic("address").(*flags.Address)
|
|
||||||
if addrFlag.IsSet {
|
|
||||||
addrHash := addrFlag.Uint160()
|
|
||||||
acc := wall.GetAccount(addrHash)
|
|
||||||
if acc == nil {
|
|
||||||
return cli.NewExitError(fmt.Errorf("can't find account for the address: %s", address.Uint160ToString(addrHash)), 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)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
c, err := options.GetRPCClient(gctx, ctx)
|
|
||||||
if err != nil {
|
|
||||||
return cli.NewExitError(err, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
name := ctx.String("token")
|
|
||||||
if name == "" {
|
|
||||||
return cli.NewExitError("token hash or name should be specified", 1)
|
|
||||||
}
|
|
||||||
token, err := getMatchingToken(ctx, wall, name, manifest.NEP11StandardName)
|
|
||||||
if err != nil {
|
|
||||||
tokenHash, err := flags.ParseAddress(name)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.NewExitError(fmt.Errorf("can't fetch matching token from RPC-node: %w", err), 1)
|
return err
|
||||||
}
|
}
|
||||||
token, err = getTokenWithStandard(c, tokenHash, manifest.NEP11StandardName)
|
var tokenFound bool
|
||||||
if err != nil {
|
for i := range balances.Balances {
|
||||||
return cli.NewExitError(err.Error(), 1)
|
curToken := tokenFromNEP11Balance(&balances.Balances[i])
|
||||||
|
if tokenMatch(curToken, token, name) {
|
||||||
|
printNFTBalance(ctx, balances.Balances[i], nftID)
|
||||||
|
tokenFound = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
if name == "" || tokenFound {
|
||||||
// Always initialize divisible token to be able to use both balanceOf methods.
|
return nil
|
||||||
n11 := nep11.NewDivisibleReader(invoker.New(c, nil), token.Hash)
|
|
||||||
|
|
||||||
tokenID := ctx.String("id")
|
|
||||||
tokenIDBytes, err := hex.DecodeString(tokenID)
|
|
||||||
if err != nil {
|
|
||||||
return cli.NewExitError(fmt.Errorf("invalid tokenID bytes: %w", err), 1)
|
|
||||||
}
|
|
||||||
for k, acc := range accounts {
|
|
||||||
addrHash, err := address.StringToUint160(acc.Address)
|
|
||||||
if err != nil {
|
|
||||||
return cli.NewExitError(fmt.Errorf("invalid account address: %w", err), 1)
|
|
||||||
}
|
}
|
||||||
|
if token != nil {
|
||||||
if k != 0 {
|
// We have an exact token, but there is no balance data for it -> print without NFTs.
|
||||||
fmt.Fprintln(ctx.App.Writer)
|
printNFTBalance(ctx, result.NEP11AssetBalance{
|
||||||
}
|
Asset: token.Hash,
|
||||||
fmt.Fprintf(ctx.App.Writer, "Account %s\n", acc.Address)
|
Decimals: int(token.Decimals),
|
||||||
|
Name: token.Name,
|
||||||
var amount *big.Int
|
Symbol: token.Symbol,
|
||||||
if len(tokenIDBytes) == 0 {
|
}, "")
|
||||||
amount, err = n11.BalanceOf(addrHash)
|
|
||||||
} else {
|
} else {
|
||||||
amount, err = n11.BalanceOfD(addrHash, tokenIDBytes)
|
// We have no data for this token at all, maybe it's not even correct -> complain.
|
||||||
|
fmt.Fprintf(ctx.App.Writer, "Can't find data for %q token\n", name)
|
||||||
}
|
}
|
||||||
if err != nil {
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func printNFTBalance(ctx *cli.Context, balance result.NEP11AssetBalance, nftID string) {
|
||||||
|
fmt.Fprintf(ctx.App.Writer, "%s: %s (%s)\n", balance.Symbol, balance.Name, balance.Asset.StringLE())
|
||||||
|
for _, tok := range balance.Tokens {
|
||||||
|
if len(nftID) > 0 && nftID != tok.ID {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
amountStr := fixedn.ToString(amount, int(token.Decimals))
|
fmt.Fprintf(ctx.App.Writer, "\tToken: %s\n", tok.ID)
|
||||||
|
fmt.Fprintf(ctx.App.Writer, "\t\tAmount: %s\n", decimalAmount(tok.Amount, balance.Decimals))
|
||||||
format := "%s: %s (%s)\n"
|
fmt.Fprintf(ctx.App.Writer, "\t\tUpdated: %d\n", tok.LastUpdated)
|
||||||
formatArgs := []interface{}{token.Symbol, token.Name, token.Hash.StringLE()}
|
|
||||||
if len(tokenIDBytes) != 0 {
|
|
||||||
format = "%s: %s (%s, %s)\n"
|
|
||||||
formatArgs = append(formatArgs, tokenID)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(ctx.App.Writer, format, formatArgs...)
|
|
||||||
fmt.Fprintf(ctx.App.Writer, "\tAmount : %s\n", amountStr)
|
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func transferNEP11(ctx *cli.Context) error {
|
func transferNEP11(ctx *cli.Context) error {
|
||||||
|
|
|
@ -170,7 +170,49 @@ func newNEP17Commands() []cli.Command {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tokenMatch(curToken *wallet.Token, expToken *wallet.Token, name string) bool {
|
||||||
|
return name == "" || // No specification at all, everything matches.
|
||||||
|
(expToken != nil && expToken.Hash == curToken.Hash) || // Exact token specification, matches perfectly.
|
||||||
|
(expToken == nil && name != "" && (curToken.Name == name || curToken.Symbol == name)) // Loose (named non-native) token specification, best-effort.
|
||||||
|
}
|
||||||
|
|
||||||
func getNEP17Balance(ctx *cli.Context) error {
|
func getNEP17Balance(ctx *cli.Context) error {
|
||||||
|
return getNEPBalance(ctx, manifest.NEP17StandardName, func(ctx *cli.Context, c *rpcclient.Client, addrHash util.Uint160, name string, token *wallet.Token, _ string) error {
|
||||||
|
balances, err := c.GetNEP17Balances(addrHash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var tokenFound bool
|
||||||
|
for i := range balances.Balances {
|
||||||
|
curToken := tokenFromNEP17Balance(&balances.Balances[i])
|
||||||
|
if tokenMatch(curToken, token, name) {
|
||||||
|
printAssetBalance(ctx, balances.Balances[i])
|
||||||
|
tokenFound = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if name == "" || tokenFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if token != nil {
|
||||||
|
// We have an exact token, but there is no balance data for it -> print 0.
|
||||||
|
printAssetBalance(ctx, result.NEP17Balance{
|
||||||
|
Asset: token.Hash,
|
||||||
|
Amount: "0",
|
||||||
|
Decimals: int(token.Decimals),
|
||||||
|
LastUpdated: 0,
|
||||||
|
Name: token.Name,
|
||||||
|
Symbol: token.Symbol,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// We have no data for this token at all, maybe it's not even correct -> complain.
|
||||||
|
fmt.Fprintf(ctx.App.Writer, "Can't find data for %q token\n", name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNEPBalance(ctx *cli.Context, standard string, accHandler func(*cli.Context, *rpcclient.Client, util.Uint160, string, *wallet.Token, string) error) error {
|
||||||
var accounts []*wallet.Account
|
var accounts []*wallet.Account
|
||||||
|
|
||||||
if err := cmdargs.EnsureNone(ctx); err != nil {
|
if err := cmdargs.EnsureNone(ctx); err != nil {
|
||||||
|
@ -209,14 +251,14 @@ func getNEP17Balance(ctx *cli.Context) error {
|
||||||
|
|
||||||
if name != "" {
|
if name != "" {
|
||||||
// Token was explicitly specified, let's try finding it, search in the wallet first.
|
// Token was explicitly specified, let's try finding it, search in the wallet first.
|
||||||
token, err = getMatchingToken(ctx, wall, name, manifest.NEP17StandardName)
|
token, err = getMatchingToken(ctx, wall, name, standard)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var h util.Uint160
|
var h util.Uint160
|
||||||
|
|
||||||
// Well-known hardcoded names/symbols.
|
// Well-known hardcoded names/symbols.
|
||||||
if name == nativenames.Neo || name == "NEO" {
|
if standard == manifest.NEP17StandardName && (name == nativenames.Neo || name == "NEO") {
|
||||||
h = neo.Hash
|
h = neo.Hash
|
||||||
} else if name == nativenames.Gas || name == "GAS" {
|
} else if standard == manifest.NEP17StandardName && (name == nativenames.Gas || name == "GAS") {
|
||||||
h = gas.Hash
|
h = gas.Hash
|
||||||
} else {
|
} else {
|
||||||
// The last resort, maybe it's a direct hash or address.
|
// The last resort, maybe it's a direct hash or address.
|
||||||
|
@ -227,74 +269,54 @@ func getNEP17Balance(ctx *cli.Context) error {
|
||||||
// in balances.
|
// in balances.
|
||||||
if !h.Equals(util.Uint160{}) {
|
if !h.Equals(util.Uint160{}) {
|
||||||
// But if we have an exact hash, it must be correct.
|
// But if we have an exact hash, it must be correct.
|
||||||
token, err = getTokenWithStandard(c, h, manifest.NEP17StandardName)
|
token, err = getTokenWithStandard(c, h, standard)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.NewExitError(fmt.Errorf("%q is not a valid NEP-17 token: %w", name, err), 1)
|
return cli.NewExitError(fmt.Errorf("%q is not a valid NEP-17 token: %w", name, err), 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
tokenID := ctx.String("id")
|
||||||
|
if standard == manifest.NEP11StandardName {
|
||||||
|
if len(tokenID) > 0 {
|
||||||
|
_, err = hex.DecodeString(tokenID)
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(fmt.Errorf("invalid token ID: %w", err), 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
for k, acc := range accounts {
|
for k, acc := range accounts {
|
||||||
addrHash, err := address.StringToUint160(acc.Address)
|
addrHash, err := address.StringToUint160(acc.Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.NewExitError(fmt.Errorf("invalid account address: %w", err), 1)
|
return cli.NewExitError(fmt.Errorf("invalid account address: %w", err), 1)
|
||||||
}
|
}
|
||||||
// We can't use nep17.BalanceOf() even if the token is known because of LastUpdated.
|
|
||||||
balances, err := c.GetNEP17Balances(addrHash)
|
|
||||||
if err != nil {
|
|
||||||
return cli.NewExitError(err, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if k != 0 {
|
if k != 0 {
|
||||||
fmt.Fprintln(ctx.App.Writer)
|
fmt.Fprintln(ctx.App.Writer)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(ctx.App.Writer, "Account %s\n", acc.Address)
|
fmt.Fprintf(ctx.App.Writer, "Account %s\n", acc.Address)
|
||||||
|
|
||||||
var tokenFound bool
|
err = accHandler(ctx, c, addrHash, name, token, tokenID)
|
||||||
for i := range balances.Balances {
|
if err != nil {
|
||||||
curToken := tokenFromNEP17Balance(&balances.Balances[i])
|
return cli.NewExitError(err, 1)
|
||||||
if token != nil && token.Hash != curToken.Hash {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Loose (named non-native) token specification, try our best.
|
|
||||||
if name != "" && !(curToken.Name == name || curToken.Symbol == name || curToken.Address() == name || curToken.Hash.StringLE() == name) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
printAssetBalance(ctx, balances.Balances[i])
|
|
||||||
tokenFound = true
|
|
||||||
}
|
|
||||||
if name == "" || tokenFound {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if token != nil {
|
|
||||||
// We have an exact token, but there is no balance data for it -> print 0.
|
|
||||||
printAssetBalance(ctx, result.NEP17Balance{
|
|
||||||
Asset: token.Hash,
|
|
||||||
Amount: "0",
|
|
||||||
Decimals: int(token.Decimals),
|
|
||||||
LastUpdated: 0,
|
|
||||||
Name: token.Name,
|
|
||||||
Symbol: token.Symbol,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// We have no data for this token at all, maybe it's not even correct -> complain.
|
|
||||||
fmt.Fprintf(ctx.App.Writer, "Can't find data for %q token\n", name)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func printAssetBalance(ctx *cli.Context, balance result.NEP17Balance) {
|
func decimalAmount(amount string, decimals int) string {
|
||||||
fmt.Fprintf(ctx.App.Writer, "%s: %s (%s)\n", balance.Symbol, balance.Name, balance.Asset.StringLE())
|
if decimals != 0 {
|
||||||
amount := balance.Amount
|
|
||||||
if balance.Decimals != 0 {
|
|
||||||
b, ok := new(big.Int).SetString(amount, 10)
|
b, ok := new(big.Int).SetString(amount, 10)
|
||||||
if ok {
|
if ok {
|
||||||
amount = fixedn.ToString(b, balance.Decimals)
|
amount = fixedn.ToString(b, decimals)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Fprintf(ctx.App.Writer, "\tAmount : %s\n", amount)
|
return amount
|
||||||
|
}
|
||||||
|
|
||||||
|
func printAssetBalance(ctx *cli.Context, balance result.NEP17Balance) {
|
||||||
|
fmt.Fprintf(ctx.App.Writer, "%s: %s (%s)\n", balance.Symbol, balance.Name, balance.Asset.StringLE())
|
||||||
|
fmt.Fprintf(ctx.App.Writer, "\tAmount : %s\n", decimalAmount(balance.Amount, balance.Decimals))
|
||||||
fmt.Fprintf(ctx.App.Writer, "\tUpdated: %d\n", balance.LastUpdated)
|
fmt.Fprintf(ctx.App.Writer, "\tUpdated: %d\n", balance.LastUpdated)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -456,13 +456,13 @@ commands with the following adjustments.
|
||||||
|
|
||||||
#### Balance
|
#### Balance
|
||||||
|
|
||||||
Specify token ID via `--id` flag to call divisible NEP-11 `balanceOf` method:
|
Specify token ID via `--id` flag to get data for a particular NFT:
|
||||||
|
|
||||||
```
|
```
|
||||||
./bin/neo-go wallet nep11 balance -w /etc/neo-go/wallet.json --token 67ecb7766dba4acf7c877392207984d1b4d15731 --id R5OREI5BU+Uyd23/MuV/xzI3F+Q= -r http://localhost:20332
|
./bin/neo-go wallet nep11 balance -w /etc/neo-go/wallet.json --token 67ecb7766dba4acf7c877392207984d1b4d15731 --id 7e244ffd6aa85fb1579d2ed22e9b761ab62e3486 -r http://localhost:20332
|
||||||
```
|
```
|
||||||
|
|
||||||
By default, no token ID specified, i.e. common `balanceOf` method is called.
|
By default, no token ID specified, i.e all NFTs returned by the server are listed.
|
||||||
|
|
||||||
#### Transfers
|
#### Transfers
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue