onflow / freshmint

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Claim sale does not distribute in the minted order

psiemens opened this issue · comments

When creating a ClaimSale instance from a newly-minted collection, the items are not claimed in the order in which they are minted.

Some notes on the performance of the new Collection implementation I'm testing out in #123:

Freshmint users need to be able to distribute NFTs in the same order in which they are minted, so I've been experimenting with a new implementation of NonFungibleToken.Collection that preserves insertion order using an additional array field. I was worried about the performance so I did some benchmarking on testnet.

The results were surprising! I minted up to 10K NFTs and looked at the computation required to withdraw the first NFT returned by collection.getIDs(). The ordered collection drastically outperforms the standard collection when you remove the first NFT in the ID list. I imagine this has to do with the underlying tree implementation for arrays and maps in Cadence.

When withdrawing the NFTs not in order, the ordered collection is expectedly slower than the standard collection, likely due to the additional overhead of having to remove the ID from the ids list.

Freshmint users need to be able to distribute NFTs in the same order in which they are minted.

I'm not set on using a Collection to satisfy this requirement, but I wanted to try, because I think it'd be really nice to give developers the flexibility to transfer individual NFTs from the same collection they use for their drop (e.g. for gifting or airdrops). The other option is to put all the minted NFTs in an array and just pop off the top.

Another added benefit of the ordered collection is that it allows an on-chain query to return NFTs to users in the order in which they acquired them, which IMO is more intuitive than relying on the ordering that Cadence chooses for dictionary keys (which isn't random, but definitely feels random to a user).

Freshmint NFT Collection_ Withdraw First NFT (Testnet)
Freshmint NFT Collection_ Withdraw Random NFT (Testnet)


Simplified code showing the different implementations:

Standard Collection (the one we know and love)

pub resource StandardCollection: NonFungibleToken.Collection {
  
  pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}

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

  pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
    let token <- self.ownedNFTs.remove(key: withdrawID)!

    emit Withdraw(id: token.id, from: self.owner?.address)

    return <- token
  }

  pub fun deposit(token: @NonFungibleToken.NFT) {
    let token <- token as! @Foo.NFT

    let id: UInt64 = token.id

    let oldToken <- self.ownedNFTs[id] <- token
    destroy oldToken

    emit Deposit(id: id, to: self.owner?.address)
  }

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

Ordered Collection

pub resource OrderedCollection: NonFungibleToken.Collection {
  
  /// An array of all NFT IDs in this collection in insertion order
  ///
  pub let ids: [UInt64]

  pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}

  init () {
    self.ids = []
    self.ownedNFTs <- {}
  }

  pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
    let token <- self.ownedNFTs.remove(key: withdrawID)!

    // Remove the NFT ID from the IDs list
    let index = self.ids.firstIndex(of: token.id)!
    self.ids.remove(at: index)

    emit Withdraw(id: token.id, from: self.owner?.address)

    return <- token
  }

  pub fun deposit(token: @NonFungibleToken.NFT) {
    let token <- token as! @Foo3.NFT

    let id: UInt64 = token.id

    let oldToken <- self.ownedNFTs[id] <- token
    destroy oldToken

    // Add the NFT ID to the end of the IDs list
    self.ids.append(id)

    emit Deposit(id: id, to: self.owner?.address)
  }

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

Implemented in #143