cli: add command for asset transfer

It uses getunspents RPC for getting UTXO and
forming transaction.
This commit is contained in:
Evgenii Stratonikov 2020-02-28 10:25:03 +03:00
parent 1b9968df67
commit ed190dcfd3

View file

@ -14,6 +14,8 @@ import (
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
"github.com/CityOfZion/neo-go/pkg/encoding/address"
"github.com/CityOfZion/neo-go/pkg/rpc/client"
"github.com/CityOfZion/neo-go/pkg/rpc/request"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/CityOfZion/neo-go/pkg/vm/opcode"
"github.com/CityOfZion/neo-go/pkg/wallet"
"github.com/urfave/cli"
@ -139,6 +141,34 @@ func NewCommands() []cli.Command {
},
},
},
{
Name: "transfer",
Usage: "transfer NEO/GAS",
UsageText: "transfer --path <path> --from <addr> --to <addr>" +
" --amount <amount> --asset [NEO|GAS|<hex-id>]",
Action: transferAsset,
Flags: []cli.Flag{
walletPathFlag,
rpcFlag,
timeoutFlag,
cli.StringFlag{
Name: "from",
Usage: "Address to send an asset from",
},
cli.StringFlag{
Name: "to",
Usage: "Address to send an asset to",
},
cli.StringFlag{
Name: "amount",
Usage: "Amount of asset to send",
},
cli.StringFlag{
Name: "asset",
Usage: "Asset ID",
},
},
},
},
}}
}
@ -344,6 +374,74 @@ func importWallet(ctx *cli.Context) error {
return nil
}
func transferAsset(ctx *cli.Context) error {
wall, err := openWallet(ctx.String("path"))
if err != nil {
return cli.NewExitError(err, 1)
}
defer wall.Close()
from := ctx.String("from")
addr, err := address.StringToUint160(from)
if err != nil {
return cli.NewExitError("invalid address", 1)
}
acc := wall.GetAccount(addr)
if acc == nil {
return cli.NewExitError(fmt.Errorf("wallet contains no account for '%s'", addr), 1)
}
asset, err := getAssetID(ctx.String("asset"))
if err != nil {
return cli.NewExitError(fmt.Errorf("invalid asset id: %v", err), 1)
}
amount, err := util.Fixed8FromString(ctx.String("amount"))
if err != nil {
return cli.NewExitError(fmt.Errorf("invalid amount: %v", err), 1)
}
pass, err := readPassword("Enter wallet password > ")
if err != nil {
return cli.NewExitError(err, 1)
} else if err := acc.Decrypt(pass); err != nil {
return cli.NewExitError(err, 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)
}
tx := transaction.NewContractTX()
tx.Data = new(transaction.ContractTX)
if err := request.AddInputsAndUnspentsToTx(tx, from, asset, amount, c); err != nil {
return cli.NewExitError(err, 1)
}
toAddr, err := address.StringToUint160(ctx.String("to"))
if err != nil {
return cli.NewExitError(err, 1)
}
tx.AddOutput(&transaction.Output{
AssetID: asset,
Amount: amount,
ScriptHash: toAddr,
Position: 1,
})
signTx(tx, acc)
if err := c.SendRawTransaction(tx); err != nil {
return cli.NewExitError(err, 1)
}
fmt.Println(tx.Hash().StringLE())
return nil
}
func getGoContext(ctx *cli.Context) (context.Context, func()) {
if dur := ctx.Duration("timeout"); dur != 0 {
return context.WithTimeout(context.Background(), dur)
@ -450,6 +548,18 @@ func openWallet(path string) (*wallet.Wallet, error) {
return wallet.NewWalletFromFile(path)
}
func getAssetID(s string) (util.Uint256, error) {
s = strings.ToLower(s)
switch {
case s == "neo":
return core.GoverningTokenID(), nil
case s == "gas":
return core.UtilityTokenID(), nil
default:
return util.Uint256DecodeStringLE(s)
}
}
func newAccountFromWIF(wif string) (*wallet.Account, error) {
// note: NEP2 strings always have length of 58 even though
// base58 strings can have different lengths even if slice lengths are equal