From 053b779be7bacfbdb6504a81cff8d13815007deb Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Fri, 6 Mar 2020 17:22:14 +0300 Subject: [PATCH] cli: implement NEP5 transfer --- cli/wallet/nep5.go | 115 +++++++++++++++++++++++++++++++++++++++++++ cli/wallet/wallet.go | 18 ++++--- 2 files changed, 125 insertions(+), 8 deletions(-) diff --git a/cli/wallet/nep5.go b/cli/wallet/nep5.go index f3e322cb0..a166c60cd 100644 --- a/cli/wallet/nep5.go +++ b/cli/wallet/nep5.go @@ -4,9 +4,16 @@ import ( "errors" "fmt" + "github.com/nspcc-dev/neo-go/cli/flags" + "github.com/nspcc-dev/neo-go/pkg/core" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/encoding/address" + "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/rpc/client" + "github.com/nspcc-dev/neo-go/pkg/rpc/request" "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/emit" + "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/urfave/cli" ) @@ -46,6 +53,31 @@ func newNEP5Commands() []cli.Command { }, }, }, + { + Name: "transfer", + Usage: "transfer NEP5 tokens", + UsageText: "transfer --path --rpc --from --to --token --amount string", + Action: transferNEP5, + Flags: []cli.Flag{ + walletPathFlag, + rpcFlag, + timeoutFlag, + fromAddrFlag, + toAddrFlag, + cli.StringFlag{ + Name: "token", + Usage: "Token to use", + }, + cli.StringFlag{ + Name: "amount", + Usage: "Amount of asset to send", + }, + cli.StringFlag{ + Name: "gas", + Usage: "Amount of GAS to attach to a tx", + }, + }, + }, } } @@ -189,3 +221,86 @@ func printTokenInfo(tok *wallet.Token) { fmt.Printf("Decimals: %d\n", tok.Decimals) fmt.Printf("Address: %s\n", tok.Address) } + +func transferNEP5(ctx *cli.Context) error { + wall, err := openWallet(ctx.String("path")) + if err != nil { + return cli.NewExitError(err, 1) + } + defer wall.Close() + + fromFlag := ctx.Generic("from").(*flags.Address) + from := fromFlag.Uint160() + acc := wall.GetAccount(from) + if acc == nil { + return cli.NewExitError(fmt.Errorf("can't find account for the address: %s", fromFlag), 1) + } + + gctx, cancel := getGoContext(ctx) + defer cancel() + c, err := client.New(gctx, ctx.String("rpc"), client.Options{}) + if err != nil { + return cli.NewExitError(err, 1) + } + + toFlag := ctx.Generic("to").(*flags.Address) + to := toFlag.Uint160() + token, err := getMatchingToken(wall, ctx.String("token")) + if err != nil { + fmt.Println("Can't find matching token in the wallet. Querying RPC-node for balances.") + token, err = getMatchingTokenRPC(c, from, ctx.String("token")) + if err != nil { + return cli.NewExitError(err, 1) + } + } + + amount, err := util.FixedNFromString(ctx.String("amount"), int(token.Decimals)) + if err != nil { + return cli.NewExitError(fmt.Errorf("invalid amount: %v", err), 1) + } + + // Note: we don't use invoke function here because it requires + // 2 round trips instead of one. + w := io.NewBufBinWriter() + emit.Int(w.BinWriter, amount) + emit.Bytes(w.BinWriter, to.BytesBE()) + emit.Bytes(w.BinWriter, from.BytesBE()) + emit.Int(w.BinWriter, 3) + emit.Opcode(w.BinWriter, opcode.PACK) + emit.String(w.BinWriter, "transfer") + emit.AppCall(w.BinWriter, token.Hash, false) + emit.Opcode(w.BinWriter, opcode.THROWIFNOT) + + var gas util.Fixed8 + if gasString := ctx.String("gas"); gasString != "" { + gas, err = util.Fixed8FromString(gasString) + if err != nil { + return cli.NewExitError(fmt.Errorf("invalid GAS amount: %v", err), 1) + } + } + + tx := transaction.NewInvocationTX(w.Bytes(), gas) + tx.Attributes = append(tx.Attributes, transaction.Attribute{ + Usage: transaction.Script, + Data: from.BytesBE(), + }) + + if err := request.AddInputsAndUnspentsToTx(tx, fromFlag.String(), core.UtilityTokenID(), gas, c); err != nil { + return cli.NewExitError(fmt.Errorf("can't add GAS to a tx: %v", err), 1) + } + + if pass, err := readPassword("Password > "); err != nil { + return cli.NewExitError(err, 1) + } else if err := acc.Decrypt(pass); err != nil { + return cli.NewExitError(err, 1) + } else if err := acc.SignTx(tx); err != nil { + return cli.NewExitError(fmt.Errorf("can't sign tx: %v", err), 1) + } + + if err := c.SendRawTransaction(tx); err != nil { + return cli.NewExitError(err, 1) + } + + fmt.Println(tx.Hash()) + return nil +} diff --git a/cli/wallet/wallet.go b/cli/wallet/wallet.go index da89a0174..1ad91810c 100644 --- a/cli/wallet/wallet.go +++ b/cli/wallet/wallet.go @@ -59,6 +59,14 @@ var ( Name: "in", Usage: "file with JSON transaction", } + fromAddrFlag = flags.AddressFlag{ + Name: "from", + Usage: "Address to send an asset from", + } + toAddrFlag = flags.AddressFlag{ + Name: "to", + Usage: "Address to send an asset to", + } ) // NewCommands returns 'wallet' command. @@ -163,14 +171,8 @@ func NewCommands() []cli.Command { rpcFlag, timeoutFlag, outFlag, - flags.AddressFlag{ - Name: "from", - Usage: "Address to send an asset from", - }, - flags.AddressFlag{ - Name: "to", - Usage: "Address to send an asset to", - }, + fromAddrFlag, + toAddrFlag, cli.StringFlag{ Name: "amount", Usage: "Amount of asset to send",