Skip to content
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
126 changes: 101 additions & 25 deletions cadence/contracts/DappyContract.cdc
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@

import FungibleToken from "./FungibleToken.cdc"
import MetadataViews from "./MetadataViews.cdc"

pub contract DappyContract {
access(self) var templates: {UInt32: Template}
access(self) var families: @{UInt32: Family}
// mapping [name] -> ipfs Hash
access(self) var images: {String: String}

pub var nextTemplateID: UInt32
pub var nextFamilyID: UInt32
Expand All @@ -13,31 +16,51 @@ pub contract DappyContract {
pub let CollectionPublicPath: PublicPath
pub let AdminStoragePath: StoragePath

pub enum Dna : UInt8 {
pub case ThereeStrips
pub case FourStrips
pub case FiveStrips
}

pub fun dnaToString(_ dna: Dna): String {
switch dna {
case Dna.ThereeStrips:
return "3 Strips"
case Dna.FourStrips:
return "4 Strips"
case Dna.FiveStrips:
return "5 Strips"
}
return ""
}

pub struct Template {
pub let templateID: UInt32
pub let dna: String
pub let dna: Dna
pub let name: String
pub let price: UFix64

init(templateID: UInt32, dna: String, name: String) {
init(templateID: UInt32, dna: Dna, name: String) {
self.templateID = templateID
self.dna = dna
self.name = name
self.price = self._calculatePrice(dna: dna.length)
self.price = self._calculatePrice(dna: dna)
}

access(self) fun _calculatePrice(dna: Int): UFix64 {
if dna >= 31 {
return 21.0
} else if dna >= 25 {
return 14.0
} else {
return 7.0
}
access(self) fun _calculatePrice(dna: Dna): UFix64 {
switch dna {
case Dna.ThereeStrips:
return 21.0
case Dna.FourStrips:
return 14.0
case Dna.FiveStrips:
return 7.0
}
return 0.0
}
}

pub resource Dappy {
pub resource Dappy: MetadataViews.Resolver {
pub let id: UInt64
pub let data: Template

Expand All @@ -50,6 +73,48 @@ pub contract DappyContract {
self.id = DappyContract.totalDappies
self.data = Template(templateID: templateID, dna: dappy.dna, name: dappy.name)
}

pub fun name(): String {
return DappyContract.dnaToString(self.data.dna)
.concat(" ")
.concat(self.data.name)
}

pub fun description(): String {
return "A "
.concat(DappyContract.dnaToString(self.data.dna).toLower())
.concat(" ")
.concat(self.data.name)
.concat(" with serial number ")
.concat(self.id.toString())
}

pub fun imageCID(): String {
return DappyContract.images[self.data.name]!
}

pub fun getViews(): [Type] {
return [
Type<MetadataViews.Display>()
]
}

pub fun resolveView(_ view: Type): AnyStruct? {
switch view {
case Type<MetadataViews.Display>():
return MetadataViews.Display(
name: self.name(),
description: self.description(),
thumbnail: MetadataViews.IPFSFile(
cid: self.imageCID(),
path: "sm.png"
)
)
}

return nil
}

}

pub resource Family {
Expand Down Expand Up @@ -106,15 +171,16 @@ pub contract DappyContract {
}

pub resource Admin {
pub fun createTemplate(dna: String, name: String): UInt32 {
pub fun createTemplate(dna: Dna, name: String, ipfsHash: String): UInt32 {
pre {
dna.length > 0 : "Could not create template: dna is required."
name.length > 0 : "Could not create template: name is required."
ipfsHash.length > 0 : "Could not create template: invalid ipfs hash"
}
let newDappyID = DappyContract.nextTemplateID
DappyContract.templates[newDappyID] = Template(templateID: newDappyID, dna: dna, name: name)
let newTemplateId = DappyContract.nextTemplateID
DappyContract.templates[newTemplateId] = Template(templateID: newTemplateId, dna: dna, name: name)
DappyContract.nextTemplateID = DappyContract.nextTemplateID + 1
return newDappyID
DappyContract.images[name] = ipfsHash
return newTemplateId
}

pub fun destroyTemplate(dappyID: UInt32) {
Expand Down Expand Up @@ -148,7 +214,14 @@ pub contract DappyContract {
pub resource interface CollectionPublic {
pub fun deposit(token: @Dappy)
pub fun getIDs(): [UInt64]
pub fun listDappies(): {UInt64: Template}
pub fun borrowDappy(id: UInt64): &DappyContract.Dappy? {
// 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 Dappy reference: The ID of the returned reference is incorrect"
}
}
}

pub resource interface Provider {
Expand Down Expand Up @@ -186,13 +259,14 @@ pub contract DappyContract {
return self.ownedDappies.keys
}

pub fun listDappies(): {UInt64: Template} {
var dappyTemplates: {UInt64:Template} = {}
for key in self.ownedDappies.keys {
let el = &self.ownedDappies[key] as &Dappy
dappyTemplates.insert(key: el.id, el.data)
// borrowDappy
pub fun borrowDappy(id: UInt64): &DappyContract.Dappy? {
if self.ownedDappies[id] != nil {
let ref = &self.ownedDappies[id] as auth &DappyContract.Dappy
return ref
} else {
return nil
}
return dappyTemplates
}

destroy() {
Expand Down Expand Up @@ -317,6 +391,8 @@ pub contract DappyContract {
self.AdminStoragePath = /storage/DappyAdmin
self.account.save<@Admin>(<- create Admin(), to: self.AdminStoragePath)
self.families <- {}
self.images = {}
}

}
}

114 changes: 114 additions & 0 deletions cadence/contracts/MetadataViews.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/**

This contract implements the metadata standard proposed
in FLIP-0636.

Ref: https://github.com/onflow/flow/blob/master/flips/20210916-nft-metadata.md

Structs and resources can implement one or more
metadata types, called views. Each view type represents
a different kind of metadata, such as a creator biography
or a JPEG image file.
*/

pub contract MetadataViews {

// A Resolver provides access to a set of metadata views.
//
// A struct or resource (e.g. an NFT) can implement this interface
// to provide access to the views that it supports.
//
pub resource interface Resolver {
pub fun getViews(): [Type]
pub fun resolveView(_ view: Type): AnyStruct?
}

// A ResolverCollection is a group of view resolvers index by ID.
//
pub resource interface ResolverCollection {
pub fun borrowViewResolver(id: UInt64): &{Resolver}
pub fun getIDs(): [UInt64]
}

// Display is a basic view that includes the name and description
// of an object. Most objects should implement this view.
//
pub struct Display {
pub let name: String
pub let description: String
pub let thumbnail: AnyStruct{File}

init(
name: String,
description: String,
thumbnail: AnyStruct{File}
) {
self.name = name
self.description = description
self.thumbnail = thumbnail
}
}

// File is a generic interface that represents a file stored on or off chain.
//
// Files can be used to references images, videos and other media.
//
pub struct interface File {
pub fun uri(): String
}

// HTTPFile is a file that is accessible at an HTTP (or HTTPS) URL.
//
pub struct HTTPFile: File {
pub let url: String

init(url: String) {
self.url = url
}

pub fun uri(): String {
return self.url
}
}

// IPFSThumbnail returns a thumbnail image for an object
// stored as an image file in IPFS.
//
// IPFS images are referenced by their content identifier (CID)
// rather than a direct URI. A client application can use this CID
// to find and load the image via an IPFS gateway.
//
pub struct IPFSFile: File {

// CID is the content identifier for this IPFS file.
//
// Ref: https://docs.ipfs.io/concepts/content-addressing/
//
pub let cid: String

// Path is an optional path to the file resource in an IPFS directory.
//
// This field is only needed if the file is inside a directory.
//
// Ref: https://docs.ipfs.io/concepts/file-systems/
//
pub let path: String?

init(cid: String, path: String?) {
self.cid = cid
self.path = path
}

// This function returns the IPFS native URL for this file.
//
// Ref: https://docs.ipfs.io/how-to/address-ipfs-on-web/#native-urls
//
pub fun uri(): String {
if let path = self.path {
return "ipfs://".concat(self.cid).concat("/").concat(path)
}

return "ipfs://".concat(self.cid)
}
}
}
71 changes: 66 additions & 5 deletions cadence/scripts/ListUserDappies.cdc
Original file line number Diff line number Diff line change
@@ -1,9 +1,70 @@
import DappyContract from "../contracts/DappyContract.cdc"
import MetadataViews from "../contracts/MetadataViews.cdc";

pub fun main(addr: Address): {UInt64: DappyContract.Template} {
pub struct DappyDetails {

Choose a reason for hiding this comment

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

Would it make sense to make defined in the contract instead of in a script?

Copy link
Author

Choose a reason for hiding this comment

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

It has no use in the contract as the data structure but can define in the contract as well, But I think it will just eat space besides making the system better,

Choose a reason for hiding this comment

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

It doesn't have use in the contract necessarily, but it might be more useful to have it centrally defined instead of having to define it in every script. For cryptodappy, it doesn't really matter because it isn't a production level contract that will actually be in use in the real world, but might be a good practice in smart contracts in general. But if you don't think it should be included in the contract, then I'll defer to you.

Copy link
Author

Choose a reason for hiding this comment

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

Yeah, I agree with you that it is good to have it in the smart contract for the production-grade system so everyone can use that data structure.

pub let name: String
pub let description: String
pub let thumbnail: String

pub let id: UInt64
pub let templateId: UInt64
pub let dna: DappyContract.Dna
pub let owner: Address

init(
name: String,
description: String,
thumbnail: String,
id: UInt64,
templateId: UInt64,
dna: DappyContract.Dna,
owner: Address,
) {
self.name = name
self.description = description
self.thumbnail = thumbnail
self.id = id
self.templateId = templateId
self.dna = dna
self.owner = owner
}
}

pub fun dwebURL(_ file: MetadataViews.IPFSFile): String {
var url = "https://"
.concat(file.cid)
.concat(".ipfs.dweb.link/")

if let path = file.path {
return url.concat(path)
}

return url
}

pub fun main(addr: Address): [DappyDetails]? {
var dappies: [DappyDetails] = []
let account = getAccount(addr)
let ref = account.getCapability<&{DappyContract.CollectionPublic}>(DappyContract.CollectionPublicPath)
.borrow() ?? panic("Cannot borrow reference")
let dappies = ref.listDappies()
return dappies
if let collection = account.getCapability<&{DappyContract.CollectionPublic}>(DappyContract.CollectionPublicPath).borrow() {
let dappiesId = collection.getIDs()
for id in dappiesId {
if let dappy = collection.borrowDappy(id: id) {
if let view = dappy.resolveView(Type<MetadataViews.Display>()) {
let display = view as! MetadataViews.Display
let ipfsThumbnail = display.thumbnail as! MetadataViews.IPFSFile
dappies.append(DappyDetails(
name: display.name,
description: display.description,
thumbnail: dwebURL(ipfsThumbnail),
id: dappy.id,
templateId: dappy.data.templateID,
dna: dappy.data.dna,
owner: addr,
))
}
}
}
return dappies
}
return nil
}
Loading