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 }