github.com/MetalBlockchain/metalgo@v1.11.9/vms/avm/service.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package avm
     5  
     6  import (
     7  	"encoding/json"
     8  	"errors"
     9  	"fmt"
    10  	"math"
    11  	"net/http"
    12  
    13  	"go.uber.org/zap"
    14  
    15  	"github.com/MetalBlockchain/metalgo/api"
    16  	"github.com/MetalBlockchain/metalgo/database"
    17  	"github.com/MetalBlockchain/metalgo/ids"
    18  	"github.com/MetalBlockchain/metalgo/snow/choices"
    19  	"github.com/MetalBlockchain/metalgo/utils"
    20  	"github.com/MetalBlockchain/metalgo/utils/crypto/secp256k1"
    21  	"github.com/MetalBlockchain/metalgo/utils/formatting"
    22  	"github.com/MetalBlockchain/metalgo/utils/logging"
    23  	"github.com/MetalBlockchain/metalgo/utils/set"
    24  	"github.com/MetalBlockchain/metalgo/vms/avm/txs"
    25  	"github.com/MetalBlockchain/metalgo/vms/components/avax"
    26  	"github.com/MetalBlockchain/metalgo/vms/components/keystore"
    27  	"github.com/MetalBlockchain/metalgo/vms/components/verify"
    28  	"github.com/MetalBlockchain/metalgo/vms/nftfx"
    29  	"github.com/MetalBlockchain/metalgo/vms/secp256k1fx"
    30  
    31  	avajson "github.com/MetalBlockchain/metalgo/utils/json"
    32  	safemath "github.com/MetalBlockchain/metalgo/utils/math"
    33  )
    34  
    35  const (
    36  	// Max number of addresses that can be passed in as argument to GetUTXOs
    37  	maxGetUTXOsAddrs = 1024
    38  
    39  	// Max number of items allowed in a page
    40  	maxPageSize uint64 = 1024
    41  )
    42  
    43  var (
    44  	errTxNotCreateAsset   = errors.New("transaction doesn't create an asset")
    45  	errNoMinters          = errors.New("no minters provided")
    46  	errNoHoldersOrMinters = errors.New("no minters or initialHolders provided")
    47  	errZeroAmount         = errors.New("amount must be positive")
    48  	errNoOutputs          = errors.New("no outputs to send")
    49  	errInvalidMintAmount  = errors.New("amount minted must be positive")
    50  	errNilTxID            = errors.New("nil transaction ID")
    51  	errNoAddresses        = errors.New("no addresses provided")
    52  	errNoKeys             = errors.New("from addresses have no keys or funds")
    53  	errMissingPrivateKey  = errors.New("argument 'privateKey' not given")
    54  	errNotLinearized      = errors.New("chain is not linearized")
    55  )
    56  
    57  // FormattedAssetID defines a JSON formatted struct containing an assetID as a string
    58  type FormattedAssetID struct {
    59  	AssetID ids.ID `json:"assetID"`
    60  }
    61  
    62  // Service defines the base service for the asset vm
    63  type Service struct{ vm *VM }
    64  
    65  // GetBlock returns the requested block.
    66  func (s *Service) GetBlock(_ *http.Request, args *api.GetBlockArgs, reply *api.GetBlockResponse) error {
    67  	s.vm.ctx.Log.Debug("API called",
    68  		zap.String("service", "avm"),
    69  		zap.String("method", "getBlock"),
    70  		zap.Stringer("blkID", args.BlockID),
    71  		zap.Stringer("encoding", args.Encoding),
    72  	)
    73  
    74  	s.vm.ctx.Lock.Lock()
    75  	defer s.vm.ctx.Lock.Unlock()
    76  
    77  	if s.vm.chainManager == nil {
    78  		return errNotLinearized
    79  	}
    80  	block, err := s.vm.chainManager.GetStatelessBlock(args.BlockID)
    81  	if err != nil {
    82  		return fmt.Errorf("couldn't get block with id %s: %w", args.BlockID, err)
    83  	}
    84  	reply.Encoding = args.Encoding
    85  
    86  	var result any
    87  	if args.Encoding == formatting.JSON {
    88  		block.InitCtx(s.vm.ctx)
    89  		for _, tx := range block.Txs() {
    90  			err := tx.Unsigned.Visit(&txInit{
    91  				tx:            tx,
    92  				ctx:           s.vm.ctx,
    93  				typeToFxIndex: s.vm.typeToFxIndex,
    94  				fxs:           s.vm.fxs,
    95  			})
    96  			if err != nil {
    97  				return err
    98  			}
    99  		}
   100  		result = block
   101  	} else {
   102  		result, err = formatting.Encode(args.Encoding, block.Bytes())
   103  		if err != nil {
   104  			return fmt.Errorf("couldn't encode block %s as string: %w", args.BlockID, err)
   105  		}
   106  	}
   107  
   108  	reply.Block, err = json.Marshal(result)
   109  	return err
   110  }
   111  
   112  // GetBlockByHeight returns the block at the given height.
   113  func (s *Service) GetBlockByHeight(_ *http.Request, args *api.GetBlockByHeightArgs, reply *api.GetBlockResponse) error {
   114  	s.vm.ctx.Log.Debug("API called",
   115  		zap.String("service", "avm"),
   116  		zap.String("method", "getBlockByHeight"),
   117  		zap.Uint64("height", uint64(args.Height)),
   118  	)
   119  
   120  	s.vm.ctx.Lock.Lock()
   121  	defer s.vm.ctx.Lock.Unlock()
   122  
   123  	if s.vm.chainManager == nil {
   124  		return errNotLinearized
   125  	}
   126  	reply.Encoding = args.Encoding
   127  
   128  	blockID, err := s.vm.state.GetBlockIDAtHeight(uint64(args.Height))
   129  	if err != nil {
   130  		return fmt.Errorf("couldn't get block at height %d: %w", args.Height, err)
   131  	}
   132  	block, err := s.vm.chainManager.GetStatelessBlock(blockID)
   133  	if err != nil {
   134  		s.vm.ctx.Log.Error("couldn't get accepted block",
   135  			zap.Stringer("blkID", blockID),
   136  			zap.Error(err),
   137  		)
   138  		return fmt.Errorf("couldn't get block with id %s: %w", blockID, err)
   139  	}
   140  
   141  	var result any
   142  	if args.Encoding == formatting.JSON {
   143  		block.InitCtx(s.vm.ctx)
   144  		for _, tx := range block.Txs() {
   145  			err := tx.Unsigned.Visit(&txInit{
   146  				tx:            tx,
   147  				ctx:           s.vm.ctx,
   148  				typeToFxIndex: s.vm.typeToFxIndex,
   149  				fxs:           s.vm.fxs,
   150  			})
   151  			if err != nil {
   152  				return err
   153  			}
   154  		}
   155  		result = block
   156  	} else {
   157  		result, err = formatting.Encode(args.Encoding, block.Bytes())
   158  		if err != nil {
   159  			return fmt.Errorf("couldn't encode block %s as string: %w", blockID, err)
   160  		}
   161  	}
   162  
   163  	reply.Block, err = json.Marshal(result)
   164  	return err
   165  }
   166  
   167  // GetHeight returns the height of the last accepted block.
   168  func (s *Service) GetHeight(_ *http.Request, _ *struct{}, reply *api.GetHeightResponse) error {
   169  	s.vm.ctx.Log.Debug("API called",
   170  		zap.String("service", "avm"),
   171  		zap.String("method", "getHeight"),
   172  	)
   173  
   174  	s.vm.ctx.Lock.Lock()
   175  	defer s.vm.ctx.Lock.Unlock()
   176  
   177  	if s.vm.chainManager == nil {
   178  		return errNotLinearized
   179  	}
   180  
   181  	blockID := s.vm.state.GetLastAccepted()
   182  	block, err := s.vm.chainManager.GetStatelessBlock(blockID)
   183  	if err != nil {
   184  		s.vm.ctx.Log.Error("couldn't get last accepted block",
   185  			zap.Stringer("blkID", blockID),
   186  			zap.Error(err),
   187  		)
   188  		return fmt.Errorf("couldn't get block with id %s: %w", blockID, err)
   189  	}
   190  
   191  	reply.Height = avajson.Uint64(block.Height())
   192  	return nil
   193  }
   194  
   195  // IssueTx attempts to issue a transaction into consensus
   196  func (s *Service) IssueTx(_ *http.Request, args *api.FormattedTx, reply *api.JSONTxID) error {
   197  	s.vm.ctx.Log.Debug("API called",
   198  		zap.String("service", "avm"),
   199  		zap.String("method", "issueTx"),
   200  		logging.UserString("tx", args.Tx),
   201  	)
   202  
   203  	txBytes, err := formatting.Decode(args.Encoding, args.Tx)
   204  	if err != nil {
   205  		return fmt.Errorf("problem decoding transaction: %w", err)
   206  	}
   207  
   208  	tx, err := s.vm.parser.ParseTx(txBytes)
   209  	if err != nil {
   210  		s.vm.ctx.Log.Debug("failed to parse tx",
   211  			zap.Error(err),
   212  		)
   213  		return err
   214  	}
   215  
   216  	reply.TxID, err = s.vm.issueTxFromRPC(tx)
   217  	return err
   218  }
   219  
   220  // GetTxStatusReply defines the GetTxStatus replies returned from the API
   221  type GetTxStatusReply struct {
   222  	Status choices.Status `json:"status"`
   223  }
   224  
   225  type GetAddressTxsArgs struct {
   226  	api.JSONAddress
   227  	// Cursor used as a page index / offset
   228  	Cursor avajson.Uint64 `json:"cursor"`
   229  	// PageSize num of items per page
   230  	PageSize avajson.Uint64 `json:"pageSize"`
   231  	// AssetID defaulted to AVAX if omitted or left blank
   232  	AssetID string `json:"assetID"`
   233  }
   234  
   235  type GetAddressTxsReply struct {
   236  	TxIDs []ids.ID `json:"txIDs"`
   237  	// Cursor used as a page index / offset
   238  	Cursor avajson.Uint64 `json:"cursor"`
   239  }
   240  
   241  // GetAddressTxs returns list of transactions for a given address
   242  func (s *Service) GetAddressTxs(_ *http.Request, args *GetAddressTxsArgs, reply *GetAddressTxsReply) error {
   243  	cursor := uint64(args.Cursor)
   244  	pageSize := uint64(args.PageSize)
   245  	s.vm.ctx.Log.Warn("deprecated API called",
   246  		zap.String("service", "avm"),
   247  		zap.String("method", "getAddressTxs"),
   248  		logging.UserString("address", args.Address),
   249  		logging.UserString("assetID", args.AssetID),
   250  		zap.Uint64("cursor", cursor),
   251  		zap.Uint64("pageSize", pageSize),
   252  	)
   253  	if pageSize > maxPageSize {
   254  		return fmt.Errorf("pageSize > maximum allowed (%d)", maxPageSize)
   255  	} else if pageSize == 0 {
   256  		pageSize = maxPageSize
   257  	}
   258  
   259  	// Parse to address
   260  	address, err := avax.ParseServiceAddress(s.vm, args.Address)
   261  	if err != nil {
   262  		return fmt.Errorf("couldn't parse argument 'address' to address: %w", err)
   263  	}
   264  
   265  	// Lookup assetID
   266  	assetID, err := s.vm.lookupAssetID(args.AssetID)
   267  	if err != nil {
   268  		return fmt.Errorf("specified `assetID` is invalid: %w", err)
   269  	}
   270  
   271  	s.vm.ctx.Log.Debug("fetching transactions",
   272  		logging.UserString("address", args.Address),
   273  		logging.UserString("assetID", args.AssetID),
   274  		zap.Uint64("cursor", cursor),
   275  		zap.Uint64("pageSize", pageSize),
   276  	)
   277  
   278  	s.vm.ctx.Lock.Lock()
   279  	defer s.vm.ctx.Lock.Unlock()
   280  
   281  	// Read transactions from the indexer
   282  	reply.TxIDs, err = s.vm.addressTxsIndexer.Read(address[:], assetID, cursor, pageSize)
   283  	if err != nil {
   284  		return err
   285  	}
   286  	s.vm.ctx.Log.Debug("fetched transactions",
   287  		logging.UserString("address", args.Address),
   288  		logging.UserString("assetID", args.AssetID),
   289  		zap.Int("numTxs", len(reply.TxIDs)),
   290  	)
   291  
   292  	// To get the next set of tx IDs, the user should provide this cursor.
   293  	// e.g. if they provided cursor 5, and read 6 tx IDs, they should start
   294  	// next time from index (cursor) 11.
   295  	reply.Cursor = avajson.Uint64(cursor + uint64(len(reply.TxIDs)))
   296  	return nil
   297  }
   298  
   299  // GetTxStatus returns the status of the specified transaction
   300  //
   301  // Deprecated: GetTxStatus only returns Accepted or Unknown, GetTx should be
   302  // used instead to determine if the tx was accepted.
   303  func (s *Service) GetTxStatus(_ *http.Request, args *api.JSONTxID, reply *GetTxStatusReply) error {
   304  	s.vm.ctx.Log.Debug("deprecated API called",
   305  		zap.String("service", "avm"),
   306  		zap.String("method", "getTxStatus"),
   307  		zap.Stringer("txID", args.TxID),
   308  	)
   309  
   310  	if args.TxID == ids.Empty {
   311  		return errNilTxID
   312  	}
   313  
   314  	s.vm.ctx.Lock.Lock()
   315  	defer s.vm.ctx.Lock.Unlock()
   316  
   317  	_, err := s.vm.state.GetTx(args.TxID)
   318  	switch err {
   319  	case nil:
   320  		reply.Status = choices.Accepted
   321  	case database.ErrNotFound:
   322  		reply.Status = choices.Unknown
   323  	default:
   324  		return err
   325  	}
   326  	return nil
   327  }
   328  
   329  // GetTx returns the specified transaction
   330  func (s *Service) GetTx(_ *http.Request, args *api.GetTxArgs, reply *api.GetTxReply) error {
   331  	s.vm.ctx.Log.Debug("API called",
   332  		zap.String("service", "avm"),
   333  		zap.String("method", "getTx"),
   334  		zap.Stringer("txID", args.TxID),
   335  	)
   336  
   337  	if args.TxID == ids.Empty {
   338  		return errNilTxID
   339  	}
   340  
   341  	s.vm.ctx.Lock.Lock()
   342  	defer s.vm.ctx.Lock.Unlock()
   343  
   344  	tx, err := s.vm.state.GetTx(args.TxID)
   345  	if err != nil {
   346  		return err
   347  	}
   348  	reply.Encoding = args.Encoding
   349  
   350  	var result any
   351  	if args.Encoding == formatting.JSON {
   352  		err = tx.Unsigned.Visit(&txInit{
   353  			tx:            tx,
   354  			ctx:           s.vm.ctx,
   355  			typeToFxIndex: s.vm.typeToFxIndex,
   356  			fxs:           s.vm.fxs,
   357  		})
   358  		result = tx
   359  	} else {
   360  		result, err = formatting.Encode(args.Encoding, tx.Bytes())
   361  	}
   362  	if err != nil {
   363  		return err
   364  	}
   365  
   366  	reply.Tx, err = json.Marshal(result)
   367  	return err
   368  }
   369  
   370  // GetUTXOs gets all utxos for passed in addresses
   371  func (s *Service) GetUTXOs(_ *http.Request, args *api.GetUTXOsArgs, reply *api.GetUTXOsReply) error {
   372  	s.vm.ctx.Log.Debug("API called",
   373  		zap.String("service", "avm"),
   374  		zap.String("method", "getUTXOs"),
   375  		logging.UserStrings("addresses", args.Addresses),
   376  	)
   377  
   378  	if len(args.Addresses) == 0 {
   379  		return errNoAddresses
   380  	}
   381  	if len(args.Addresses) > maxGetUTXOsAddrs {
   382  		return fmt.Errorf("number of addresses given, %d, exceeds maximum, %d", len(args.Addresses), maxGetUTXOsAddrs)
   383  	}
   384  
   385  	var sourceChain ids.ID
   386  	if args.SourceChain == "" {
   387  		sourceChain = s.vm.ctx.ChainID
   388  	} else {
   389  		chainID, err := s.vm.ctx.BCLookup.Lookup(args.SourceChain)
   390  		if err != nil {
   391  			return fmt.Errorf("problem parsing source chainID %q: %w", args.SourceChain, err)
   392  		}
   393  		sourceChain = chainID
   394  	}
   395  
   396  	addrSet, err := avax.ParseServiceAddresses(s.vm, args.Addresses)
   397  	if err != nil {
   398  		return err
   399  	}
   400  
   401  	startAddr := ids.ShortEmpty
   402  	startUTXO := ids.Empty
   403  	if args.StartIndex.Address != "" || args.StartIndex.UTXO != "" {
   404  		startAddr, err = avax.ParseServiceAddress(s.vm, args.StartIndex.Address)
   405  		if err != nil {
   406  			return fmt.Errorf("couldn't parse start index address %q: %w", args.StartIndex.Address, err)
   407  		}
   408  		startUTXO, err = ids.FromString(args.StartIndex.UTXO)
   409  		if err != nil {
   410  			return fmt.Errorf("couldn't parse start index utxo: %w", err)
   411  		}
   412  	}
   413  
   414  	var (
   415  		utxos     []*avax.UTXO
   416  		endAddr   ids.ShortID
   417  		endUTXOID ids.ID
   418  	)
   419  	limit := int(args.Limit)
   420  	if limit <= 0 || int(maxPageSize) < limit {
   421  		limit = int(maxPageSize)
   422  	}
   423  
   424  	s.vm.ctx.Lock.Lock()
   425  	defer s.vm.ctx.Lock.Unlock()
   426  
   427  	if sourceChain == s.vm.ctx.ChainID {
   428  		utxos, endAddr, endUTXOID, err = avax.GetPaginatedUTXOs(
   429  			s.vm.state,
   430  			addrSet,
   431  			startAddr,
   432  			startUTXO,
   433  			limit,
   434  		)
   435  	} else {
   436  		utxos, endAddr, endUTXOID, err = avax.GetAtomicUTXOs(
   437  			s.vm.ctx.SharedMemory,
   438  			s.vm.parser.Codec(),
   439  			sourceChain,
   440  			addrSet,
   441  			startAddr,
   442  			startUTXO,
   443  			limit,
   444  		)
   445  	}
   446  	if err != nil {
   447  		return fmt.Errorf("problem retrieving UTXOs: %w", err)
   448  	}
   449  
   450  	reply.UTXOs = make([]string, len(utxos))
   451  	codec := s.vm.parser.Codec()
   452  	for i, utxo := range utxos {
   453  		b, err := codec.Marshal(txs.CodecVersion, utxo)
   454  		if err != nil {
   455  			return fmt.Errorf("problem marshalling UTXO: %w", err)
   456  		}
   457  		reply.UTXOs[i], err = formatting.Encode(args.Encoding, b)
   458  		if err != nil {
   459  			return fmt.Errorf("couldn't encode UTXO %s as string: %w", utxo.InputID(), err)
   460  		}
   461  	}
   462  
   463  	endAddress, err := s.vm.FormatLocalAddress(endAddr)
   464  	if err != nil {
   465  		return fmt.Errorf("problem formatting address: %w", err)
   466  	}
   467  
   468  	reply.EndIndex.Address = endAddress
   469  	reply.EndIndex.UTXO = endUTXOID.String()
   470  	reply.NumFetched = avajson.Uint64(len(utxos))
   471  	reply.Encoding = args.Encoding
   472  	return nil
   473  }
   474  
   475  // GetAssetDescriptionArgs are arguments for passing into GetAssetDescription requests
   476  type GetAssetDescriptionArgs struct {
   477  	AssetID string `json:"assetID"`
   478  }
   479  
   480  // GetAssetDescriptionReply defines the GetAssetDescription replies returned from the API
   481  type GetAssetDescriptionReply struct {
   482  	FormattedAssetID
   483  	Name         string        `json:"name"`
   484  	Symbol       string        `json:"symbol"`
   485  	Denomination avajson.Uint8 `json:"denomination"`
   486  }
   487  
   488  // GetAssetDescription creates an empty account with the name passed in
   489  func (s *Service) GetAssetDescription(_ *http.Request, args *GetAssetDescriptionArgs, reply *GetAssetDescriptionReply) error {
   490  	s.vm.ctx.Log.Debug("API called",
   491  		zap.String("service", "avm"),
   492  		zap.String("method", "getAssetDescription"),
   493  		logging.UserString("assetID", args.AssetID),
   494  	)
   495  
   496  	assetID, err := s.vm.lookupAssetID(args.AssetID)
   497  	if err != nil {
   498  		return err
   499  	}
   500  
   501  	s.vm.ctx.Lock.Lock()
   502  	defer s.vm.ctx.Lock.Unlock()
   503  
   504  	tx, err := s.vm.state.GetTx(assetID)
   505  	if err != nil {
   506  		return err
   507  	}
   508  	createAssetTx, ok := tx.Unsigned.(*txs.CreateAssetTx)
   509  	if !ok {
   510  		return errTxNotCreateAsset
   511  	}
   512  
   513  	reply.AssetID = assetID
   514  	reply.Name = createAssetTx.Name
   515  	reply.Symbol = createAssetTx.Symbol
   516  	reply.Denomination = avajson.Uint8(createAssetTx.Denomination)
   517  
   518  	return nil
   519  }
   520  
   521  // GetBalanceArgs are arguments for passing into GetBalance requests
   522  type GetBalanceArgs struct {
   523  	Address        string `json:"address"`
   524  	AssetID        string `json:"assetID"`
   525  	IncludePartial bool   `json:"includePartial"`
   526  }
   527  
   528  // GetBalanceReply defines the GetBalance replies returned from the API
   529  type GetBalanceReply struct {
   530  	Balance avajson.Uint64 `json:"balance"`
   531  	UTXOIDs []avax.UTXOID  `json:"utxoIDs"`
   532  }
   533  
   534  // GetBalance returns the balance of an asset held by an address.
   535  // If ![args.IncludePartial], returns only the balance held solely
   536  // (1 out of 1 multisig) by the address and with a locktime in the past.
   537  // Otherwise, returned balance includes assets held only partially by the
   538  // address, and includes balances with locktime in the future.
   539  func (s *Service) GetBalance(_ *http.Request, args *GetBalanceArgs, reply *GetBalanceReply) error {
   540  	s.vm.ctx.Log.Debug("deprecated API called",
   541  		zap.String("service", "avm"),
   542  		zap.String("method", "getBalance"),
   543  		logging.UserString("address", args.Address),
   544  		logging.UserString("assetID", args.AssetID),
   545  	)
   546  
   547  	addr, err := avax.ParseServiceAddress(s.vm, args.Address)
   548  	if err != nil {
   549  		return fmt.Errorf("problem parsing address '%s': %w", args.Address, err)
   550  	}
   551  
   552  	assetID, err := s.vm.lookupAssetID(args.AssetID)
   553  	if err != nil {
   554  		return err
   555  	}
   556  
   557  	addrSet := set.Of(addr)
   558  
   559  	s.vm.ctx.Lock.Lock()
   560  	defer s.vm.ctx.Lock.Unlock()
   561  
   562  	utxos, err := avax.GetAllUTXOs(s.vm.state, addrSet)
   563  	if err != nil {
   564  		return fmt.Errorf("problem retrieving UTXOs: %w", err)
   565  	}
   566  
   567  	now := s.vm.clock.Unix()
   568  	reply.UTXOIDs = make([]avax.UTXOID, 0, len(utxos))
   569  	for _, utxo := range utxos {
   570  		if utxo.AssetID() != assetID {
   571  			continue
   572  		}
   573  		// TODO make this not specific to *secp256k1fx.TransferOutput
   574  		transferable, ok := utxo.Out.(*secp256k1fx.TransferOutput)
   575  		if !ok {
   576  			continue
   577  		}
   578  		owners := transferable.OutputOwners
   579  		if !args.IncludePartial && (len(owners.Addrs) != 1 || owners.Locktime > now) {
   580  			continue
   581  		}
   582  		amt, err := safemath.Add64(transferable.Amount(), uint64(reply.Balance))
   583  		if err != nil {
   584  			return err
   585  		}
   586  		reply.Balance = avajson.Uint64(amt)
   587  		reply.UTXOIDs = append(reply.UTXOIDs, utxo.UTXOID)
   588  	}
   589  
   590  	return nil
   591  }
   592  
   593  type Balance struct {
   594  	AssetID string         `json:"asset"`
   595  	Balance avajson.Uint64 `json:"balance"`
   596  }
   597  
   598  type GetAllBalancesArgs struct {
   599  	api.JSONAddress
   600  	IncludePartial bool `json:"includePartial"`
   601  }
   602  
   603  // GetAllBalancesReply is the response from a call to GetAllBalances
   604  type GetAllBalancesReply struct {
   605  	Balances []Balance `json:"balances"`
   606  }
   607  
   608  // GetAllBalances returns a map where:
   609  //
   610  // Key: ID of an asset such that [args.Address] has a non-zero balance of the asset
   611  // Value: The balance of the asset held by the address
   612  //
   613  // If ![args.IncludePartial], returns only unlocked balance/UTXOs with a 1-out-of-1 multisig.
   614  // Otherwise, returned balance/UTXOs includes assets held only partially by the
   615  // address, and includes balances with locktime in the future.
   616  func (s *Service) GetAllBalances(_ *http.Request, args *GetAllBalancesArgs, reply *GetAllBalancesReply) error {
   617  	s.vm.ctx.Log.Debug("deprecated API called",
   618  		zap.String("service", "avm"),
   619  		zap.String("method", "getAllBalances"),
   620  		logging.UserString("address", args.Address),
   621  	)
   622  
   623  	address, err := avax.ParseServiceAddress(s.vm, args.Address)
   624  	if err != nil {
   625  		return fmt.Errorf("problem parsing address '%s': %w", args.Address, err)
   626  	}
   627  	addrSet := set.Of(address)
   628  
   629  	s.vm.ctx.Lock.Lock()
   630  	defer s.vm.ctx.Lock.Unlock()
   631  
   632  	utxos, err := avax.GetAllUTXOs(s.vm.state, addrSet)
   633  	if err != nil {
   634  		return fmt.Errorf("couldn't get address's UTXOs: %w", err)
   635  	}
   636  
   637  	now := s.vm.clock.Unix()
   638  	assetIDs := set.Set[ids.ID]{}       // IDs of assets the address has a non-zero balance of
   639  	balances := make(map[ids.ID]uint64) // key: ID (as bytes). value: balance of that asset
   640  	for _, utxo := range utxos {
   641  		// TODO make this not specific to *secp256k1fx.TransferOutput
   642  		transferable, ok := utxo.Out.(*secp256k1fx.TransferOutput)
   643  		if !ok {
   644  			continue
   645  		}
   646  		owners := transferable.OutputOwners
   647  		if !args.IncludePartial && (len(owners.Addrs) != 1 || owners.Locktime > now) {
   648  			continue
   649  		}
   650  		assetID := utxo.AssetID()
   651  		assetIDs.Add(assetID)
   652  		balance := balances[assetID] // 0 if key doesn't exist
   653  		balance, err := safemath.Add64(transferable.Amount(), balance)
   654  		if err != nil {
   655  			balances[assetID] = math.MaxUint64
   656  		} else {
   657  			balances[assetID] = balance
   658  		}
   659  	}
   660  
   661  	reply.Balances = make([]Balance, assetIDs.Len())
   662  	i := 0
   663  	for assetID := range assetIDs {
   664  		alias := s.vm.PrimaryAliasOrDefault(assetID)
   665  		reply.Balances[i] = Balance{
   666  			AssetID: alias,
   667  			Balance: avajson.Uint64(balances[assetID]),
   668  		}
   669  		i++
   670  	}
   671  
   672  	return nil
   673  }
   674  
   675  // Holder describes how much an address owns of an asset
   676  type Holder struct {
   677  	Amount  avajson.Uint64 `json:"amount"`
   678  	Address string         `json:"address"`
   679  }
   680  
   681  // Owners describes who can perform an action
   682  type Owners struct {
   683  	Threshold avajson.Uint32 `json:"threshold"`
   684  	Minters   []string       `json:"minters"`
   685  }
   686  
   687  // CreateAssetArgs are arguments for passing into CreateAsset
   688  type CreateAssetArgs struct {
   689  	api.JSONSpendHeader           // User, password, from addrs, change addr
   690  	Name                string    `json:"name"`
   691  	Symbol              string    `json:"symbol"`
   692  	Denomination        byte      `json:"denomination"`
   693  	InitialHolders      []*Holder `json:"initialHolders"`
   694  	MinterSets          []Owners  `json:"minterSets"`
   695  }
   696  
   697  // AssetIDChangeAddr is an asset ID and a change address
   698  type AssetIDChangeAddr struct {
   699  	FormattedAssetID
   700  	api.JSONChangeAddr
   701  }
   702  
   703  // CreateAsset returns ID of the newly created asset
   704  func (s *Service) CreateAsset(_ *http.Request, args *CreateAssetArgs, reply *AssetIDChangeAddr) error {
   705  	s.vm.ctx.Log.Warn("deprecated API called",
   706  		zap.String("service", "avm"),
   707  		zap.String("method", "createAsset"),
   708  		logging.UserString("name", args.Name),
   709  		logging.UserString("symbol", args.Symbol),
   710  		zap.Int("numInitialHolders", len(args.InitialHolders)),
   711  		zap.Int("numMinters", len(args.MinterSets)),
   712  	)
   713  
   714  	tx, changeAddr, err := s.buildCreateAssetTx(args)
   715  	if err != nil {
   716  		return err
   717  	}
   718  
   719  	assetID, err := s.vm.issueTxFromRPC(tx)
   720  	if err != nil {
   721  		return fmt.Errorf("problem issuing transaction: %w", err)
   722  	}
   723  
   724  	reply.AssetID = assetID
   725  	reply.ChangeAddr, err = s.vm.FormatLocalAddress(changeAddr)
   726  	return err
   727  }
   728  
   729  func (s *Service) buildCreateAssetTx(args *CreateAssetArgs) (*txs.Tx, ids.ShortID, error) {
   730  	if len(args.InitialHolders) == 0 && len(args.MinterSets) == 0 {
   731  		return nil, ids.ShortEmpty, errNoHoldersOrMinters
   732  	}
   733  
   734  	// Parse the from addresses
   735  	fromAddrs, err := avax.ParseServiceAddresses(s.vm, args.From)
   736  	if err != nil {
   737  		return nil, ids.ShortEmpty, err
   738  	}
   739  
   740  	s.vm.ctx.Lock.Lock()
   741  	defer s.vm.ctx.Lock.Unlock()
   742  
   743  	// Get the UTXOs/keys for the from addresses
   744  	utxos, kc, err := s.vm.LoadUser(args.Username, args.Password, fromAddrs)
   745  	if err != nil {
   746  		return nil, ids.ShortEmpty, err
   747  	}
   748  
   749  	// Parse the change address.
   750  	if len(kc.Keys) == 0 {
   751  		return nil, ids.ShortEmpty, errNoKeys
   752  	}
   753  	changeAddr, err := s.vm.selectChangeAddr(kc.Keys[0].PublicKey().Address(), args.ChangeAddr)
   754  	if err != nil {
   755  		return nil, ids.ShortEmpty, err
   756  	}
   757  
   758  	amountsSpent, ins, keys, err := s.vm.Spend(
   759  		utxos,
   760  		kc,
   761  		map[ids.ID]uint64{
   762  			s.vm.feeAssetID: s.vm.CreateAssetTxFee,
   763  		},
   764  	)
   765  	if err != nil {
   766  		return nil, ids.ShortEmpty, err
   767  	}
   768  
   769  	outs := []*avax.TransferableOutput{}
   770  	if amountSpent := amountsSpent[s.vm.feeAssetID]; amountSpent > s.vm.CreateAssetTxFee {
   771  		outs = append(outs, &avax.TransferableOutput{
   772  			Asset: avax.Asset{ID: s.vm.feeAssetID},
   773  			Out: &secp256k1fx.TransferOutput{
   774  				Amt: amountSpent - s.vm.CreateAssetTxFee,
   775  				OutputOwners: secp256k1fx.OutputOwners{
   776  					Locktime:  0,
   777  					Threshold: 1,
   778  					Addrs:     []ids.ShortID{changeAddr},
   779  				},
   780  			},
   781  		})
   782  	}
   783  
   784  	initialState := &txs.InitialState{
   785  		FxIndex: 0, // TODO: Should lookup secp256k1fx FxID
   786  		Outs:    make([]verify.State, 0, len(args.InitialHolders)+len(args.MinterSets)),
   787  	}
   788  	for _, holder := range args.InitialHolders {
   789  		addr, err := avax.ParseServiceAddress(s.vm, holder.Address)
   790  		if err != nil {
   791  			return nil, ids.ShortEmpty, err
   792  		}
   793  		initialState.Outs = append(initialState.Outs, &secp256k1fx.TransferOutput{
   794  			Amt: uint64(holder.Amount),
   795  			OutputOwners: secp256k1fx.OutputOwners{
   796  				Threshold: 1,
   797  				Addrs:     []ids.ShortID{addr},
   798  			},
   799  		})
   800  	}
   801  	for _, owner := range args.MinterSets {
   802  		minter := &secp256k1fx.MintOutput{
   803  			OutputOwners: secp256k1fx.OutputOwners{
   804  				Threshold: uint32(owner.Threshold),
   805  				Addrs:     make([]ids.ShortID, 0, len(owner.Minters)),
   806  			},
   807  		}
   808  		minterAddrsSet, err := avax.ParseServiceAddresses(s.vm, owner.Minters)
   809  		if err != nil {
   810  			return nil, ids.ShortEmpty, err
   811  		}
   812  		minter.Addrs = minterAddrsSet.List()
   813  		utils.Sort(minter.Addrs)
   814  		initialState.Outs = append(initialState.Outs, minter)
   815  	}
   816  
   817  	codec := s.vm.parser.Codec()
   818  	initialState.Sort(codec)
   819  
   820  	tx := &txs.Tx{Unsigned: &txs.CreateAssetTx{
   821  		BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{
   822  			NetworkID:    s.vm.ctx.NetworkID,
   823  			BlockchainID: s.vm.ctx.ChainID,
   824  			Outs:         outs,
   825  			Ins:          ins,
   826  		}},
   827  		Name:         args.Name,
   828  		Symbol:       args.Symbol,
   829  		Denomination: args.Denomination,
   830  		States:       []*txs.InitialState{initialState},
   831  	}}
   832  	return tx, changeAddr, tx.SignSECP256K1Fx(codec, keys)
   833  }
   834  
   835  // CreateFixedCapAsset returns ID of the newly created asset
   836  func (s *Service) CreateFixedCapAsset(r *http.Request, args *CreateAssetArgs, reply *AssetIDChangeAddr) error {
   837  	s.vm.ctx.Log.Warn("deprecated API called",
   838  		zap.String("service", "avm"),
   839  		zap.String("method", "createFixedCapAsset"),
   840  		logging.UserString("name", args.Name),
   841  		logging.UserString("symbol", args.Symbol),
   842  		zap.Int("numInitialHolders", len(args.InitialHolders)),
   843  	)
   844  
   845  	return s.CreateAsset(r, args, reply)
   846  }
   847  
   848  // CreateVariableCapAsset returns ID of the newly created asset
   849  func (s *Service) CreateVariableCapAsset(r *http.Request, args *CreateAssetArgs, reply *AssetIDChangeAddr) error {
   850  	s.vm.ctx.Log.Warn("deprecated API called",
   851  		zap.String("service", "avm"),
   852  		zap.String("method", "createVariableCapAsset"),
   853  		logging.UserString("name", args.Name),
   854  		logging.UserString("symbol", args.Symbol),
   855  		zap.Int("numMinters", len(args.MinterSets)),
   856  	)
   857  
   858  	return s.CreateAsset(r, args, reply)
   859  }
   860  
   861  // CreateNFTAssetArgs are arguments for passing into CreateNFTAsset requests
   862  type CreateNFTAssetArgs struct {
   863  	api.JSONSpendHeader          // User, password, from addrs, change addr
   864  	Name                string   `json:"name"`
   865  	Symbol              string   `json:"symbol"`
   866  	MinterSets          []Owners `json:"minterSets"`
   867  }
   868  
   869  // CreateNFTAsset returns ID of the newly created asset
   870  func (s *Service) CreateNFTAsset(_ *http.Request, args *CreateNFTAssetArgs, reply *AssetIDChangeAddr) error {
   871  	s.vm.ctx.Log.Warn("deprecated API called",
   872  		zap.String("service", "avm"),
   873  		zap.String("method", "createNFTAsset"),
   874  		logging.UserString("name", args.Name),
   875  		logging.UserString("symbol", args.Symbol),
   876  		zap.Int("numMinters", len(args.MinterSets)),
   877  	)
   878  
   879  	tx, changeAddr, err := s.buildCreateNFTAsset(args)
   880  	if err != nil {
   881  		return err
   882  	}
   883  
   884  	assetID, err := s.vm.issueTxFromRPC(tx)
   885  	if err != nil {
   886  		return fmt.Errorf("problem issuing transaction: %w", err)
   887  	}
   888  
   889  	reply.AssetID = assetID
   890  	reply.ChangeAddr, err = s.vm.FormatLocalAddress(changeAddr)
   891  	return err
   892  }
   893  
   894  func (s *Service) buildCreateNFTAsset(args *CreateNFTAssetArgs) (*txs.Tx, ids.ShortID, error) {
   895  	if len(args.MinterSets) == 0 {
   896  		return nil, ids.ShortEmpty, errNoMinters
   897  	}
   898  
   899  	// Parse the from addresses
   900  	fromAddrs, err := avax.ParseServiceAddresses(s.vm, args.From)
   901  	if err != nil {
   902  		return nil, ids.ShortEmpty, err
   903  	}
   904  
   905  	s.vm.ctx.Lock.Lock()
   906  	defer s.vm.ctx.Lock.Unlock()
   907  
   908  	// Get the UTXOs/keys for the from addresses
   909  	utxos, kc, err := s.vm.LoadUser(args.Username, args.Password, fromAddrs)
   910  	if err != nil {
   911  		return nil, ids.ShortEmpty, err
   912  	}
   913  
   914  	// Parse the change address.
   915  	if len(kc.Keys) == 0 {
   916  		return nil, ids.ShortEmpty, errNoKeys
   917  	}
   918  	changeAddr, err := s.vm.selectChangeAddr(kc.Keys[0].PublicKey().Address(), args.ChangeAddr)
   919  	if err != nil {
   920  		return nil, ids.ShortEmpty, err
   921  	}
   922  
   923  	amountsSpent, ins, keys, err := s.vm.Spend(
   924  		utxos,
   925  		kc,
   926  		map[ids.ID]uint64{
   927  			s.vm.feeAssetID: s.vm.CreateAssetTxFee,
   928  		},
   929  	)
   930  	if err != nil {
   931  		return nil, ids.ShortEmpty, err
   932  	}
   933  
   934  	outs := []*avax.TransferableOutput{}
   935  	if amountSpent := amountsSpent[s.vm.feeAssetID]; amountSpent > s.vm.CreateAssetTxFee {
   936  		outs = append(outs, &avax.TransferableOutput{
   937  			Asset: avax.Asset{ID: s.vm.feeAssetID},
   938  			Out: &secp256k1fx.TransferOutput{
   939  				Amt: amountSpent - s.vm.CreateAssetTxFee,
   940  				OutputOwners: secp256k1fx.OutputOwners{
   941  					Locktime:  0,
   942  					Threshold: 1,
   943  					Addrs:     []ids.ShortID{changeAddr},
   944  				},
   945  			},
   946  		})
   947  	}
   948  
   949  	initialState := &txs.InitialState{
   950  		FxIndex: 1, // TODO: Should lookup nftfx FxID
   951  		Outs:    make([]verify.State, 0, len(args.MinterSets)),
   952  	}
   953  	for i, owner := range args.MinterSets {
   954  		minter := &nftfx.MintOutput{
   955  			GroupID: uint32(i),
   956  			OutputOwners: secp256k1fx.OutputOwners{
   957  				Threshold: uint32(owner.Threshold),
   958  			},
   959  		}
   960  		minterAddrsSet, err := avax.ParseServiceAddresses(s.vm, owner.Minters)
   961  		if err != nil {
   962  			return nil, ids.ShortEmpty, err
   963  		}
   964  		minter.Addrs = minterAddrsSet.List()
   965  		utils.Sort(minter.Addrs)
   966  		initialState.Outs = append(initialState.Outs, minter)
   967  	}
   968  
   969  	codec := s.vm.parser.Codec()
   970  	initialState.Sort(codec)
   971  
   972  	tx := &txs.Tx{Unsigned: &txs.CreateAssetTx{
   973  		BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{
   974  			NetworkID:    s.vm.ctx.NetworkID,
   975  			BlockchainID: s.vm.ctx.ChainID,
   976  			Outs:         outs,
   977  			Ins:          ins,
   978  		}},
   979  		Name:         args.Name,
   980  		Symbol:       args.Symbol,
   981  		Denomination: 0, // NFTs are non-fungible
   982  		States:       []*txs.InitialState{initialState},
   983  	}}
   984  	return tx, changeAddr, tx.SignSECP256K1Fx(codec, keys)
   985  }
   986  
   987  // CreateAddress creates an address for the user [args.Username]
   988  func (s *Service) CreateAddress(_ *http.Request, args *api.UserPass, reply *api.JSONAddress) error {
   989  	s.vm.ctx.Log.Warn("deprecated API called",
   990  		zap.String("service", "avm"),
   991  		zap.String("method", "createAddress"),
   992  		logging.UserString("username", args.Username),
   993  	)
   994  
   995  	s.vm.ctx.Lock.Lock()
   996  	defer s.vm.ctx.Lock.Unlock()
   997  
   998  	user, err := keystore.NewUserFromKeystore(s.vm.ctx.Keystore, args.Username, args.Password)
   999  	if err != nil {
  1000  		return err
  1001  	}
  1002  	defer user.Close()
  1003  
  1004  	sk, err := keystore.NewKey(user)
  1005  	if err != nil {
  1006  		return err
  1007  	}
  1008  
  1009  	reply.Address, err = s.vm.FormatLocalAddress(sk.PublicKey().Address())
  1010  	if err != nil {
  1011  		return fmt.Errorf("problem formatting address: %w", err)
  1012  	}
  1013  
  1014  	// Return an error if the DB can't close, this will execute before the above
  1015  	// db close.
  1016  	return user.Close()
  1017  }
  1018  
  1019  // ListAddresses returns all of the addresses controlled by user [args.Username]
  1020  func (s *Service) ListAddresses(_ *http.Request, args *api.UserPass, response *api.JSONAddresses) error {
  1021  	s.vm.ctx.Log.Warn("deprecated API called",
  1022  		zap.String("service", "avm"),
  1023  		zap.String("method", "listAddresses"),
  1024  		logging.UserString("username", args.Username),
  1025  	)
  1026  
  1027  	s.vm.ctx.Lock.Lock()
  1028  	defer s.vm.ctx.Lock.Unlock()
  1029  
  1030  	user, err := keystore.NewUserFromKeystore(s.vm.ctx.Keystore, args.Username, args.Password)
  1031  	if err != nil {
  1032  		return err
  1033  	}
  1034  
  1035  	response.Addresses = []string{}
  1036  
  1037  	addresses, err := user.GetAddresses()
  1038  	if err != nil {
  1039  		// An error fetching the addresses may just mean that the user has no
  1040  		// addresses.
  1041  		return user.Close()
  1042  	}
  1043  
  1044  	for _, address := range addresses {
  1045  		addr, err := s.vm.FormatLocalAddress(address)
  1046  		if err != nil {
  1047  			// Drop any potential error closing the database to report the
  1048  			// original error
  1049  			_ = user.Close()
  1050  			return fmt.Errorf("problem formatting address: %w", err)
  1051  		}
  1052  		response.Addresses = append(response.Addresses, addr)
  1053  	}
  1054  	return user.Close()
  1055  }
  1056  
  1057  // ExportKeyArgs are arguments for ExportKey
  1058  type ExportKeyArgs struct {
  1059  	api.UserPass
  1060  	Address string `json:"address"`
  1061  }
  1062  
  1063  // ExportKeyReply is the response for ExportKey
  1064  type ExportKeyReply struct {
  1065  	// The decrypted PrivateKey for the Address provided in the arguments
  1066  	PrivateKey *secp256k1.PrivateKey `json:"privateKey"`
  1067  }
  1068  
  1069  // ExportKey returns a private key from the provided user
  1070  func (s *Service) ExportKey(_ *http.Request, args *ExportKeyArgs, reply *ExportKeyReply) error {
  1071  	s.vm.ctx.Log.Warn("deprecated API called",
  1072  		zap.String("service", "avm"),
  1073  		zap.String("method", "exportKey"),
  1074  		logging.UserString("username", args.Username),
  1075  	)
  1076  
  1077  	addr, err := avax.ParseServiceAddress(s.vm, args.Address)
  1078  	if err != nil {
  1079  		return fmt.Errorf("problem parsing address %q: %w", args.Address, err)
  1080  	}
  1081  
  1082  	s.vm.ctx.Lock.Lock()
  1083  	defer s.vm.ctx.Lock.Unlock()
  1084  
  1085  	user, err := keystore.NewUserFromKeystore(s.vm.ctx.Keystore, args.Username, args.Password)
  1086  	if err != nil {
  1087  		return err
  1088  	}
  1089  
  1090  	reply.PrivateKey, err = user.GetKey(addr)
  1091  	if err != nil {
  1092  		// Drop any potential error closing the database to report the original
  1093  		// error
  1094  		_ = user.Close()
  1095  		return fmt.Errorf("problem retrieving private key: %w", err)
  1096  	}
  1097  	return user.Close()
  1098  }
  1099  
  1100  // ImportKeyArgs are arguments for ImportKey
  1101  type ImportKeyArgs struct {
  1102  	api.UserPass
  1103  	PrivateKey *secp256k1.PrivateKey `json:"privateKey"`
  1104  }
  1105  
  1106  // ImportKeyReply is the response for ImportKey
  1107  type ImportKeyReply struct {
  1108  	// The address controlled by the PrivateKey provided in the arguments
  1109  	Address string `json:"address"`
  1110  }
  1111  
  1112  // ImportKey adds a private key to the provided user
  1113  func (s *Service) ImportKey(_ *http.Request, args *ImportKeyArgs, reply *api.JSONAddress) error {
  1114  	s.vm.ctx.Log.Warn("deprecated API called",
  1115  		zap.String("service", "avm"),
  1116  		zap.String("method", "importKey"),
  1117  		logging.UserString("username", args.Username),
  1118  	)
  1119  
  1120  	if args.PrivateKey == nil {
  1121  		return errMissingPrivateKey
  1122  	}
  1123  
  1124  	s.vm.ctx.Lock.Lock()
  1125  	defer s.vm.ctx.Lock.Unlock()
  1126  
  1127  	user, err := keystore.NewUserFromKeystore(s.vm.ctx.Keystore, args.Username, args.Password)
  1128  	if err != nil {
  1129  		return err
  1130  	}
  1131  	defer user.Close()
  1132  
  1133  	if err := user.PutKeys(args.PrivateKey); err != nil {
  1134  		return fmt.Errorf("problem saving key %w", err)
  1135  	}
  1136  
  1137  	newAddress := args.PrivateKey.PublicKey().Address()
  1138  	reply.Address, err = s.vm.FormatLocalAddress(newAddress)
  1139  	if err != nil {
  1140  		return fmt.Errorf("problem formatting address: %w", err)
  1141  	}
  1142  
  1143  	return user.Close()
  1144  }
  1145  
  1146  // SendOutput specifies that [Amount] of asset [AssetID] be sent to [To]
  1147  type SendOutput struct {
  1148  	// The amount of funds to send
  1149  	Amount avajson.Uint64 `json:"amount"`
  1150  
  1151  	// ID of the asset being sent
  1152  	AssetID string `json:"assetID"`
  1153  
  1154  	// Address of the recipient
  1155  	To string `json:"to"`
  1156  }
  1157  
  1158  // SendArgs are arguments for passing into Send requests
  1159  type SendArgs struct {
  1160  	// User, password, from addrs, change addr
  1161  	api.JSONSpendHeader
  1162  
  1163  	// The amount, assetID, and destination to send funds to
  1164  	SendOutput
  1165  
  1166  	// Memo field
  1167  	Memo string `json:"memo"`
  1168  }
  1169  
  1170  // SendMultipleArgs are arguments for passing into SendMultiple requests
  1171  type SendMultipleArgs struct {
  1172  	// User, password, from addrs, change addr
  1173  	api.JSONSpendHeader
  1174  
  1175  	// The outputs of the transaction
  1176  	Outputs []SendOutput `json:"outputs"`
  1177  
  1178  	// Memo field
  1179  	Memo string `json:"memo"`
  1180  }
  1181  
  1182  // Send returns the ID of the newly created transaction
  1183  func (s *Service) Send(r *http.Request, args *SendArgs, reply *api.JSONTxIDChangeAddr) error {
  1184  	return s.SendMultiple(r, &SendMultipleArgs{
  1185  		JSONSpendHeader: args.JSONSpendHeader,
  1186  		Outputs:         []SendOutput{args.SendOutput},
  1187  		Memo:            args.Memo,
  1188  	}, reply)
  1189  }
  1190  
  1191  // SendMultiple sends a transaction with multiple outputs.
  1192  func (s *Service) SendMultiple(_ *http.Request, args *SendMultipleArgs, reply *api.JSONTxIDChangeAddr) error {
  1193  	s.vm.ctx.Log.Warn("deprecated API called",
  1194  		zap.String("service", "avm"),
  1195  		zap.String("method", "sendMultiple"),
  1196  		logging.UserString("username", args.Username),
  1197  	)
  1198  
  1199  	tx, changeAddr, err := s.buildSendMultiple(args)
  1200  	if err != nil {
  1201  		return err
  1202  	}
  1203  
  1204  	txID, err := s.vm.issueTxFromRPC(tx)
  1205  	if err != nil {
  1206  		return fmt.Errorf("problem issuing transaction: %w", err)
  1207  	}
  1208  
  1209  	reply.TxID = txID
  1210  	reply.ChangeAddr, err = s.vm.FormatLocalAddress(changeAddr)
  1211  	return err
  1212  }
  1213  
  1214  func (s *Service) buildSendMultiple(args *SendMultipleArgs) (*txs.Tx, ids.ShortID, error) {
  1215  	// Validate the memo field
  1216  	memoBytes := []byte(args.Memo)
  1217  	if l := len(memoBytes); l > avax.MaxMemoSize {
  1218  		return nil, ids.ShortEmpty, fmt.Errorf("max memo length is %d but provided memo field is length %d", avax.MaxMemoSize, l)
  1219  	} else if len(args.Outputs) == 0 {
  1220  		return nil, ids.ShortEmpty, errNoOutputs
  1221  	}
  1222  
  1223  	// Parse the from addresses
  1224  	fromAddrs, err := avax.ParseServiceAddresses(s.vm, args.From)
  1225  	if err != nil {
  1226  		return nil, ids.ShortEmpty, err
  1227  	}
  1228  
  1229  	s.vm.ctx.Lock.Lock()
  1230  	defer s.vm.ctx.Lock.Unlock()
  1231  
  1232  	// Load user's UTXOs/keys
  1233  	utxos, kc, err := s.vm.LoadUser(args.Username, args.Password, fromAddrs)
  1234  	if err != nil {
  1235  		return nil, ids.ShortEmpty, err
  1236  	}
  1237  
  1238  	// Parse the change address.
  1239  	if len(kc.Keys) == 0 {
  1240  		return nil, ids.ShortEmpty, errNoKeys
  1241  	}
  1242  	changeAddr, err := s.vm.selectChangeAddr(kc.Keys[0].PublicKey().Address(), args.ChangeAddr)
  1243  	if err != nil {
  1244  		return nil, ids.ShortEmpty, err
  1245  	}
  1246  
  1247  	// Calculate required input amounts and create the desired outputs
  1248  	// String repr. of asset ID --> asset ID
  1249  	assetIDs := make(map[string]ids.ID)
  1250  	// Asset ID --> amount of that asset being sent
  1251  	amounts := make(map[ids.ID]uint64)
  1252  	// Outputs of our tx
  1253  	outs := []*avax.TransferableOutput{}
  1254  	for _, output := range args.Outputs {
  1255  		if output.Amount == 0 {
  1256  			return nil, ids.ShortEmpty, errZeroAmount
  1257  		}
  1258  		assetID, ok := assetIDs[output.AssetID] // Asset ID of next output
  1259  		if !ok {
  1260  			assetID, err = s.vm.lookupAssetID(output.AssetID)
  1261  			if err != nil {
  1262  				return nil, ids.ShortEmpty, fmt.Errorf("couldn't find asset %s", output.AssetID)
  1263  			}
  1264  			assetIDs[output.AssetID] = assetID
  1265  		}
  1266  		currentAmount := amounts[assetID]
  1267  		newAmount, err := safemath.Add64(currentAmount, uint64(output.Amount))
  1268  		if err != nil {
  1269  			return nil, ids.ShortEmpty, fmt.Errorf("problem calculating required spend amount: %w", err)
  1270  		}
  1271  		amounts[assetID] = newAmount
  1272  
  1273  		// Parse the to address
  1274  		to, err := avax.ParseServiceAddress(s.vm, output.To)
  1275  		if err != nil {
  1276  			return nil, ids.ShortEmpty, fmt.Errorf("problem parsing to address %q: %w", output.To, err)
  1277  		}
  1278  
  1279  		// Create the Output
  1280  		outs = append(outs, &avax.TransferableOutput{
  1281  			Asset: avax.Asset{ID: assetID},
  1282  			Out: &secp256k1fx.TransferOutput{
  1283  				Amt: uint64(output.Amount),
  1284  				OutputOwners: secp256k1fx.OutputOwners{
  1285  					Locktime:  0,
  1286  					Threshold: 1,
  1287  					Addrs:     []ids.ShortID{to},
  1288  				},
  1289  			},
  1290  		})
  1291  	}
  1292  
  1293  	amountsWithFee := make(map[ids.ID]uint64, len(amounts)+1)
  1294  	for assetID, amount := range amounts {
  1295  		amountsWithFee[assetID] = amount
  1296  	}
  1297  
  1298  	amountWithFee, err := safemath.Add64(amounts[s.vm.feeAssetID], s.vm.TxFee)
  1299  	if err != nil {
  1300  		return nil, ids.ShortEmpty, fmt.Errorf("problem calculating required spend amount: %w", err)
  1301  	}
  1302  	amountsWithFee[s.vm.feeAssetID] = amountWithFee
  1303  
  1304  	amountsSpent, ins, keys, err := s.vm.Spend(
  1305  		utxos,
  1306  		kc,
  1307  		amountsWithFee,
  1308  	)
  1309  	if err != nil {
  1310  		return nil, ids.ShortEmpty, err
  1311  	}
  1312  
  1313  	// Add the required change outputs
  1314  	for assetID, amountWithFee := range amountsWithFee {
  1315  		amountSpent := amountsSpent[assetID]
  1316  
  1317  		if amountSpent > amountWithFee {
  1318  			outs = append(outs, &avax.TransferableOutput{
  1319  				Asset: avax.Asset{ID: assetID},
  1320  				Out: &secp256k1fx.TransferOutput{
  1321  					Amt: amountSpent - amountWithFee,
  1322  					OutputOwners: secp256k1fx.OutputOwners{
  1323  						Locktime:  0,
  1324  						Threshold: 1,
  1325  						Addrs:     []ids.ShortID{changeAddr},
  1326  					},
  1327  				},
  1328  			})
  1329  		}
  1330  	}
  1331  
  1332  	codec := s.vm.parser.Codec()
  1333  	avax.SortTransferableOutputs(outs, codec)
  1334  
  1335  	tx := &txs.Tx{Unsigned: &txs.BaseTx{BaseTx: avax.BaseTx{
  1336  		NetworkID:    s.vm.ctx.NetworkID,
  1337  		BlockchainID: s.vm.ctx.ChainID,
  1338  		Outs:         outs,
  1339  		Ins:          ins,
  1340  		Memo:         memoBytes,
  1341  	}}}
  1342  	return tx, changeAddr, tx.SignSECP256K1Fx(codec, keys)
  1343  }
  1344  
  1345  // MintArgs are arguments for passing into Mint requests
  1346  type MintArgs struct {
  1347  	api.JSONSpendHeader                // User, password, from addrs, change addr
  1348  	Amount              avajson.Uint64 `json:"amount"`
  1349  	AssetID             string         `json:"assetID"`
  1350  	To                  string         `json:"to"`
  1351  }
  1352  
  1353  // Mint issues a transaction that mints more of the asset
  1354  func (s *Service) Mint(_ *http.Request, args *MintArgs, reply *api.JSONTxIDChangeAddr) error {
  1355  	s.vm.ctx.Log.Warn("deprecated API called",
  1356  		zap.String("service", "avm"),
  1357  		zap.String("method", "mint"),
  1358  		logging.UserString("username", args.Username),
  1359  	)
  1360  
  1361  	tx, changeAddr, err := s.buildMint(args)
  1362  	if err != nil {
  1363  		return err
  1364  	}
  1365  
  1366  	txID, err := s.vm.issueTxFromRPC(tx)
  1367  	if err != nil {
  1368  		return fmt.Errorf("problem issuing transaction: %w", err)
  1369  	}
  1370  
  1371  	reply.TxID = txID
  1372  	reply.ChangeAddr, err = s.vm.FormatLocalAddress(changeAddr)
  1373  	return err
  1374  }
  1375  
  1376  func (s *Service) buildMint(args *MintArgs) (*txs.Tx, ids.ShortID, error) {
  1377  	s.vm.ctx.Log.Warn("deprecated API called",
  1378  		zap.String("service", "avm"),
  1379  		zap.String("method", "mint"),
  1380  		logging.UserString("username", args.Username),
  1381  	)
  1382  
  1383  	if args.Amount == 0 {
  1384  		return nil, ids.ShortEmpty, errInvalidMintAmount
  1385  	}
  1386  
  1387  	assetID, err := s.vm.lookupAssetID(args.AssetID)
  1388  	if err != nil {
  1389  		return nil, ids.ShortEmpty, err
  1390  	}
  1391  
  1392  	to, err := avax.ParseServiceAddress(s.vm, args.To)
  1393  	if err != nil {
  1394  		return nil, ids.ShortEmpty, fmt.Errorf("problem parsing to address %q: %w", args.To, err)
  1395  	}
  1396  
  1397  	// Parse the from addresses
  1398  	fromAddrs, err := avax.ParseServiceAddresses(s.vm, args.From)
  1399  	if err != nil {
  1400  		return nil, ids.ShortEmpty, err
  1401  	}
  1402  
  1403  	s.vm.ctx.Lock.Lock()
  1404  	defer s.vm.ctx.Lock.Unlock()
  1405  
  1406  	// Get the UTXOs/keys for the from addresses
  1407  	feeUTXOs, feeKc, err := s.vm.LoadUser(args.Username, args.Password, fromAddrs)
  1408  	if err != nil {
  1409  		return nil, ids.ShortEmpty, err
  1410  	}
  1411  
  1412  	// Parse the change address.
  1413  	if len(feeKc.Keys) == 0 {
  1414  		return nil, ids.ShortEmpty, errNoKeys
  1415  	}
  1416  	changeAddr, err := s.vm.selectChangeAddr(feeKc.Keys[0].PublicKey().Address(), args.ChangeAddr)
  1417  	if err != nil {
  1418  		return nil, ids.ShortEmpty, err
  1419  	}
  1420  
  1421  	amountsSpent, ins, keys, err := s.vm.Spend(
  1422  		feeUTXOs,
  1423  		feeKc,
  1424  		map[ids.ID]uint64{
  1425  			s.vm.feeAssetID: s.vm.TxFee,
  1426  		},
  1427  	)
  1428  	if err != nil {
  1429  		return nil, ids.ShortEmpty, err
  1430  	}
  1431  
  1432  	outs := []*avax.TransferableOutput{}
  1433  	if amountSpent := amountsSpent[s.vm.feeAssetID]; amountSpent > s.vm.TxFee {
  1434  		outs = append(outs, &avax.TransferableOutput{
  1435  			Asset: avax.Asset{ID: s.vm.feeAssetID},
  1436  			Out: &secp256k1fx.TransferOutput{
  1437  				Amt: amountSpent - s.vm.TxFee,
  1438  				OutputOwners: secp256k1fx.OutputOwners{
  1439  					Locktime:  0,
  1440  					Threshold: 1,
  1441  					Addrs:     []ids.ShortID{changeAddr},
  1442  				},
  1443  			},
  1444  		})
  1445  	}
  1446  
  1447  	// Get all UTXOs/keys for the user
  1448  	utxos, kc, err := s.vm.LoadUser(args.Username, args.Password, nil)
  1449  	if err != nil {
  1450  		return nil, ids.ShortEmpty, err
  1451  	}
  1452  
  1453  	ops, opKeys, err := s.vm.Mint(
  1454  		utxos,
  1455  		kc,
  1456  		map[ids.ID]uint64{
  1457  			assetID: uint64(args.Amount),
  1458  		},
  1459  		to,
  1460  	)
  1461  	if err != nil {
  1462  		return nil, ids.ShortEmpty, err
  1463  	}
  1464  	keys = append(keys, opKeys...)
  1465  
  1466  	tx := &txs.Tx{Unsigned: &txs.OperationTx{
  1467  		BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{
  1468  			NetworkID:    s.vm.ctx.NetworkID,
  1469  			BlockchainID: s.vm.ctx.ChainID,
  1470  			Outs:         outs,
  1471  			Ins:          ins,
  1472  		}},
  1473  		Ops: ops,
  1474  	}}
  1475  	return tx, changeAddr, tx.SignSECP256K1Fx(s.vm.parser.Codec(), keys)
  1476  }
  1477  
  1478  // SendNFTArgs are arguments for passing into SendNFT requests
  1479  type SendNFTArgs struct {
  1480  	api.JSONSpendHeader                // User, password, from addrs, change addr
  1481  	AssetID             string         `json:"assetID"`
  1482  	GroupID             avajson.Uint32 `json:"groupID"`
  1483  	To                  string         `json:"to"`
  1484  }
  1485  
  1486  // SendNFT sends an NFT
  1487  func (s *Service) SendNFT(_ *http.Request, args *SendNFTArgs, reply *api.JSONTxIDChangeAddr) error {
  1488  	s.vm.ctx.Log.Warn("deprecated API called",
  1489  		zap.String("service", "avm"),
  1490  		zap.String("method", "sendNFT"),
  1491  		logging.UserString("username", args.Username),
  1492  	)
  1493  
  1494  	tx, changeAddr, err := s.buildSendNFT(args)
  1495  	if err != nil {
  1496  		return err
  1497  	}
  1498  
  1499  	txID, err := s.vm.issueTxFromRPC(tx)
  1500  	if err != nil {
  1501  		return fmt.Errorf("problem issuing transaction: %w", err)
  1502  	}
  1503  
  1504  	reply.TxID = txID
  1505  	reply.ChangeAddr, err = s.vm.FormatLocalAddress(changeAddr)
  1506  	return err
  1507  }
  1508  
  1509  func (s *Service) buildSendNFT(args *SendNFTArgs) (*txs.Tx, ids.ShortID, error) {
  1510  	// Parse the asset ID
  1511  	assetID, err := s.vm.lookupAssetID(args.AssetID)
  1512  	if err != nil {
  1513  		return nil, ids.ShortEmpty, err
  1514  	}
  1515  
  1516  	// Parse the to address
  1517  	to, err := avax.ParseServiceAddress(s.vm, args.To)
  1518  	if err != nil {
  1519  		return nil, ids.ShortEmpty, fmt.Errorf("problem parsing to address %q: %w", args.To, err)
  1520  	}
  1521  
  1522  	// Parse the from addresses
  1523  	fromAddrs, err := avax.ParseServiceAddresses(s.vm, args.From)
  1524  	if err != nil {
  1525  		return nil, ids.ShortEmpty, err
  1526  	}
  1527  
  1528  	s.vm.ctx.Lock.Lock()
  1529  	defer s.vm.ctx.Lock.Unlock()
  1530  
  1531  	// Get the UTXOs/keys for the from addresses
  1532  	utxos, kc, err := s.vm.LoadUser(args.Username, args.Password, fromAddrs)
  1533  	if err != nil {
  1534  		return nil, ids.ShortEmpty, err
  1535  	}
  1536  
  1537  	// Parse the change address.
  1538  	if len(kc.Keys) == 0 {
  1539  		return nil, ids.ShortEmpty, errNoKeys
  1540  	}
  1541  	changeAddr, err := s.vm.selectChangeAddr(kc.Keys[0].PublicKey().Address(), args.ChangeAddr)
  1542  	if err != nil {
  1543  		return nil, ids.ShortEmpty, err
  1544  	}
  1545  
  1546  	amountsSpent, ins, secpKeys, err := s.vm.Spend(
  1547  		utxos,
  1548  		kc,
  1549  		map[ids.ID]uint64{
  1550  			s.vm.feeAssetID: s.vm.TxFee,
  1551  		},
  1552  	)
  1553  	if err != nil {
  1554  		return nil, ids.ShortEmpty, err
  1555  	}
  1556  
  1557  	outs := []*avax.TransferableOutput{}
  1558  	if amountSpent := amountsSpent[s.vm.feeAssetID]; amountSpent > s.vm.TxFee {
  1559  		outs = append(outs, &avax.TransferableOutput{
  1560  			Asset: avax.Asset{ID: s.vm.feeAssetID},
  1561  			Out: &secp256k1fx.TransferOutput{
  1562  				Amt: amountSpent - s.vm.TxFee,
  1563  				OutputOwners: secp256k1fx.OutputOwners{
  1564  					Locktime:  0,
  1565  					Threshold: 1,
  1566  					Addrs:     []ids.ShortID{changeAddr},
  1567  				},
  1568  			},
  1569  		})
  1570  	}
  1571  
  1572  	ops, nftKeys, err := s.vm.SpendNFT(
  1573  		utxos,
  1574  		kc,
  1575  		assetID,
  1576  		uint32(args.GroupID),
  1577  		to,
  1578  	)
  1579  	if err != nil {
  1580  		return nil, ids.ShortEmpty, err
  1581  	}
  1582  
  1583  	tx := &txs.Tx{Unsigned: &txs.OperationTx{
  1584  		BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{
  1585  			NetworkID:    s.vm.ctx.NetworkID,
  1586  			BlockchainID: s.vm.ctx.ChainID,
  1587  			Outs:         outs,
  1588  			Ins:          ins,
  1589  		}},
  1590  		Ops: ops,
  1591  	}}
  1592  
  1593  	codec := s.vm.parser.Codec()
  1594  	if err := tx.SignSECP256K1Fx(codec, secpKeys); err != nil {
  1595  		return nil, ids.ShortEmpty, err
  1596  	}
  1597  	return tx, changeAddr, tx.SignNFTFx(codec, nftKeys)
  1598  }
  1599  
  1600  // MintNFTArgs are arguments for passing into MintNFT requests
  1601  type MintNFTArgs struct {
  1602  	api.JSONSpendHeader                     // User, password, from addrs, change addr
  1603  	AssetID             string              `json:"assetID"`
  1604  	Payload             string              `json:"payload"`
  1605  	To                  string              `json:"to"`
  1606  	Encoding            formatting.Encoding `json:"encoding"`
  1607  }
  1608  
  1609  // MintNFT issues a MintNFT transaction and returns the ID of the newly created transaction
  1610  func (s *Service) MintNFT(_ *http.Request, args *MintNFTArgs, reply *api.JSONTxIDChangeAddr) error {
  1611  	s.vm.ctx.Log.Warn("deprecated API called",
  1612  		zap.String("service", "avm"),
  1613  		zap.String("method", "mintNFT"),
  1614  		logging.UserString("username", args.Username),
  1615  	)
  1616  
  1617  	tx, changeAddr, err := s.buildMintNFT(args)
  1618  	if err != nil {
  1619  		return err
  1620  	}
  1621  
  1622  	txID, err := s.vm.issueTxFromRPC(tx)
  1623  	if err != nil {
  1624  		return fmt.Errorf("problem issuing transaction: %w", err)
  1625  	}
  1626  
  1627  	reply.TxID = txID
  1628  	reply.ChangeAddr, err = s.vm.FormatLocalAddress(changeAddr)
  1629  	return err
  1630  }
  1631  
  1632  func (s *Service) buildMintNFT(args *MintNFTArgs) (*txs.Tx, ids.ShortID, error) {
  1633  	assetID, err := s.vm.lookupAssetID(args.AssetID)
  1634  	if err != nil {
  1635  		return nil, ids.ShortEmpty, err
  1636  	}
  1637  
  1638  	to, err := avax.ParseServiceAddress(s.vm, args.To)
  1639  	if err != nil {
  1640  		return nil, ids.ShortEmpty, fmt.Errorf("problem parsing to address %q: %w", args.To, err)
  1641  	}
  1642  
  1643  	payloadBytes, err := formatting.Decode(args.Encoding, args.Payload)
  1644  	if err != nil {
  1645  		return nil, ids.ShortEmpty, fmt.Errorf("problem decoding payload bytes: %w", err)
  1646  	}
  1647  
  1648  	// Parse the from addresses
  1649  	fromAddrs, err := avax.ParseServiceAddresses(s.vm, args.From)
  1650  	if err != nil {
  1651  		return nil, ids.ShortEmpty, err
  1652  	}
  1653  
  1654  	s.vm.ctx.Lock.Lock()
  1655  	defer s.vm.ctx.Lock.Unlock()
  1656  
  1657  	// Get the UTXOs/keys for the from addresses
  1658  	feeUTXOs, feeKc, err := s.vm.LoadUser(args.Username, args.Password, fromAddrs)
  1659  	if err != nil {
  1660  		return nil, ids.ShortEmpty, err
  1661  	}
  1662  
  1663  	// Parse the change address.
  1664  	if len(feeKc.Keys) == 0 {
  1665  		return nil, ids.ShortEmpty, errNoKeys
  1666  	}
  1667  	changeAddr, err := s.vm.selectChangeAddr(feeKc.Keys[0].PublicKey().Address(), args.ChangeAddr)
  1668  	if err != nil {
  1669  		return nil, ids.ShortEmpty, err
  1670  	}
  1671  
  1672  	amountsSpent, ins, secpKeys, err := s.vm.Spend(
  1673  		feeUTXOs,
  1674  		feeKc,
  1675  		map[ids.ID]uint64{
  1676  			s.vm.feeAssetID: s.vm.TxFee,
  1677  		},
  1678  	)
  1679  	if err != nil {
  1680  		return nil, ids.ShortEmpty, err
  1681  	}
  1682  
  1683  	outs := []*avax.TransferableOutput{}
  1684  	if amountSpent := amountsSpent[s.vm.feeAssetID]; amountSpent > s.vm.TxFee {
  1685  		outs = append(outs, &avax.TransferableOutput{
  1686  			Asset: avax.Asset{ID: s.vm.feeAssetID},
  1687  			Out: &secp256k1fx.TransferOutput{
  1688  				Amt: amountSpent - s.vm.TxFee,
  1689  				OutputOwners: secp256k1fx.OutputOwners{
  1690  					Locktime:  0,
  1691  					Threshold: 1,
  1692  					Addrs:     []ids.ShortID{changeAddr},
  1693  				},
  1694  			},
  1695  		})
  1696  	}
  1697  
  1698  	// Get all UTXOs/keys
  1699  	utxos, kc, err := s.vm.LoadUser(args.Username, args.Password, nil)
  1700  	if err != nil {
  1701  		return nil, ids.ShortEmpty, err
  1702  	}
  1703  
  1704  	ops, nftKeys, err := s.vm.MintNFT(
  1705  		utxos,
  1706  		kc,
  1707  		assetID,
  1708  		payloadBytes,
  1709  		to,
  1710  	)
  1711  	if err != nil {
  1712  		return nil, ids.ShortEmpty, err
  1713  	}
  1714  
  1715  	tx := &txs.Tx{Unsigned: &txs.OperationTx{
  1716  		BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{
  1717  			NetworkID:    s.vm.ctx.NetworkID,
  1718  			BlockchainID: s.vm.ctx.ChainID,
  1719  			Outs:         outs,
  1720  			Ins:          ins,
  1721  		}},
  1722  		Ops: ops,
  1723  	}}
  1724  
  1725  	codec := s.vm.parser.Codec()
  1726  	if err := tx.SignSECP256K1Fx(codec, secpKeys); err != nil {
  1727  		return nil, ids.ShortEmpty, err
  1728  	}
  1729  	return tx, changeAddr, tx.SignNFTFx(codec, nftKeys)
  1730  }
  1731  
  1732  // ImportArgs are arguments for passing into Import requests
  1733  type ImportArgs struct {
  1734  	// User that controls To
  1735  	api.UserPass
  1736  
  1737  	// Chain the funds are coming from
  1738  	SourceChain string `json:"sourceChain"`
  1739  
  1740  	// Address receiving the imported AVAX
  1741  	To string `json:"to"`
  1742  }
  1743  
  1744  // Import imports an asset to this chain from the P/C-Chain.
  1745  // The AVAX must have already been exported from the P/C-Chain.
  1746  // Returns the ID of the newly created atomic transaction
  1747  func (s *Service) Import(_ *http.Request, args *ImportArgs, reply *api.JSONTxID) error {
  1748  	s.vm.ctx.Log.Warn("deprecated API called",
  1749  		zap.String("service", "avm"),
  1750  		zap.String("method", "import"),
  1751  		logging.UserString("username", args.Username),
  1752  	)
  1753  
  1754  	tx, err := s.buildImport(args)
  1755  	if err != nil {
  1756  		return err
  1757  	}
  1758  
  1759  	txID, err := s.vm.issueTxFromRPC(tx)
  1760  	if err != nil {
  1761  		return fmt.Errorf("problem issuing transaction: %w", err)
  1762  	}
  1763  
  1764  	reply.TxID = txID
  1765  	return nil
  1766  }
  1767  
  1768  func (s *Service) buildImport(args *ImportArgs) (*txs.Tx, error) {
  1769  	chainID, err := s.vm.ctx.BCLookup.Lookup(args.SourceChain)
  1770  	if err != nil {
  1771  		return nil, fmt.Errorf("problem parsing chainID %q: %w", args.SourceChain, err)
  1772  	}
  1773  
  1774  	to, err := avax.ParseServiceAddress(s.vm, args.To)
  1775  	if err != nil {
  1776  		return nil, fmt.Errorf("problem parsing to address %q: %w", args.To, err)
  1777  	}
  1778  
  1779  	s.vm.ctx.Lock.Lock()
  1780  	defer s.vm.ctx.Lock.Unlock()
  1781  
  1782  	utxos, kc, err := s.vm.LoadUser(args.Username, args.Password, nil)
  1783  	if err != nil {
  1784  		return nil, err
  1785  	}
  1786  
  1787  	atomicUTXOs, _, _, err := avax.GetAtomicUTXOs(
  1788  		s.vm.ctx.SharedMemory,
  1789  		s.vm.parser.Codec(),
  1790  		chainID,
  1791  		kc.Addrs,
  1792  		ids.ShortEmpty,
  1793  		ids.Empty,
  1794  		int(maxPageSize),
  1795  	)
  1796  	if err != nil {
  1797  		return nil, fmt.Errorf("problem retrieving user's atomic UTXOs: %w", err)
  1798  	}
  1799  
  1800  	amountsSpent, importInputs, importKeys, err := s.vm.SpendAll(atomicUTXOs, kc)
  1801  	if err != nil {
  1802  		return nil, err
  1803  	}
  1804  
  1805  	ins := []*avax.TransferableInput{}
  1806  	keys := [][]*secp256k1.PrivateKey{}
  1807  
  1808  	if amountSpent := amountsSpent[s.vm.feeAssetID]; amountSpent < s.vm.TxFee {
  1809  		var localAmountsSpent map[ids.ID]uint64
  1810  		localAmountsSpent, ins, keys, err = s.vm.Spend(
  1811  			utxos,
  1812  			kc,
  1813  			map[ids.ID]uint64{
  1814  				s.vm.feeAssetID: s.vm.TxFee - amountSpent,
  1815  			},
  1816  		)
  1817  		if err != nil {
  1818  			return nil, err
  1819  		}
  1820  		for asset, amount := range localAmountsSpent {
  1821  			newAmount, err := safemath.Add64(amountsSpent[asset], amount)
  1822  			if err != nil {
  1823  				return nil, fmt.Errorf("problem calculating required spend amount: %w", err)
  1824  			}
  1825  			amountsSpent[asset] = newAmount
  1826  		}
  1827  	}
  1828  
  1829  	// Because we ensured that we had enough inputs for the fee, we can
  1830  	// safely just remove it without concern for underflow.
  1831  	amountsSpent[s.vm.feeAssetID] -= s.vm.TxFee
  1832  
  1833  	keys = append(keys, importKeys...)
  1834  
  1835  	outs := []*avax.TransferableOutput{}
  1836  	for assetID, amount := range amountsSpent {
  1837  		if amount > 0 {
  1838  			outs = append(outs, &avax.TransferableOutput{
  1839  				Asset: avax.Asset{ID: assetID},
  1840  				Out: &secp256k1fx.TransferOutput{
  1841  					Amt: amount,
  1842  					OutputOwners: secp256k1fx.OutputOwners{
  1843  						Locktime:  0,
  1844  						Threshold: 1,
  1845  						Addrs:     []ids.ShortID{to},
  1846  					},
  1847  				},
  1848  			})
  1849  		}
  1850  	}
  1851  	avax.SortTransferableOutputs(outs, s.vm.parser.Codec())
  1852  
  1853  	tx := &txs.Tx{Unsigned: &txs.ImportTx{
  1854  		BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{
  1855  			NetworkID:    s.vm.ctx.NetworkID,
  1856  			BlockchainID: s.vm.ctx.ChainID,
  1857  			Outs:         outs,
  1858  			Ins:          ins,
  1859  		}},
  1860  		SourceChain: chainID,
  1861  		ImportedIns: importInputs,
  1862  	}}
  1863  	return tx, tx.SignSECP256K1Fx(s.vm.parser.Codec(), keys)
  1864  }
  1865  
  1866  // ExportArgs are arguments for passing into ExportAVA requests
  1867  type ExportArgs struct {
  1868  	// User, password, from addrs, change addr
  1869  	api.JSONSpendHeader
  1870  	// Amount of nAVAX to send
  1871  	Amount avajson.Uint64 `json:"amount"`
  1872  
  1873  	// Chain the funds are going to. Optional. Used if To address does not include the chainID.
  1874  	TargetChain string `json:"targetChain"`
  1875  
  1876  	// ID of the address that will receive the AVAX. This address may include the
  1877  	// chainID, which is used to determine what the destination chain is.
  1878  	To string `json:"to"`
  1879  
  1880  	AssetID string `json:"assetID"`
  1881  }
  1882  
  1883  // Export sends an asset from this chain to the P/C-Chain.
  1884  // After this tx is accepted, the AVAX must be imported to the P/C-chain with an importTx.
  1885  // Returns the ID of the newly created atomic transaction
  1886  func (s *Service) Export(_ *http.Request, args *ExportArgs, reply *api.JSONTxIDChangeAddr) error {
  1887  	s.vm.ctx.Log.Warn("deprecated API called",
  1888  		zap.String("service", "avm"),
  1889  		zap.String("method", "export"),
  1890  		logging.UserString("username", args.Username),
  1891  	)
  1892  
  1893  	tx, changeAddr, err := s.buildExport(args)
  1894  	if err != nil {
  1895  		return err
  1896  	}
  1897  
  1898  	txID, err := s.vm.issueTxFromRPC(tx)
  1899  	if err != nil {
  1900  		return fmt.Errorf("problem issuing transaction: %w", err)
  1901  	}
  1902  
  1903  	reply.TxID = txID
  1904  	reply.ChangeAddr, err = s.vm.FormatLocalAddress(changeAddr)
  1905  	return err
  1906  }
  1907  
  1908  func (s *Service) buildExport(args *ExportArgs) (*txs.Tx, ids.ShortID, error) {
  1909  	// Parse the asset ID
  1910  	assetID, err := s.vm.lookupAssetID(args.AssetID)
  1911  	if err != nil {
  1912  		return nil, ids.ShortEmpty, err
  1913  	}
  1914  
  1915  	// Get the chainID and parse the to address
  1916  	chainID, to, err := s.vm.ParseAddress(args.To)
  1917  	if err != nil {
  1918  		chainID, err = s.vm.ctx.BCLookup.Lookup(args.TargetChain)
  1919  		if err != nil {
  1920  			return nil, ids.ShortEmpty, err
  1921  		}
  1922  		to, err = ids.ShortFromString(args.To)
  1923  		if err != nil {
  1924  			return nil, ids.ShortEmpty, err
  1925  		}
  1926  	}
  1927  
  1928  	if args.Amount == 0 {
  1929  		return nil, ids.ShortEmpty, errZeroAmount
  1930  	}
  1931  
  1932  	// Parse the from addresses
  1933  	fromAddrs, err := avax.ParseServiceAddresses(s.vm, args.From)
  1934  	if err != nil {
  1935  		return nil, ids.ShortEmpty, err
  1936  	}
  1937  
  1938  	s.vm.ctx.Lock.Lock()
  1939  	defer s.vm.ctx.Lock.Unlock()
  1940  
  1941  	// Get the UTXOs/keys for the from addresses
  1942  	utxos, kc, err := s.vm.LoadUser(args.Username, args.Password, fromAddrs)
  1943  	if err != nil {
  1944  		return nil, ids.ShortEmpty, err
  1945  	}
  1946  
  1947  	// Parse the change address.
  1948  	if len(kc.Keys) == 0 {
  1949  		return nil, ids.ShortEmpty, errNoKeys
  1950  	}
  1951  	changeAddr, err := s.vm.selectChangeAddr(kc.Keys[0].PublicKey().Address(), args.ChangeAddr)
  1952  	if err != nil {
  1953  		return nil, ids.ShortEmpty, err
  1954  	}
  1955  
  1956  	amounts := map[ids.ID]uint64{}
  1957  	if assetID == s.vm.feeAssetID {
  1958  		amountWithFee, err := safemath.Add64(uint64(args.Amount), s.vm.TxFee)
  1959  		if err != nil {
  1960  			return nil, ids.ShortEmpty, fmt.Errorf("problem calculating required spend amount: %w", err)
  1961  		}
  1962  		amounts[s.vm.feeAssetID] = amountWithFee
  1963  	} else {
  1964  		amounts[s.vm.feeAssetID] = s.vm.TxFee
  1965  		amounts[assetID] = uint64(args.Amount)
  1966  	}
  1967  
  1968  	amountsSpent, ins, keys, err := s.vm.Spend(utxos, kc, amounts)
  1969  	if err != nil {
  1970  		return nil, ids.ShortEmpty, err
  1971  	}
  1972  
  1973  	exportOuts := []*avax.TransferableOutput{{
  1974  		Asset: avax.Asset{ID: assetID},
  1975  		Out: &secp256k1fx.TransferOutput{
  1976  			Amt: uint64(args.Amount),
  1977  			OutputOwners: secp256k1fx.OutputOwners{
  1978  				Locktime:  0,
  1979  				Threshold: 1,
  1980  				Addrs:     []ids.ShortID{to},
  1981  			},
  1982  		},
  1983  	}}
  1984  
  1985  	outs := []*avax.TransferableOutput{}
  1986  	for assetID, amountSpent := range amountsSpent {
  1987  		amountToSend := amounts[assetID]
  1988  		if amountSpent > amountToSend {
  1989  			outs = append(outs, &avax.TransferableOutput{
  1990  				Asset: avax.Asset{ID: assetID},
  1991  				Out: &secp256k1fx.TransferOutput{
  1992  					Amt: amountSpent - amountToSend,
  1993  					OutputOwners: secp256k1fx.OutputOwners{
  1994  						Locktime:  0,
  1995  						Threshold: 1,
  1996  						Addrs:     []ids.ShortID{changeAddr},
  1997  					},
  1998  				},
  1999  			})
  2000  		}
  2001  	}
  2002  
  2003  	codec := s.vm.parser.Codec()
  2004  	avax.SortTransferableOutputs(outs, codec)
  2005  
  2006  	tx := &txs.Tx{Unsigned: &txs.ExportTx{
  2007  		BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{
  2008  			NetworkID:    s.vm.ctx.NetworkID,
  2009  			BlockchainID: s.vm.ctx.ChainID,
  2010  			Outs:         outs,
  2011  			Ins:          ins,
  2012  		}},
  2013  		DestinationChain: chainID,
  2014  		ExportedOuts:     exportOuts,
  2015  	}}
  2016  	return tx, changeAddr, tx.SignSECP256K1Fx(codec, keys)
  2017  }