github.com/mavryk-network/mvgo@v1.19.9/internal/compose/alpha/task/token_transfer.go (about)

     1  // Copyright (c) 2023 Blockwatch Data Inc.
     2  // Author: alex@blockwatch.cc, abdul@blockwatch.cc
     3  
     4  package task
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/mavryk-network/mvgo/codec"
    10  	"github.com/mavryk-network/mvgo/contract"
    11  	"github.com/mavryk-network/mvgo/internal/compose"
    12  	"github.com/mavryk-network/mvgo/internal/compose/alpha"
    13  	"github.com/mavryk-network/mvgo/mavryk"
    14  	"github.com/mavryk-network/mvgo/rpc"
    15  	"github.com/mavryk-network/mvgo/signer"
    16  
    17  	"github.com/pkg/errors"
    18  )
    19  
    20  var _ alpha.TaskBuilder = (*TokenTransferTask)(nil)
    21  
    22  func init() {
    23  	alpha.RegisterTask("token_transfer", NewTokenTransferTask)
    24  }
    25  
    26  type TokenTransferTask struct {
    27  	TargetTask
    28  	Standard  string
    29  	From      mavryk.Address
    30  	Receivers []TokenReceiver
    31  }
    32  
    33  type TokenReceiver struct {
    34  	Address mavryk.Address
    35  	Amount  mavryk.Z
    36  	TokenId mavryk.Z
    37  }
    38  
    39  func NewTokenTransferTask() alpha.TaskBuilder {
    40  	return &TokenTransferTask{}
    41  }
    42  
    43  func (t *TokenTransferTask) Type() string {
    44  	return "token_transfer"
    45  }
    46  
    47  func (t *TokenTransferTask) Build(ctx compose.Context, task alpha.Task) (*codec.Op, *rpc.CallOptions, error) {
    48  	if err := t.parse(ctx, task); err != nil {
    49  		return nil, nil, errors.Wrap(err, "parse")
    50  	}
    51  	var xfer codec.Operation
    52  	switch t.Standard {
    53  	case "fa2", "":
    54  		args := contract.NewFA2TransferArgs()
    55  		for _, r := range t.Receivers {
    56  			args.WithTransfer(t.From, r.Address, r.TokenId, r.Amount)
    57  		}
    58  		xfer = args.
    59  			Optimize().
    60  			WithSource(t.Source).
    61  			WithDestination(t.Destination).
    62  			Encode()
    63  	case "fa1", "fa12", "fa1.2":
    64  		xfer = contract.NewFA1TransferArgs().
    65  			WithTransfer(t.From, t.Receivers[0].Address, t.Receivers[0].Amount).
    66  			WithSource(t.Source).
    67  			WithDestination(t.Destination).
    68  			Encode()
    69  	}
    70  
    71  	opts := rpc.NewCallOptions()
    72  	opts.Signer = signer.NewFromKey(t.Key)
    73  	op := codec.NewOp().WithContents(xfer)
    74  	return op, opts, nil
    75  }
    76  
    77  func (t *TokenTransferTask) Validate(ctx compose.Context, task alpha.Task) error {
    78  	return t.parse(ctx, task)
    79  }
    80  
    81  func (t *TokenTransferTask) parse(ctx compose.Context, task alpha.Task) (err error) {
    82  	if err = t.TargetTask.parse(ctx, task); err != nil {
    83  		return err
    84  	}
    85  	if t.Standard, err = ctx.ResolveString(task.Args["standard"]); err != nil {
    86  		return errors.Wrap(err, "standard")
    87  	}
    88  	switch t.Standard {
    89  	case "fa2", "", "fa1", "fa12", "fa1.2":
    90  		// skip
    91  	default:
    92  		return fmt.Errorf("unsupported token standard %s", t.Standard)
    93  	}
    94  	if t.From, err = ctx.ResolveAddress(task.Args["from"]); err != nil {
    95  		return errors.Wrap(err, "from")
    96  	}
    97  	if val := task.Args["receivers"]; val == nil {
    98  		var tr TokenReceiver
    99  		if tr.Address, err = ctx.ResolveAddress(task.Args["to"]); err != nil {
   100  			return errors.Wrap(err, "to")
   101  		}
   102  		if tr.Amount, err = ctx.ResolveZ(task.Args["amount"]); err != nil {
   103  			return errors.Wrap(err, "amount")
   104  		}
   105  		// only required for fa2
   106  		switch t.Standard {
   107  		case "fa2", "":
   108  			if tr.TokenId, err = ctx.ResolveZ(task.Args["token_id"]); err != nil {
   109  				return errors.Wrap(err, "token_id")
   110  			}
   111  		}
   112  		t.Receivers = append(t.Receivers, tr)
   113  	} else {
   114  		recvs, ok := val.([]any)
   115  		if !ok {
   116  			return fmt.Errorf("invalid type %T for receivers, expected list(map)", val)
   117  		}
   118  		for i, v := range recvs {
   119  			var tr TokenReceiver
   120  			recv, ok := v.(map[string]any)
   121  			if !ok {
   122  				return fmt.Errorf("receiver[%d]: invalid type %T for receiver, expected map[string]string", i, val)
   123  			}
   124  			if tr.Address, err = ctx.ResolveAddress(recv["to"]); err != nil {
   125  				return fmt.Errorf("receiver[%d] to: %v", i, err)
   126  			}
   127  			if tr.Amount, err = ctx.ResolveZ(recv["amount"]); err != nil {
   128  				return fmt.Errorf("receiver[%d] amount: %v", i, err)
   129  			}
   130  			switch t.Standard {
   131  			case "fa2", "":
   132  				if tr.TokenId, err = ctx.ResolveZ(recv["token_id"]); err != nil {
   133  					return fmt.Errorf("receiver[%d] amount: %v", i, err)
   134  				}
   135  			}
   136  			t.Receivers = append(t.Receivers, tr)
   137  		}
   138  	}
   139  	return
   140  }