Merge pull request #1260 from nspcc-dev/feature/sendmany

cli: allow to transfer multiple tokens in `multitransfer`
This commit is contained in:
Roman Khimov 2020-08-04 19:35:03 +03:00 committed by GitHub
commit 49688de75c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 37 additions and 31 deletions

View file

@ -71,7 +71,6 @@ func newNEP5Commands() []cli.Command {
walletPathFlag, walletPathFlag,
outFlag, outFlag,
fromAddrFlag, fromAddrFlag,
tokenFlag,
gasFlag, gasFlag,
} }
multiTransferFlags = append(multiTransferFlags, options.RPC...) multiTransferFlags = append(multiTransferFlags, options.RPC...)
@ -128,7 +127,7 @@ func newNEP5Commands() []cli.Command {
Name: "multitransfer", Name: "multitransfer",
Usage: "transfer NEP5 tokens to multiple recepients", Usage: "transfer NEP5 tokens to multiple recepients",
UsageText: `multitransfer --wallet <path> --rpc-endpoint <node> --timeout <time> --from <addr>` + UsageText: `multitransfer --wallet <path> --rpc-endpoint <node> --timeout <time> --from <addr>` +
` --token <hash> <addr1>:<amount1> [<addr2>:<amount2> [...]]`, ` <token1>:<addr1>:<amount1> [<token2>:<addr2>:<amount2> [...]]`,
Action: multiTransferNEP5, Action: multiTransferNEP5,
Flags: multiTransferFlags, Flags: multiTransferFlags,
}, },
@ -357,40 +356,45 @@ func multiTransferNEP5(ctx *cli.Context) error {
return err return err
} }
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)
}
}
if ctx.NArg() == 0 { if ctx.NArg() == 0 {
return cli.NewExitError("empty recepients list", 1) return cli.NewExitError("empty recepients list", 1)
} }
var recepients []client.AddrAndAmount var recepients []client.TransferTarget
cache := make(map[string]*wallet.Token)
for i := 0; i < ctx.NArg(); i++ { for i := 0; i < ctx.NArg(); i++ {
arg := ctx.Args().Get(i) arg := ctx.Args().Get(i)
ss := strings.SplitN(arg, ":", 2) ss := strings.SplitN(arg, ":", 3)
if len(ss) != 2 { if len(ss) != 3 {
return cli.NewExitError("invalid recepient format", 1) return cli.NewExitError("send format must be '<token>:<addr>:<amount>", 1)
} }
addr, err := address.StringToUint160(ss[0]) token, ok := cache[ss[0]]
if !ok {
token, err = getMatchingToken(wall, ss[0])
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)
}
}
}
cache[ss[0]] = token
addr, err := address.StringToUint160(ss[1])
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("invalid address: '%s'", ss[0]), 1) return cli.NewExitError(fmt.Errorf("invalid address: '%s'", ss[1]), 1)
} }
amount, err := util.FixedNFromString(ss[1], int(token.Decimals)) amount, err := util.FixedNFromString(ss[2], int(token.Decimals))
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("invalid amount: %v", err), 1) return cli.NewExitError(fmt.Errorf("invalid amount: %v", err), 1)
} }
recepients = append(recepients, client.AddrAndAmount{ recepients = append(recepients, client.TransferTarget{
Token: token.Hash,
Address: addr, Address: addr,
Amount: amount, Amount: amount,
}) })
} }
return signAndSendTransfer(ctx, c, acc, token, recepients) return signAndSendTransfer(ctx, c, acc, recepients)
} }
func transferNEP5(ctx *cli.Context) error { func transferNEP5(ctx *cli.Context) error {
@ -431,14 +435,14 @@ func transferNEP5(ctx *cli.Context) error {
return cli.NewExitError(fmt.Errorf("invalid amount: %v", err), 1) return cli.NewExitError(fmt.Errorf("invalid amount: %v", err), 1)
} }
return signAndSendTransfer(ctx, c, acc, token, []client.AddrAndAmount{{ return signAndSendTransfer(ctx, c, acc, []client.TransferTarget{{
Token: token.Hash,
Address: to, Address: to,
Amount: amount, Amount: amount,
}}) }})
} }
func signAndSendTransfer(ctx *cli.Context, c *client.Client, acc *wallet.Account, token *wallet.Token, func signAndSendTransfer(ctx *cli.Context, c *client.Client, acc *wallet.Account, recepients []client.TransferTarget) error {
recepients []client.AddrAndAmount) error {
gas := flags.Fixed8FromContext(ctx, "gas") gas := flags.Fixed8FromContext(ctx, "gas")
if pass, err := readPassword("Password > "); err != nil { if pass, err := readPassword("Password > "); err != nil {
@ -447,7 +451,7 @@ func signAndSendTransfer(ctx *cli.Context, c *client.Client, acc *wallet.Account
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
tx, err := c.CreateNEP5MultiTransferTx(acc, token.Hash, int64(gas), recepients...) tx, err := c.CreateNEP5MultiTransferTx(acc, int64(gas), recepients...)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }

View file

@ -15,8 +15,9 @@ import (
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
) )
// AddrAndAmount represents target address and token amount for transfer. // TransferTarget represents target address and token amount for transfer.
type AddrAndAmount struct { type TransferTarget struct {
Token util.Uint160
Address util.Uint160 Address util.Uint160
Amount int64 Amount int64
} }
@ -110,7 +111,8 @@ func (c *Client) NEP5TokenInfo(tokenHash util.Uint160) (*wallet.Token, error) {
// (in FixedN format using contract's number of decimals) to given account and // (in FixedN format using contract's number of decimals) to given account and
// returns it. The returned transaction is not signed. // returns it. The returned transaction is not signed.
func (c *Client) CreateNEP5TransferTx(acc *wallet.Account, to util.Uint160, token util.Uint160, amount int64, gas int64) (*transaction.Transaction, error) { func (c *Client) CreateNEP5TransferTx(acc *wallet.Account, to util.Uint160, token util.Uint160, amount int64, gas int64) (*transaction.Transaction, error) {
return c.CreateNEP5MultiTransferTx(acc, token, gas, AddrAndAmount{ return c.CreateNEP5MultiTransferTx(acc, gas, TransferTarget{
Token: token,
Address: to, Address: to,
Amount: amount, Amount: amount,
}) })
@ -118,14 +120,14 @@ func (c *Client) CreateNEP5TransferTx(acc *wallet.Account, to util.Uint160, toke
// CreateNEP5MultiTransferTx creates an invocation transaction for performing NEP5 transfers // CreateNEP5MultiTransferTx creates an invocation transaction for performing NEP5 transfers
// from a single sender to multiple recepients. // from a single sender to multiple recepients.
func (c *Client) CreateNEP5MultiTransferTx(acc *wallet.Account, token util.Uint160, gas int64, recepients ...AddrAndAmount) (*transaction.Transaction, error) { func (c *Client) CreateNEP5MultiTransferTx(acc *wallet.Account, gas int64, recepients ...TransferTarget) (*transaction.Transaction, error) {
from, err := address.StringToUint160(acc.Address) from, err := address.StringToUint160(acc.Address)
if err != nil { if err != nil {
return nil, fmt.Errorf("bad account address: %v", err) return nil, fmt.Errorf("bad account address: %v", err)
} }
w := io.NewBufBinWriter() w := io.NewBufBinWriter()
for i := range recepients { for i := range recepients {
emit.AppCallWithOperationAndArgs(w.BinWriter, token, "transfer", from, emit.AppCallWithOperationAndArgs(w.BinWriter, recepients[i].Token, "transfer", from,
recepients[i].Address, recepients[i].Amount) recepients[i].Address, recepients[i].Amount)
emit.Opcode(w.BinWriter, opcode.ASSERT) emit.Opcode(w.BinWriter, opcode.ASSERT)
} }
@ -178,8 +180,8 @@ func (c *Client) TransferNEP5(acc *wallet.Account, to util.Uint160, token util.U
} }
// MultiTransferNEP5 is similar to TransferNEP5, buf allows to have multiple recepients. // MultiTransferNEP5 is similar to TransferNEP5, buf allows to have multiple recepients.
func (c *Client) MultiTransferNEP5(acc *wallet.Account, token util.Uint160, gas int64, recepients ...AddrAndAmount) (util.Uint256, error) { func (c *Client) MultiTransferNEP5(acc *wallet.Account, gas int64, recepients ...TransferTarget) (util.Uint256, error) {
tx, err := c.CreateNEP5MultiTransferTx(acc, token, gas, recepients...) tx, err := c.CreateNEP5MultiTransferTx(acc, gas, recepients...)
if err != nil { if err != nil {
return util.Uint256{}, err return util.Uint256{}, err
} }