-
Notifications
You must be signed in to change notification settings - Fork 2
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
EricLin2004
wants to merge
2
commits into
main
Choose a base branch
from
elin/packNFT-contract
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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{ | ||
/// 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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
|
||
} | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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?