Recursos aninhados

Ao armazenar recursos em nossa conta, é irritante (e muito inconveniente) armazenar cada recurso em um caminho de armazenamento diferente.

Imagine se você comprasse 100 NFTs e tivesse que armazenar cada um separadamente, e depois lembrar onde eles estavam para poder acessá-los no futuro. Não é bom.

Em vez disso, podemos definir um recurso wrapper (por exemplo, “Coleção”) que podemos armazenar em 1 caminho de armazenamento e que armazena recursos aninhados (“NFT”).

cadence
		
			// Contract file: Test.cdc
// Deployed to 0x01
pub contract Test {

   pub resource NFT {
      pub let id: UInt64
      pub let rarity: String
      pub var name: String
      
      init(rarity: String, name: String) {
         self.id = self.uuid
         self.rarity = rarity
         self.name = name
      }
   }

   pub resource interface ICollection {
      pub fun deposit(token: @NFT)
      pub fun getIDs(): [UInt64]
      pub fun borrowNFT(id: UInt64): &NFT?
   }

   pub resource Collection: ICollection {
      pub let ownedNFTs: @{UInt64: NFT}

      pub fun deposit(token: @NFT) {
         self.ownedNFTs[token.id] <-! token
      }

      pub fun withdraw(withdrawID: UInt64): @NFT {
         let nft <- self.ownedNFTs.remove(key: withdrawID) ?? panic("NFT does not exist.")
         return <- nft
      }

      pub fun borrowNFT(id: UInt64): &NFT? {
         return &self.ownedNFTs[id] as &NFT?
      }

      pub fun getIDs(): [UInt64] {
         return self.ownedNFTs.keys
      }

      init() {
         self.ownedNFTs <- {}
      }

      // when a resource stores nested resources, you must
      // define a custom destroy function that defines what 
      // to do with the nested resources when the wrapper
      // (Collection) is destroyed
      // it gets automatically run when the Collection
      // resource is destroyed
      destroy() {
         destroy self.ownedNFTs
      }
   }

   pub fun createNFT(rarity: String, name: String): @NFT {
      let nft <- create NFT(rarity: rarity, name: name)
      return <- nft
   }

   pub fun createCollection(): @Collection {
      return <- create Collection()
   }

}
		 
	
cadence
		
			// Transaction file: create_collection.cdc
import Test from 0x01

transaction() {

   prepare(signer: AuthAccount) {
      // moves the Collection resource into storage at `/storage/MyCollection`
      signer.save(<- Test.createCollection(), to: /storage/MyCollection)

      // Creates a capability at `/public/MyCollection` for anyone to borrow.
      // 
      // Notice we restrict the linked type to `&Collection{ICollection}`, so the public
      // won't be able to call our `withdraw` function since it's not in the
      // `ICollection` interface.
      signer.link<&Test.Collection{Test.ICollection}>(/public/MyCollection, target: /storage/MyCollection)
   }

   execute {
      
   }
}
		 
	
cadence
		
			// Transaction file: store_nft.cdc
import Test from 0x01

transaction(rarity: String, name: String) {

   let Collection: &Test.Collection

   prepare(signer: AuthAccount) {
      self.Collection = signer.borrow<&Test.Collection>(from: /storage/MyCollection) 
                           ?? panic("Signer does not have a Collection set up.")
   }

   execute {
      let nft <- Test.createNFT(rarity: rarity, name: name)
      
      self.Collection.deposit(token: <- nft)
   }
}
		 
	
cadence
		
			// Script file: read_nfts.cdc
import Test from 0x01

pub fun main(owner: Address): [NFTData] {
   // PublicAccount is a type that lets us borrow public
   // capabilities on that account's storage
   let ownerPublicAccount: PublicAccount = getAccount(owner)

   let collectionCapability: Capability<&Test.Collection{Test.ICollection}> = ownerPublicAccount.getCapability<&Test.Collection{Test.ICollection}>(/public/MyCollection)

   let collectionRef: &Test.Collection{Test.ICollection} = collectionCapability.borrow() ?? panic("Not a valid Collection capability.")

   /*
    INVALID: `withdraw` is not accessible on the restricted type.
    NOTE: Calling withdraw in this script wouldn't do anything
    because scripts don't modify data. But you can imagine
    allowing this to be called in a transaction would be a problem.
   
    collectionRef.withdraw(withdrawID: 10)
   */

   let result: [NFTData] = []
   for id in collectionRef.getIDs() {
      let nft: &Test.NFT = collectionRef.borrowNFT(id: id)!
      result.append(NFTData(nftRef.id, nftRef.rarity, nftRef.name))
   }

   return result
}

pub struct NFTData {
   pub let id: UInt64
   pub let rarity: String
   pub let name: String

   init(_ i: UInt64, _ r: String, _ n: String) {
      self.id = i
      self.rarity = r
      self.name = n
   }
}