Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pack NFT Contracts #2

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions contracts/IPackNFT.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import Crypto
import NonFungibleToken from "./NonFungibleToken.cdc"


pub contract interface IPackNFT{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IPackNFT can be shared by all experiences? I mean we just need to deploy this contract once for all experiences?

/// StoragePath for Collection Resource
///
pub let collectionStoragePath: StoragePath
/// PublicPath expected for deposit
///
pub let collectionPublicPath: PublicPath
/// PublicPath for receiving PackNFT
///
pub let collectionIPackNFTPublicPath: PublicPath
/// StoragePath for the PackNFT Operator Resource (issuer owns this)
///
pub let operatorStoragePath: StoragePath
/// PrivatePath to share IOperator interfaces with Operator (typically with PDS account)
///
pub let operatorPrivPath: PrivatePath
/// Request for Reveal
///
pub event RevealRequest(id: UInt64, openRequest: Bool)
/// Request for Open
///
/// This is emitted when owner of a PackNFT request for the entitled NFT to be
/// deposited to its account
pub event OpenRequest(id: UInt64)
/// New Pack NFT
///
/// Emitted when a new PackNFT has been minted
pub event Mint(id: UInt64, commitHash: String, distId: UInt64 )
/// Revealed
///
/// Emitted when a packNFT has been revealed
pub event Revealed(id: UInt64, salt: String, nfts: String)
/// Opened
///
/// Emitted when a packNFT has been opened
pub event Opened(id: UInt64)

pub enum Status: UInt8 {
pub case Sealed
pub case Revealed
pub case Opened
}

pub struct interface Collectible {
pub let address: Address
pub let contractName: String
pub let id: UInt64
pub fun hashString(): String
init(address: Address, contractName: String, id: UInt64)
}

pub resource interface IPack {
pub let commitHash: String
pub let issuer: Address
pub var status: Status
pub var salt: String?

pub fun verify(nftString: String): Bool

access(contract) fun reveal(id: UInt64, nfts: [{IPackNFT.Collectible}], salt: String)
access(contract) fun open(id: UInt64, nfts: [{IPackNFT.Collectible}])
init(commitHash: String, issuer: Address)
}

pub resource interface IOperator {
pub fun mint(distId: UInt64, commitHash: String, issuer: Address): @NFT
pub fun reveal(id: UInt64, nfts: [{Collectible}], salt: String)
pub fun open(id: UInt64, nfts: [{IPackNFT.Collectible}])
}
pub resource PackNFTOperator: IOperator {
pub fun mint(distId: UInt64, commitHash: String, issuer: Address): @NFT
pub fun reveal(id: UInt64, nfts: [{Collectible}], salt: String)
pub fun open(id: UInt64, nfts: [{IPackNFT.Collectible}])
}

pub resource interface IPackNFTToken {
pub let id: UInt64
pub let commitHash: String
pub let issuer: Address
}

pub resource NFT: NonFungibleToken.INFT, IPackNFTToken, IPackNFTOwnerOperator{
pub let id: UInt64
pub let commitHash: String
pub let issuer: Address
pub fun reveal(openRequest: Bool)
pub fun open()
}

pub resource interface IPackNFTOwnerOperator{
pub fun reveal(openRequest: Bool)
pub fun open()
}

pub resource interface IPackNFTCollectionPublic {
pub fun deposit(token: @NonFungibleToken.NFT)
pub fun getIDs(): [UInt64]
pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT
pub fun borrowPackNFT(id: UInt64): &IPackNFT.NFT? {
// If the result isn't nil, the id of the returned reference
// should be the same as the argument to the function
post {
(result == nil) || (result!.id == id):
"Cannot borrow PackNFT reference: The ID of the returned reference is incorrect"
}
}
}

access(contract) fun revealRequest(id: UInt64, openRequest: Bool)
access(contract) fun openRequest(id: UInt64)
pub fun publicReveal(id: UInt64, nfts: [{IPackNFT.Collectible}], salt: String)
}
248 changes: 248 additions & 0 deletions contracts/NFLPack.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
import Crypto
import NonFungibleToken from 0x{{.NonFungibleToken}}
import IPackNFT from 0x{{.IPackNFT}}

pub contract PackNFT: NonFungibleToken, IPackNFT {

pub var totalSupply: UInt64
pub let version: String
pub let collectionStoragePath: StoragePath
pub let collectionPublicPath: PublicPath
pub let collectionIPackNFTPublicPath: PublicPath
pub let operatorStoragePath: StoragePath
pub let operatorPrivPath: PrivatePath

// representation of the NFT in this contract to keep track of states
access(contract) let packs: @{UInt64: Pack}

pub event RevealRequest(id: UInt64, openRequest: Bool)
pub event OpenRequest(id: UInt64)
pub event Revealed(id: UInt64, salt: String, nfts: String)
pub event Opened(id: UInt64)
pub event Mint(id: UInt64, commitHash: String, distId: UInt64)
pub event ContractInitialized()
pub event Withdraw(id: UInt64, from: Address?)
pub event Deposit(id: UInt64, to: Address?)

pub enum Status: UInt8 {
pub case Sealed
pub case Revealed
pub case Opened
}

pub resource PackNFTOperator: IPackNFT.IOperator {

pub fun mint(distId: UInt64, commitHash: String, issuer: Address): @NFT{
let id = PackNFT.totalSupply
let nft <- create NFT(initID: id, commitHash: commitHash, issuer: issuer)
PackNFT.totalSupply = id + 1
let p <-create Pack(commitHash: commitHash, issuer: issuer)
PackNFT.packs[id] <-! p
emit Mint(id: id, commitHash: commitHash, distId: distId)
return <- nft
}

pub fun reveal(id: UInt64, nfts: [{IPackNFT.Collectible}], salt: String) {
let p <- PackNFT.packs.remove(key: id) ?? panic("no such pack")
p.reveal(id: id, nfts: nfts, salt: salt)
PackNFT.packs[id] <-! p
}

pub fun open(id: UInt64, nfts: [{IPackNFT.Collectible}]) {
let p <- PackNFT.packs.remove(key: id) ?? panic("no such pack")
p.open(id: id, nfts: nfts)
PackNFT.packs[id] <-! p
}

init(){}
}

pub resource Pack {
pub let commitHash: String
pub let issuer: Address
pub var status: PackNFT.Status
pub var salt: String?

pub fun verify(nftString: String): Bool {
assert(self.status != PackNFT.Status.Sealed, message: "Pack not revealed yet")
var hashString = self.salt!
hashString = hashString.concat(",").concat(nftString)
let hash = HashAlgorithm.SHA2_256.hash(hashString.utf8)
assert(self.commitHash == String.encodeHex(hash), message: "CommitHash was not verified")
return true
}

access(self) fun _verify(nfts: [{IPackNFT.Collectible}], salt: String, commitHash: String): String {
var hashString = salt
var nftString = nfts[0].hashString()
var i = 1
while i < nfts.length {
let s = nfts[i].hashString()
nftString = nftString.concat(",").concat(s)
i = i + 1
}
hashString = hashString.concat(",").concat(nftString)
let hash = HashAlgorithm.SHA2_256.hash(hashString.utf8)
assert(self.commitHash == String.encodeHex(hash), message: "CommitHash was not verified")
return nftString
}

access(contract) fun reveal(id: UInt64, nfts: [{IPackNFT.Collectible}], salt: String) {
assert(self.status == PackNFT.Status.Sealed, message: "Pack status is not Sealed")
let v = self._verify(nfts: nfts, salt: salt, commitHash: self.commitHash)
self.salt = salt
self.status = PackNFT.Status.Revealed
emit Revealed(id: id, salt: salt, nfts: v)
}

access(contract) fun open(id: UInt64, nfts: [{IPackNFT.Collectible}]) {
assert(self.status == PackNFT.Status.Revealed, message: "Pack status is not Revealed")
self._verify(nfts: nfts, salt: self.salt!, commitHash: self.commitHash)
self.status = PackNFT.Status.Opened
emit Opened(id: id)
}

init(commitHash: String, issuer: Address) {
self.commitHash = commitHash
self.issuer = issuer
self.status = PackNFT.Status.Sealed
self.salt = nil
}
}

pub resource NFT: NonFungibleToken.INFT, IPackNFT.IPackNFTToken, IPackNFT.IPackNFTOwnerOperator {
pub let id: UInt64
pub let commitHash: String
pub let issuer: Address

pub fun reveal(openRequest: Bool){
PackNFT.revealRequest(id: self.id, openRequest: openRequest)
}

pub fun open(){
PackNFT.openRequest(id: self.id)
}

init(initID: UInt64, commitHash: String, issuer: Address ) {
self.id = initID
self.commitHash = commitHash
self.issuer = issuer
}

}

pub resource Collection:
NonFungibleToken.Provider,
NonFungibleToken.Receiver,
NonFungibleToken.CollectionPublic,
IPackNFT.IPackNFTCollectionPublic
{
// dictionary of NFT conforming tokens
// NFT is a resource type with an `UInt64` ID field
pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}

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

// withdraw removes an NFT from the collection and moves it to the caller
pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
emit Withdraw(id: token.id, from: self.owner?.address)
return <- token
}

// deposit takes a NFT and adds it to the collections dictionary
// and adds the ID to the id array
pub fun deposit(token: @NonFungibleToken.NFT) {
let token <- token as! @PackNFT.NFT

let id: UInt64 = token.id

// add the new token to the dictionary which removes the old one
let oldToken <- self.ownedNFTs[id] <- token
emit Deposit(id: id, to: self.owner?.address)

destroy oldToken
}

// getIDs returns an array of the IDs that are in the collection
pub fun getIDs(): [UInt64] {
return self.ownedNFTs.keys
}

// borrowNFT gets a reference to an NFT in the collection
// so that the caller can read its metadata and call its methods
pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT {
return &self.ownedNFTs[id] as &NonFungibleToken.NFT
}

pub fun borrowPackNFT(id: UInt64): &IPackNFT.NFT? {
let nft<- self.ownedNFTs.remove(key: id) ?? panic("missing NFT")
let token <- nft as! @PackNFT.NFT
let ref = &token as &IPackNFT.NFT
self.ownedNFTs[id] <-! token as! @PackNFT.NFT
return ref
}

destroy() {
destroy self.ownedNFTs
}
}

access(contract) fun revealRequest(id: UInt64, openRequest: Bool ) {
let p = PackNFT.borrowPackRepresentation(id: id) ?? panic ("No such pack")
assert(p.status == PackNFT.Status.Sealed, message: "Pack status must be Sealed for reveal request")
emit RevealRequest(id: id, openRequest: openRequest)
}

access(contract) fun openRequest(id: UInt64) {
let p = PackNFT.borrowPackRepresentation(id: id) ?? panic ("No such pack")
assert(p.status == PackNFT.Status.Revealed, message: "Pack status must be Revealed for open request")
emit OpenRequest(id: id)
}

pub fun publicReveal(id: UInt64, nfts: [{IPackNFT.Collectible}], salt: String) {
let p = PackNFT.borrowPackRepresentation(id: id) ?? panic ("No such pack")
p.reveal(id: id, nfts: nfts, salt: salt)
}

pub fun borrowPackRepresentation(id: UInt64): &Pack? {
return &self.packs[id] as &Pack
}

pub fun createEmptyCollection(): @NonFungibleToken.Collection {
return <- create Collection()
}

init(
collectionStoragePath: StoragePath,
collectionPublicPath: PublicPath,
collectionIPackNFTPublicPath: PublicPath,
operatorStoragePath: StoragePath,
operatorPrivPath: PrivatePath,
version: String
){
self.totalSupply = 0
self.packs <- {}
self.collectionStoragePath = collectionStoragePath
self.collectionPublicPath = collectionPublicPath
self.collectionIPackNFTPublicPath = collectionIPackNFTPublicPath
self.operatorStoragePath = operatorStoragePath
self.operatorPrivPath = operatorPrivPath
self.version = version

// Create a collection to receive Pack NFTs
let collection <- create Collection()
self.account.save(<-collection, to: self.collectionStoragePath)
self.account.link<&Collection{NonFungibleToken.CollectionPublic}>(self.collectionPublicPath, target: self.collectionStoragePath)
self.account.link<&Collection{IPackNFT.IPackNFTCollectionPublic}>(self.collectionIPackNFTPublicPath, target: self.collectionStoragePath)

// Create a operator to share mint capability with proxy
let operator <- create PackNFTOperator()
self.account.save(<-operator, to: self.operatorStoragePath)
self.account.link<&PackNFTOperator{IPackNFT.IOperator}>(self.operatorPrivPath, target: self.operatorStoragePath)
}

}