Smart Contract

TraeYoungDiamondHands

A.bb39f0dae1547256.TraeYoungDiamondHands

Deployed

1d ago
Feb 26, 2026, 09:44:00 PM UTC

Dependents

0 imports
1// CREATED BY: Touchstone (https://touchstone.city/), a platform crafted by your best friends at Emerald City DAO (https://ecdao.org/).
2// STATEMENT: This contract promises to keep the 5% royalty off of primary sales and 2.5% off of secondary sales to Emerald City DAO or risk permanent suspension from participation in the DAO and its tools.
3
4import NonFungibleToken from 0x1d7e57aa55817448
5import MetadataViews from 0x1d7e57aa55817448 
6import FungibleToken from 0xf233dcee88fe0abe
7import FlowToken from 0x1654653399040a61
8import MintVerifiers from 0x7a696d6136e1dce2 
9import FUSD from 0x3c5959b568896393
10import EmeraldPass from 0x6a07dbeb03167a13
11
12pub contract TraeYoungDiamondHands: NonFungibleToken {
13
14	// Collection Information
15	access(self) let collectionInfo: {String: AnyStruct}
16
17	// Contract Information
18	pub var nextMetadataId: UInt64
19	pub var totalSupply: UInt64
20
21	// Events
22	pub event ContractInitialized()
23	pub event Withdraw(id: UInt64, from: Address?)
24	pub event Deposit(id: UInt64, to: Address?)
25	pub event TouchstonePurchase(id: UInt64, recipient: Address, metadataId: UInt64, name: String, description: String, image: MetadataViews.IPFSFile, price: UFix64)
26	pub event Minted(id: UInt64, recipient: Address, metadataId: UInt64)
27	pub event MintBatch(metadataIds: [UInt64], recipients: [Address])
28
29	// Paths
30	pub let CollectionStoragePath: StoragePath
31	pub let CollectionPublicPath: PublicPath
32	pub let CollectionPrivatePath: PrivatePath
33	pub let AdministratorStoragePath: StoragePath
34
35	// Maps metadataId of NFT to NFTMetadata
36	access(account) let metadatas: {UInt64: NFTMetadata}
37
38	// Maps the metadataId of an NFT to the primary buyer
39	//
40	// You can also get a list of purchased NFTs
41	// by doing `primaryBuyers.keys`
42	access(account) let primaryBuyers: {UInt64: Address}
43
44	access(account) let nftStorage: @{Address: {UInt64: NFT}}
45
46	pub struct NFTMetadata {
47		pub let metadataId: UInt64
48		pub let name: String
49		pub let description: String 
50		// The main image of the NFT
51		pub let image: MetadataViews.IPFSFile
52		// An optional thumbnail that can go along with it
53		// for easier loading
54		pub let thumbnail: MetadataViews.IPFSFile?
55		// If price is nil, defaults to the collection price
56		pub let price: UFix64?
57		pub var extra: {String: AnyStruct}
58
59		init(_name: String, _description: String, _image: MetadataViews.IPFSFile, _thumbnail: MetadataViews.IPFSFile?, _price: UFix64?, _extra: {String: AnyStruct}) {
60			self.metadataId = TraeYoungDiamondHands.nextMetadataId
61			self.name = _name
62			self.description = _description
63			self.image = _image
64			self.thumbnail = _thumbnail
65			self.price = _price
66			self.extra = _extra
67
68			TraeYoungDiamondHands.nextMetadataId = TraeYoungDiamondHands.nextMetadataId + 1
69		}
70	}
71
72	pub resource NFT: NonFungibleToken.INFT, MetadataViews.Resolver {
73		// The 'id' is the same as the 'uuid'
74		pub let id: UInt64
75		// The 'metadataId' is what maps this NFT to its 'NFTMetadata'
76		pub let metadataId: UInt64
77
78		pub fun getMetadata(): NFTMetadata {
79			return TraeYoungDiamondHands.getNFTMetadata(self.metadataId)!
80		}
81
82		pub fun getViews(): [Type] {
83			return [
84				Type<MetadataViews.Display>(),
85				Type<MetadataViews.ExternalURL>(),
86				Type<MetadataViews.NFTCollectionData>(),
87				Type<MetadataViews.NFTCollectionDisplay>(),
88				Type<MetadataViews.Royalties>(),
89				Type<MetadataViews.Serial>(),
90				Type<MetadataViews.Traits>(),
91				Type<MetadataViews.NFTView>()
92			]
93		}
94
95		pub fun resolveView(_ view: Type): AnyStruct? {
96			switch view {
97				case Type<MetadataViews.Display>():
98					let metadata = self.getMetadata()
99					return MetadataViews.Display(
100						name: metadata.name,
101						description: metadata.description,
102						thumbnail: metadata.thumbnail ?? metadata.image
103					)
104				case Type<MetadataViews.NFTCollectionData>():
105					return MetadataViews.NFTCollectionData(
106						storagePath: TraeYoungDiamondHands.CollectionStoragePath,
107						publicPath: TraeYoungDiamondHands.CollectionPublicPath,
108						providerPath: TraeYoungDiamondHands.CollectionPrivatePath,
109						publicCollection: Type<&Collection{NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, MetadataViews.ResolverCollection}>(),
110						publicLinkedType: Type<&Collection{NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, MetadataViews.ResolverCollection}>(),
111						providerLinkedType: Type<&Collection{NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, MetadataViews.ResolverCollection, NonFungibleToken.Provider}>(),
112						createEmptyCollectionFunction: (fun (): @NonFungibleToken.Collection {
113								return <- TraeYoungDiamondHands.createEmptyCollection()
114						})
115					)
116				case Type<MetadataViews.ExternalURL>():
117          return MetadataViews.ExternalURL("https://touchstone.city/discover/".concat(self.owner!.address.toString()).concat("/TraeYoungDiamondHands"))
118				case Type<MetadataViews.NFTCollectionDisplay>():
119					let squareMedia = MetadataViews.Media(
120						file: TraeYoungDiamondHands.getCollectionAttribute(key: "image") as! MetadataViews.IPFSFile,
121						mediaType: "image"
122					)
123
124					// If a banner image exists, use it
125					// Otherwise, default to the main square image
126					var bannerMedia: MetadataViews.Media? = nil
127					if let bannerImage = TraeYoungDiamondHands.getOptionalCollectionAttribute(key: "bannerImage") as! MetadataViews.IPFSFile? {
128						bannerMedia = MetadataViews.Media(
129							file: bannerImage,
130							mediaType: "image"
131						)
132					}
133					return MetadataViews.NFTCollectionDisplay(
134						name: TraeYoungDiamondHands.getCollectionAttribute(key: "name") as! String,
135						description: TraeYoungDiamondHands.getCollectionAttribute(key: "description") as! String,
136						externalURL: MetadataViews.ExternalURL("https://touchstone.city/discover/".concat(self.owner!.address.toString()).concat("/TraeYoungDiamondHands")),
137						squareImage: squareMedia,
138						bannerImage: bannerMedia ?? squareMedia,
139						socials: TraeYoungDiamondHands.getCollectionAttribute(key: "socials") as! {String: MetadataViews.ExternalURL}
140					)
141				case Type<MetadataViews.Royalties>():
142					return MetadataViews.Royalties([
143						// This is for Emerald City in favor of producing Touchstone, a free platform for our users. Failure to keep this in the contract may result in permanent suspension from Emerald City.
144						MetadataViews.Royalty(
145							recepient: getAccount(0x5643fd47a29770e7).getCapability<&FlowToken.Vault{FungibleToken.Receiver}>(/public/flowTokenReceiver),
146							cut: 0.025, // 2.5% royalty on secondary sales
147							description: "Emerald City DAO receives a 2.5% royalty from secondary sales because this collection was created using Touchstone (https://touchstone.city/), a tool for creating your own NFT collections, crafted by Emerald City DAO."
148						)
149					])
150				case Type<MetadataViews.Serial>():
151					return MetadataViews.Serial(
152						self.metadataId
153					)
154				case Type<MetadataViews.Traits>():
155					return MetadataViews.dictToTraits(dict: self.getMetadata().extra, excludedNames: nil)
156				case Type<MetadataViews.NFTView>():
157					return MetadataViews.NFTView(
158						id: self.id,
159						uuid: self.uuid,
160						display: self.resolveView(Type<MetadataViews.Display>()) as! MetadataViews.Display?,
161						externalURL: self.resolveView(Type<MetadataViews.ExternalURL>()) as! MetadataViews.ExternalURL?,
162						collectionData: self.resolveView(Type<MetadataViews.NFTCollectionData>()) as! MetadataViews.NFTCollectionData?,
163						collectionDisplay: self.resolveView(Type<MetadataViews.NFTCollectionDisplay>()) as! MetadataViews.NFTCollectionDisplay?,
164						royalties: self.resolveView(Type<MetadataViews.Royalties>()) as! MetadataViews.Royalties?,
165						traits: self.resolveView(Type<MetadataViews.Traits>()) as! MetadataViews.Traits?
166					)
167			}
168			return nil
169		}
170
171		init(_metadataId: UInt64, _recipient: Address) {
172			pre {
173				TraeYoungDiamondHands.metadatas[_metadataId] != nil:
174					"This NFT does not exist yet."
175				!TraeYoungDiamondHands.primaryBuyers.containsKey(_metadataId):
176					"This NFT has already been minted."
177			}
178			self.id = self.uuid
179			self.metadataId = _metadataId
180
181			TraeYoungDiamondHands.primaryBuyers[_metadataId] = _recipient
182			TraeYoungDiamondHands.totalSupply = TraeYoungDiamondHands.totalSupply + 1
183
184			emit Minted(id: self.id, recipient: _recipient, metadataId: _metadataId)
185		}
186	}
187
188	pub resource Collection: NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection {
189		// dictionary of NFT conforming tokens
190		// NFT is a resource type with an 'UInt64' ID field
191		pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}
192
193		// withdraw removes an NFT from the collection and moves it to the caller
194		pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
195			let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
196
197			emit Withdraw(id: token.id, from: self.owner?.address)
198
199			return <-token
200		}
201
202		// deposit takes a NFT and adds it to the collections dictionary
203		// and adds the ID to the id array
204		pub fun deposit(token: @NonFungibleToken.NFT) {
205			let token <- token as! @NFT
206
207			let id: UInt64 = token.id
208
209			// add the new token to the dictionary
210			self.ownedNFTs[id] <-! token
211
212			emit Deposit(id: id, to: self.owner?.address)
213		}
214
215		// getIDs returns an array of the IDs that are in the collection
216		pub fun getIDs(): [UInt64] {
217			return self.ownedNFTs.keys
218		}
219
220		// borrowNFT gets a reference to an NFT in the collection
221		// so that the caller can read its metadata and call its methods
222		pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT {
223			return (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)!
224		}
225
226		pub fun borrowViewResolver(id: UInt64): &AnyResource{MetadataViews.Resolver} {
227			let token = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
228			let nft = token as! &NFT
229			return nft as &AnyResource{MetadataViews.Resolver}
230		}
231
232		pub fun claim() {
233			if let storage = &TraeYoungDiamondHands.nftStorage[self.owner!.address] as &{UInt64: NFT}? {
234				for id in storage.keys {
235					self.deposit(token: <- storage.remove(key: id)!)
236				}
237			}
238		}
239
240		init () {
241			self.ownedNFTs <- {}
242		}
243
244		destroy() {
245			destroy self.ownedNFTs
246		}
247	}
248
249	// A function to mint NFTs. 
250	// You can only call this function if minting
251	// is currently active.
252	pub fun mintNFT(metadataId: UInt64, recipient: &{NonFungibleToken.Receiver}, payment: @FlowToken.Vault): UInt64 {
253		pre {
254			self.canMint(): "Minting is currently closed by the Administrator!"
255			payment.balance == self.getPriceOfNFT(metadataId): 
256				"Payment does not match the price. You passed in ".concat(payment.balance.toString()).concat(" but this NFT costs ").concat(self.getPriceOfNFT(metadataId)!.toString())
257		}
258		let price: UFix64 = self.getPriceOfNFT(metadataId)!
259
260		// Confirm recipient passes all verifiers
261		for verifier in self.getMintVerifiers() {
262			let params = {"minter": recipient.owner!.address}
263			if let error = verifier.verify(params) {
264				panic(error)
265			}
266		}
267
268		// Handle Emerald City DAO royalty (5%)
269		let EmeraldCityTreasury = getAccount(0x5643fd47a29770e7).getCapability(/public/flowTokenReceiver)
270								.borrow<&FlowToken.Vault{FungibleToken.Receiver}>()!
271		let emeraldCityCut: UFix64 = 0.05 * price
272
273		// Handle royalty to user that was configured upon creation
274		if let royalty = TraeYoungDiamondHands.getOptionalCollectionAttribute(key: "royalty") as! MetadataViews.Royalty? {
275			royalty.receiver.borrow()!.deposit(from: <- payment.withdraw(amount: price * royalty.cut))
276		}
277
278		EmeraldCityTreasury.deposit(from: <- payment.withdraw(amount: emeraldCityCut))
279
280		// Give the rest to the collection owner
281		let paymentRecipient = self.account.getCapability(/public/flowTokenReceiver)
282								.borrow<&FlowToken.Vault{FungibleToken.Receiver}>()!
283		paymentRecipient.deposit(from: <- payment)
284
285		// Mint the nft 
286		let nft <- create NFT(_metadataId: metadataId, _recipient: recipient.owner!.address)
287		let nftId: UInt64 = nft.id
288		let metadata = self.getNFTMetadata(metadataId)!
289		self.collectionInfo["profit"] = (self.getCollectionAttribute(key: "profit") as! UFix64) + price
290
291		// Emit event
292		emit TouchstonePurchase(id: nftId, recipient: recipient.owner!.address, metadataId: metadataId, name: metadata.name, description: metadata.description, image: metadata.image, price: price)
293		
294		// Deposit nft
295		recipient.deposit(token: <- nft)
296
297		return nftId
298	}
299
300	pub resource Administrator {
301		pub fun createNFTMetadata(name: String, description: String, imagePath: String, thumbnailPath: String?, ipfsCID: String, price: UFix64?, extra: {String: AnyStruct}) {
302			TraeYoungDiamondHands.metadatas[TraeYoungDiamondHands.nextMetadataId] = NFTMetadata(
303				_name: name,
304				_description: description,
305				_image: MetadataViews.IPFSFile(
306					cid: ipfsCID,
307					path: imagePath
308				),
309				_thumbnail: thumbnailPath == nil ? nil : MetadataViews.IPFSFile(cid: ipfsCID, path: thumbnailPath),
310				_price: price,
311				_extra: extra
312			)
313		}
314
315		// mintNFT mints a new NFT and deposits 
316		// it in the recipients collection
317		pub fun mintNFT(metadataId: UInt64, recipient: Address) {
318			pre {
319				EmeraldPass.isActive(user: TraeYoungDiamondHands.account.address): "You must have an active Emerald Pass subscription to airdrop NFTs. You can purchase Emerald Pass at https://pass.ecdao.org/"
320			}
321			let nft <- create NFT(_metadataId: metadataId, _recipient: recipient)
322			if let recipientCollection = getAccount(recipient).getCapability(TraeYoungDiamondHands.CollectionPublicPath).borrow<&TraeYoungDiamondHands.Collection{NonFungibleToken.CollectionPublic}>() {
323				recipientCollection.deposit(token: <- nft)
324			} else {
325				if let storage = &TraeYoungDiamondHands.nftStorage[recipient] as &{UInt64: NFT}? {
326					storage[nft.id] <-! nft
327				} else {
328					TraeYoungDiamondHands.nftStorage[recipient] <-! {nft.id: <- nft}
329				}
330			}
331		}
332
333		pub fun mintBatch(metadataIds: [UInt64], recipients: [Address]) {
334			pre {
335				metadataIds.length == recipients.length: "You need to pass in an equal number of metadataIds and recipients."
336			}
337			var i = 0
338			while i < metadataIds.length {
339				self.mintNFT(metadataId: metadataIds[i], recipient: recipients[i])
340				i = i + 1
341			}
342
343			emit MintBatch(metadataIds: metadataIds, recipients: recipients)
344		}
345
346		// create a new Administrator resource
347		pub fun createAdmin(): @Administrator {
348			return <- create Administrator()
349		}
350
351		// change piece of collection info
352		pub fun changeField(key: String, value: AnyStruct) {
353			TraeYoungDiamondHands.collectionInfo[key] = value
354		}
355	}
356
357	// public function that anyone can call to create a new empty collection
358	pub fun createEmptyCollection(): @NonFungibleToken.Collection {
359		return <- create Collection()
360	}
361
362	// Get information about a NFTMetadata
363	pub fun getNFTMetadata(_ metadataId: UInt64): NFTMetadata? {
364		return self.metadatas[metadataId]
365	}
366
367	pub fun getNFTMetadatas(): {UInt64: NFTMetadata} {
368		return self.metadatas
369	}
370
371	pub fun getPrimaryBuyers(): {UInt64: Address} {
372		return self.primaryBuyers
373	}
374
375	pub fun getCollectionInfo(): {String: AnyStruct} {
376		let collectionInfo = self.collectionInfo
377		collectionInfo["metadatas"] = self.metadatas
378		collectionInfo["primaryBuyers"] = self.primaryBuyers
379		collectionInfo["totalSupply"] = self.totalSupply
380		collectionInfo["nextMetadataId"] = self.nextMetadataId
381		return collectionInfo
382	}
383
384	pub fun getCollectionAttribute(key: String): AnyStruct {
385		return self.collectionInfo[key] ?? panic(key.concat(" is not an attribute in this collection."))
386	}
387
388	pub fun getOptionalCollectionAttribute(key: String): AnyStruct? {
389		return self.collectionInfo[key]
390	}
391
392	pub fun getMintVerifiers(): [{MintVerifiers.IVerifier}] {
393		return self.getCollectionAttribute(key: "mintVerifiers") as! [{MintVerifiers.IVerifier}]
394	}
395
396	pub fun canMint(): Bool {
397		return self.getCollectionAttribute(key: "minting") as! Bool
398	}
399
400	// Returns nil if an NFT with this metadataId doesn't exist
401	pub fun getPriceOfNFT(_ metadataId: UInt64): UFix64? {
402		if let metadata: TraeYoungDiamondHands.NFTMetadata = self.getNFTMetadata(metadataId) {
403			let defaultPrice: UFix64 = self.getCollectionAttribute(key: "price") as! UFix64
404			if self.getCollectionAttribute(key: "lotteryBuying") as! Bool {
405				return defaultPrice
406			}
407			return metadata.price ?? defaultPrice
408		}
409		// If the metadataId doesn't exist
410		return nil
411	}
412
413	// Returns an mapping of `id` to NFTMetadata
414	// for the NFTs a user can claim
415	pub fun getClaimableNFTs(user: Address): {UInt64: NFTMetadata} {
416		let answer: {UInt64: NFTMetadata} = {}
417		if let storage = &TraeYoungDiamondHands.nftStorage[user] as &{UInt64: NFT}? {
418			for id in storage.keys {
419				let nftRef = (&storage[id] as &NFT?)!
420				answer[id] = self.getNFTMetadata(nftRef.metadataId)
421			}
422		}
423		return answer
424	}
425
426	init(
427		_name: String, 
428		_description: String, 
429		_imagePath: String, 
430		_bannerImagePath: String?,
431		_minting: Bool, 
432		_royalty: MetadataViews.Royalty?,
433		_defaultPrice: UFix64,
434		_paymentType: String,
435		_ipfsCID: String,
436		_lotteryBuying: Bool,
437		_socials: {String: MetadataViews.ExternalURL},
438		_mintVerifiers: [{MintVerifiers.IVerifier}]
439	) {
440		// Collection Info
441		self.collectionInfo = {}
442		self.collectionInfo["name"] = _name
443		self.collectionInfo["description"] = _description
444		self.collectionInfo["image"] = MetadataViews.IPFSFile(
445			cid: _ipfsCID,
446			path: _imagePath
447		)
448		if let bannerImagePath = _bannerImagePath {
449			self.collectionInfo["bannerImage"] = MetadataViews.IPFSFile(
450				cid: _ipfsCID,
451				path: _bannerImagePath
452			)
453		}
454		self.collectionInfo["ipfsCID"] = _ipfsCID
455		self.collectionInfo["socials"] = _socials
456		self.collectionInfo["minting"] = _minting
457		self.collectionInfo["lotteryBuying"] = _lotteryBuying
458		if let royalty = _royalty {
459			assert(royalty.receiver.check(), message: "The passed in royalty receiver is not valid. The royalty account must set up the intended payment token.")
460			assert(royalty.cut <= 0.95, message: "The royalty cut cannot be bigger than 95% because 5% goes to Emerald City treasury for primary sales.")
461			self.collectionInfo["royalty"] = royalty
462		}
463		self.collectionInfo["price"] = _defaultPrice
464		self.collectionInfo["paymentType"] = _paymentType
465		self.collectionInfo["dateCreated"] = getCurrentBlock().timestamp
466		self.collectionInfo["mintVerifiers"] = _mintVerifiers
467		self.collectionInfo["profit"] = 0.0
468
469		self.nextMetadataId = 0
470		self.totalSupply = 0
471		self.metadatas = {}
472		self.primaryBuyers = {}
473		self.nftStorage <- {}
474
475		// Set the named paths
476		// We include the user's address in the paths.
477		// This is to prevent clashing with existing 
478		// Collection paths in the ecosystem.
479		self.CollectionStoragePath = /storage/TraeYoungDiamondHandsCollection_0xbb39f0dae1547256
480		self.CollectionPublicPath = /public/TraeYoungDiamondHandsCollection_0xbb39f0dae1547256
481		self.CollectionPrivatePath = /private/TraeYoungDiamondHandsCollection_0xbb39f0dae1547256
482		self.AdministratorStoragePath = /storage/TraeYoungDiamondHandsAdministrator_0xbb39f0dae1547256
483
484		// Create a Collection resource and save it to storage
485		let collection <- create Collection()
486		self.account.save(<- collection, to: self.CollectionStoragePath)
487
488		// create a public capability for the collection
489		self.account.link<&Collection{NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, MetadataViews.ResolverCollection}>(
490			self.CollectionPublicPath,
491			target: self.CollectionStoragePath
492		)
493
494		// Create a Administrator resource and save it to storage
495		let administrator <- create Administrator()
496		self.account.save(<- administrator, to: self.AdministratorStoragePath)
497
498		emit ContractInitialized()
499	}
500}
501