Skip to content

Instantly share code, notes, and snippets.

@briandilley
Created August 18, 2021 23:49
Show Gist options
  • Save briandilley/a32973d5b268bcd8774fd170b88c2711 to your computer and use it in GitHub Desktop.
Save briandilley/a32973d5b268bcd8774fd170b88c2711 to your computer and use it in GitHub Desktop.
import NonFungibleToken from 0xNONFUNGIBLETOKEN
pub contract TenantService: NonFungibleToken {
// basic data about the tenant
pub let version: UInt32
pub let name: String
pub let description: String
pub var closed: Bool
// NFT
pub var totalSupply: UInt64
// paths
access(all) let ADMIN_NFT_COLLECTION_PATH: StoragePath
access(all) let ADMIN_OBJECT_PATH: StoragePath
access(all) let PUBLIC_NFT_COLLECTION_PATH: PublicPath
// archetypes
access(self) let archetypes: {UInt64: Archetype}
access(self) let archetypeAdmins: @{UInt64: ArchetypeAdmin}
access(self) var archetypeSeq: UInt64
access(self) let artifactsByArchetype: {UInt64: {UInt64: Bool}} // archetypeId -> {artifactId: true}
// artifacts
access(self) let artifacts: {UInt64: Artifact}
access(self) let artifactAdmins: @{UInt64: ArtifactAdmin}
access(self) var artifactSeq: UInt64
access(self) var nextNftSerialNumber: {UInt64: UInt64}
access(self) let setsByArtifact: {UInt64: {UInt64: Bool}} // artifactId -> {setId: true}
access(self) let faucetsByArtifact: {UInt64: {UInt64: Bool}} // artifactId -> {faucetId: true}
// sets
access(self) let sets: {UInt64: Set}
access(self) let setAdmins: @{UInt64: SetAdmin}
access(self) var setSeq: UInt64
access(self) let artifactsBySet: {UInt64: {UInt64: Bool}} // setId -> {artifactId: true}
access(self) let faucetsBySet: {UInt64: {UInt64: Bool}} // setId -> {faucetId: true}
// prints
access(self) let prints: {UInt64: Print}
access(self) let printAdmins: @{UInt64: PrintAdmin}
access(self) var printSeq: UInt64
// faucets
access(self) let faucets: {UInt64: Faucet}
access(self) let faucetAdmins: @{UInt64: FaucetAdmin}
access(self) var faucetSeq: UInt64
// tenant events
pub event TenantClosed()
// NFT events
pub event ContractInitialized()
pub event Withdraw(id: UInt64, from: Address?)
pub event Deposit(id: UInt64, to: Address?)
init(tenantName: String, tenantDescription: String) {
self.version = 1
self.name = tenantName
self.description = tenantDescription
self.closed = false
self.archetypes = {}
self.archetypeAdmins <- {}
self.archetypeSeq = 1
self.artifactsByArchetype = {}
self.artifacts = {}
self.artifactAdmins <- {}
self.artifactSeq = 1
self.nextNftSerialNumber = {}
self.setsByArtifact = {}
self.faucetsByArtifact = {}
self.sets = {}
self.setAdmins <- {}
self.setSeq = 1
self.artifactsBySet = {}
self.faucetsBySet = {}
self.prints = {}
self.printAdmins <- {}
self.printSeq = 1
self.faucets = {}
self.faucetAdmins <- {}
self.faucetSeq = 1
self.totalSupply = 0
self.OBJECT_TYPE_MASK = UInt64.max << 55
self.SEQUENCE_MASK = (UInt64.max << UInt64(9)) >> UInt64(9)
self.ADMIN_NFT_COLLECTION_PATH = /storage/TenantShardedNFTCollection
self.PUBLIC_NFT_COLLECTION_PATH = /public/TenantNFTCollection
self.ADMIN_OBJECT_PATH = /storage/TenantAdmin
// create a collection for the admin
self.account.save<@ShardedCollection>(<- TenantService.createEmptyShardedCollection(numBuckets: 32), to: TenantService.ADMIN_NFT_COLLECTION_PATH)
// Create a public capability for the Collection
self.account.link<&{CollectionPublic}>(TenantService.PUBLIC_NFT_COLLECTION_PATH, target: TenantService.ADMIN_NFT_COLLECTION_PATH)
// put the admin in storage
self.account.save<@TenantAdmin>(<- create TenantAdmin(), to: TenantService.ADMIN_OBJECT_PATH)
emit ContractInitialized()
}
pub enum ObjectType: UInt8 {
pub case UNKNOWN
// An Archetype is a high level organizational unit for a type of NFT. For instance, in the
// case that the Tenant is a company dealing with professional sports they might have an Archetype
// for each of the sports that they support, ie: Basketball, Baseball, Football, etc.
//
pub case ARCHETYPE
// An Artifact is the actual object that is minted as an NFT. It contains all of the meta data data
// and a reference to the Archetype that it belongs to.
//
pub case ARTIFACT
// NFTs can be minted into a Set. A set could be something along the lines of "Greatest Pitchers",
// "Slam Dunk Artists", or "Running Backs" (continuing with the sports theme from above). NFT do
// not have to be minted into a set. Also, an NFT could be minted from an Artifact by itself, and
// in another instance as part of a set - so that the NFT references the same Artifact, but only
// one of them belongs to the Set.
//
pub case SET
// A Print reserves a block of serial numbers for minting at a later time. It is associated with
// a single Artifact and when the Print is minted it reserves the next serial number through however
// many serial numbers are to be reserved. NFTs can then later be minted from the Print and will
// be given the reserved serial numbers.
//
pub case PRINT
// A Faucet is similar to a Print except that it doesn't reserve a block of serial numbers, it merely
// mints NFTs from a given Artifact on demand. A Faucet can have a maxMintCount or be unbound and
// mint infinitely (or however many NFTs are allowed to be minted for the Artifact that it is bound to).
//
pub case FAUCET
// An NFT holds metadata, a reference to it's Artifact (and therefore Archetype), a reference to
// it's Set (if it belongs to one), a reference to it's Print (if it was minted by one), a reference
// to it's Faucet (if it was minted by one) and has a unique serial number.
pub case NFT
}
pub let OBJECT_TYPE_MASK: UInt64
pub let SEQUENCE_MASK: UInt64
// Generates an ID for the given object type and sequence. We generate IDs this way
// so that they are unique across the various types of objects supported by this
// contract.
//
pub fun generateId(_ objectType: ObjectType, _ sequence: UInt64): UInt64 {
if (sequence > 36028797018963967) {
panic("sequence may only have 55 bits and must be less than 36028797018963967")
}
var ret: UInt64 = UInt64(objectType.rawValue)
ret = ret << UInt64(55)
ret = ret | ((sequence << UInt64(9)) >> UInt64(9))
return ret
}
// Extracts the ObjectType from an id
//
pub fun getObjectType(_ id: UInt64): ObjectType {
return ObjectType(rawValue: UInt8(id >> UInt64(55)))!
}
// Extracts the sequence from an id
//
pub fun getSequence(_ id: UInt64): UInt64 {
return id & TenantService.SEQUENCE_MASK
}
// Indicates whether or not the given id is for a given ObjectType.
//
pub fun isObjectType(_ id: UInt64, _ objectType: ObjectType): Bool {
return (TenantService.getObjectType(id) == objectType)
}
// Returns the version of this contract
//
pub fun getVersion(): UInt32 {
return self.version
}
// TenantAdmin is used for administering the Tenant
//
pub resource TenantAdmin {
// Closes the Tenant, rendering any write access impossible
//
pub fun close() {
if !TenantService.closed {
TenantService.closed = true
emit TenantClosed()
}
}
// Creates a new Archetype returning it's id.
//
pub fun createArchetype(
name: String,
description: String,
metadata: {String: TenantService.MetadataField}
): UInt64 {
pre {
TenantService.closed != true: "The Tenant is closed"
}
var archetype = Archetype(name: name, description: description, metadata: metadata)
TenantService.archetypes[archetype.id] = archetype
TenantService.archetypeAdmins[archetype.id] <-! create ArchetypeAdmin(archetype.id)
return archetype.id
}
// Grants admin access to the given Archetype
//
pub fun borrowArchetypeAdmin(_ id: UInt64): &ArchetypeAdmin {
pre {
TenantService.closed != true: "The Tenant is closed"
TenantService.archetypeAdmins[id] != nil: "Archetype not found"
TenantService.isObjectType(id, ObjectType.ARCHETYPE): "ObjectType is not an Archetype"
}
return &TenantService.archetypeAdmins[id] as &ArchetypeAdmin
}
// Creates a new Artifact returning it's id.
//
pub fun createArtifact(
archetypeId: UInt64,
name: String,
description: String,
maxMintCount: UInt64,
metadata: {String: TenantService.MetadataField}
): UInt64 {
pre {
TenantService.closed != true: "The Tenant is closed"
TenantService.archetypes[archetypeId] != nil: "The Archetype wasn't found"
self.borrowArchetypeAdmin(archetypeId).closed != true: "The Archetype is closed"
}
var artifact = Artifact(archetypeId: archetypeId, name: name, description: description, maxMintCount: maxMintCount, metadata: metadata)
TenantService.artifacts[artifact.id] = artifact
TenantService.artifactAdmins[artifact.id] <-! create ArtifactAdmin(id: artifact.id)
TenantService.nextNftSerialNumber[artifact.id] = 1
return artifact.id
}
// Grants admin access to the given Artifact
//
pub fun borrowArtifactAdmin(_ id: UInt64): &ArtifactAdmin {
pre {
TenantService.closed != true: "The Tenant is closed"
TenantService.artifactAdmins[id] != nil: "Artifact not found"
TenantService.isObjectType(id, ObjectType.ARTIFACT): "ObjectType is not an Artifact"
}
return &TenantService.artifactAdmins[id] as &ArtifactAdmin
}
// Creates a new Set returning it's id.
//
pub fun createSet(name: String, description: String, metadata: {String: TenantService.MetadataField}): UInt64 {
pre {
TenantService.closed != true: "The Tenant is closed"
}
var set = Set(name: name, description: description, metadata: metadata)
TenantService.sets[set.id] = set
TenantService.setAdmins[set.id] <-! create SetAdmin(set.id)
return set.id
}
// Grants admin access to the given Set
//
pub fun borrowSetAdmin(_ id: UInt64): &SetAdmin {
pre {
TenantService.closed != true: "The Tenant is closed"
TenantService.setAdmins[id] != nil: "Set not found"
TenantService.isObjectType(id, ObjectType.SET): "ObjectType is not a Set"
}
return &TenantService.setAdmins[id] as &SetAdmin
}
// Creates a new Print returning it's id.
//
pub fun createPrint(
artifactId: UInt64,
setId: UInt64?,
name: String,
description: String,
maxMintCount: UInt64,
metadata: {String: TenantService.MetadataField}
): UInt64 {
pre {
TenantService.closed != true: "The Tenant is closed"
self.borrowArtifactAdmin(artifactId).closed != true: "The Artifact is closed"
setId == nil || self.borrowSetAdmin(setId!).closed != true: "The Set is closed"
}
var print = Print(artifactId: artifactId, setId: setId, name: name, description: description, maxMintCount: maxMintCount, metadata: metadata)
TenantService.prints[print.id] = print
TenantService.printAdmins[print.id] <-! create PrintAdmin(print.id, print.serialNumberStart)
return print.id
}
// Grants admin access to the given Print
//
pub fun borrowPrintAdmin(_ id: UInt64): &PrintAdmin {
pre {
TenantService.closed != true: "The Tenant is closed"
TenantService.printAdmins[id] != nil: "Print not found"
TenantService.isObjectType(id, ObjectType.PRINT): "ObjectType is not a print"
}
return &TenantService.printAdmins[id] as &PrintAdmin
}
// Creates a new Faucet returning it's id.
//
pub fun createFaucet(
artifactId: UInt64,
setId: UInt64?,
name: String,
description: String,
maxMintCount: UInt64,
metadata: {String: TenantService.MetadataField}
): UInt64 {
pre {
TenantService.closed != true: "The Tenant is closed"
self.borrowArtifactAdmin(artifactId).closed != true: "The Artifact is closed"
setId == nil || self.borrowSetAdmin(setId!).closed != true: "The Set is closed"
}
var faucet = Faucet(artifactId: artifactId, setId: setId, name: name, description: description, maxMintCount: maxMintCount, metadata: metadata)
TenantService.faucets[faucet.id] = faucet
TenantService.faucetAdmins[faucet.id] <-! create FaucetAdmin(id: faucet.id)
return faucet.id
}
// Grants admin access to the given Faucet
//
pub fun borrowFaucetAdmin(_ id: UInt64): &FaucetAdmin {
pre {
TenantService.closed != true: "The Tenant is closed"
TenantService.faucetAdmins[id] != nil: "Faucet not found"
TenantService.isObjectType(id, ObjectType.FAUCET): "ObjectType is not a faucet"
}
return &TenantService.faucetAdmins[id] as &FaucetAdmin
}
// Mints an NFT
//
pub fun mintNFT(artifactId: UInt64, printId: UInt64?, faucetId: UInt64?, setId: UInt64?, metadata: {String: TenantService.MetadataField}): @NFT {
pre {
TenantService.artifacts[artifactId] != nil: "Cannot mint the NFT: The Artifact wasn't found"
self.borrowArtifactAdmin(artifactId).closed != true: "The Artifact is closed"
printId == nil || TenantService.isObjectType(printId!, ObjectType.PRINT): "Id supplied for printId is not an ObjectType of print"
faucetId == nil || TenantService.isObjectType(faucetId!, ObjectType.FAUCET): "Id supplied for faucetId is not an ObjectType of faucet"
setId == nil || TenantService.isObjectType(setId!, ObjectType.SET): "Id supplied for setId is not an ObjectType of set"
printId == nil || TenantService.prints[printId!] != nil: "Cannot mint the NFT: The Print wasn't found"
faucetId == nil || TenantService.faucets[faucetId!] != nil: "Cannot mint the NFT: The Faucet wasn't found"
setId == nil || TenantService.sets[setId!] != nil: "Cannot mint the NFT: The Set wasn't found"
printId == nil || self.borrowPrintAdmin(printId!).closed != true: "The Print is closed"
faucetId == nil || self.borrowFaucetAdmin(faucetId!).closed != true: "The Faucet is closed"
setId == nil || self.borrowSetAdmin(setId!).closed != true: "The Set is closed"
faucetId == nil || TenantService.faucets[faucetId!]!.artifactId == artifactId: "The artifactId doesn't match the Faucet's artifactId"
printId == nil || TenantService.prints[printId!]!.artifactId == artifactId: "The artifactId doesn't match the Print's artifactId"
faucetId == nil || TenantService.faucets[faucetId!]!.setId == setId: "The setId doesn't match the Faucet's setId"
printId == nil || TenantService.prints[printId!]!.setId == setId: "The setId doesn't match the Print's setId"
!(faucetId != nil && printId != nil): "Can only mint from one of a faucet or print"
}
let artifact: Artifact = TenantService.artifacts[artifactId]!
let artifactAdmin = self.borrowArtifactAdmin(artifactId)
artifactAdmin.logMint(1)
if printId != nil {
artifactAdmin.logPrint(1)
}
let archetype: Archetype = TenantService.archetypes[artifact.archetypeId]!
let archetypeAdmin = self.borrowArchetypeAdmin(artifact.archetypeId)
if archetypeAdmin != nil {
archetypeAdmin.logMint(1)
if printId != nil {
archetypeAdmin.logPrint(1)
}
}
if faucetId != nil {
let faucetAdmin = self.borrowFaucetAdmin(faucetId!)
faucetAdmin.logMint(1)
}
if setId != nil {
let setAdmin = self.borrowSetAdmin(setId!)
setAdmin.logMint(1)
if printId != nil {
setAdmin.logPrint(1)
}
}
if printId != nil {
let printAdmin = self.borrowPrintAdmin(printId!)
printAdmin.logMint(1)
}
let newNFT: @NFT <- create NFT(
archetypeId: artifact.archetypeId,
artifactId: artifact.id,
printId: printId,
faucetId: faucetId,
setId: setId,
metadata: metadata)
return <- newNFT
}
// Mints many NFTs
//
pub fun batchMintNFTs(
count: UInt64,
artifactId: UInt64,
printId: UInt64?,
faucetId: UInt64?,
setId: UInt64?,
metadata: {String: TenantService.MetadataField}
): @Collection {
let newCollection <- create Collection()
var i: UInt64 = 0
while i < count {
newCollection.deposit(token: <-self.mintNFT(
artifactId: artifactId,
printId: printId,
faucetId: faucetId,
setId: setId,
metadata: metadata
))
i = i + (1 as UInt64)
}
return <- newCollection
}
// Creates a new TenantAdmin that allows for another account
// to administer the Tenant
//
pub fun createNewTenantAdmin(): @TenantAdmin {
return <- create TenantAdmin()
}
}
// =====================================
// Archetype
// =====================================
pub event ArchetypeCreated(_ id: UInt64)
pub event ArchetypeDestroyed(_ id: UInt64)
pub event ArchetypeClosed(_ id: UInt64)
pub fun getArchetype(_ id: UInt64): Archetype? {
pre {
TenantService.isObjectType(id, ObjectType.ARCHETYPE): "Id supplied is not for an archetype"
}
return TenantService.archetypes[id]
}
pub fun getArchetypeView(_ id: UInt64): ArchetypeView? {
pre {
TenantService.isObjectType(id, ObjectType.ARCHETYPE): "Id supplied is not for an archetype"
}
if TenantService.archetypes[id] == nil {
return nil
}
let archetype = TenantService.archetypes[id]!
let archetypeAdmin = &TenantService.archetypeAdmins[id] as &ArchetypeAdmin
return ArchetypeView(
id: archetype.id,
name: archetype.name,
description: archetype.description,
metadata: archetype.metadata,
mintCount: archetypeAdmin.mintCount,
printCount: archetypeAdmin.printCount,
closed: archetypeAdmin.closed
)
}
pub fun getArchetypeViews(_ archetypes: [UInt64]): [ArchetypeView] {
let ret: [ArchetypeView] = []
for archetype in archetypes {
let element = self.getArchetypeView(archetype)
if element != nil {
ret.append(element!)
}
}
return ret
}
pub fun getAllArchetypes(): [Archetype] {
return TenantService.archetypes.values
}
// The immutable data for an Archetype
//
pub struct Archetype {
pub let id: UInt64
pub let name: String
pub let description: String
pub let metadata: {String: TenantService.MetadataField}
init(name: String, description: String, metadata: {String: TenantService.MetadataField}) {
self.id = TenantService.generateId(ObjectType.ARCHETYPE, TenantService.archetypeSeq)
self.name = name
self.description = description
self.metadata = metadata
TenantService.archetypeSeq = TenantService.archetypeSeq + 1 as UInt64
emit ArchetypeCreated(self.id)
}
}
// The mutable data for an Archetype
//
pub resource ArchetypeAdmin {
pub let id: UInt64
pub var mintCount: UInt64
pub var printCount: UInt64
pub var closed: Bool
init(_ id: UInt64) {
self.id = id
self.mintCount = 0
self.printCount = 0
self.closed = false
}
pub fun close() {
if !self.closed {
self.closed = true
emit ArchetypeClosed(self.id)
}
}
pub fun logMint(_ count: UInt64) {
pre {
TenantService.closed != true: "The Tenant is closed"
self.closed != true: "The Archetype is closed"
}
self.mintCount = self.mintCount + count
}
pub fun logPrint(_ count: UInt64) {
pre {
TenantService.closed != true: "The Tenant is closed"
self.closed != true: "The Archetype is closed"
}
self.printCount = self.printCount + count
}
destroy() {
emit ArchetypeDestroyed(self.id)
}
}
// An immutable view for an Archetype and all of it's data
//
pub struct ArchetypeView {
pub let id: UInt64
pub let name: String
pub let description: String
pub let metadata: {String: TenantService.MetadataField}
pub let mintCount: UInt64
pub let printCount: UInt64
pub let closed: Bool
init(
id: UInt64,
name: String,
description: String,
metadata: {String: TenantService.MetadataField},
mintCount: UInt64,
printCount: UInt64,
closed: Bool
) {
self.id = id
self.name = name
self.description = description
self.metadata = metadata
self.mintCount = mintCount
self.printCount = printCount
self.closed = closed
}
}
// =====================================
// Artifact
// =====================================
pub event ArtifactCreated(_ id: UInt64)
pub event ArtifactMaxMintCountChanged(_ id: UInt64, _ oldMaxMintCount: UInt64, _ newMaxMintCount: UInt64)
pub event ArtifactDestroyed(_ id: UInt64)
pub event ArtifactClosed(_ id: UInt64)
pub fun getArtifact(_ id: UInt64): Artifact? {
pre {
TenantService.isObjectType(id, ObjectType.ARTIFACT): "Id supplied is not for an artifact"
}
return TenantService.artifacts[id]
}
pub fun getArtifactView(_ id: UInt64): ArtifactView? {
pre {
TenantService.isObjectType(id, ObjectType.ARTIFACT): "Id supplied is not for an artifact"
}
if TenantService.artifacts[id] == nil {
return nil
}
let artifact = TenantService.artifacts[id]!
let artifactAdmin = &TenantService.artifactAdmins[id] as &ArtifactAdmin
return ArtifactView(
id: artifact.id,
archetypeId: artifact.archetypeId,
name: artifact.name,
description: artifact.description,
metadata: artifact.metadata,
maxMintCount: artifact.maxMintCount,
mintCount: artifactAdmin.mintCount,
printCount: artifactAdmin.printCount,
closed: artifactAdmin.closed
)
}
pub fun getArtifactViews(_ artifacts: [UInt64]): [ArtifactView] {
let ret: [ArtifactView] = []
for artifact in artifacts {
let element = self.getArtifactView(artifact)
if element != nil {
ret.append(element!)
}
}
return ret
}
pub fun getAllArtifacts(): [Artifact] {
return TenantService.artifacts.values
}
pub fun getArtifactsBySet(_ setId: UInt64): [UInt64] {
let map = TenantService.artifactsBySet[setId]
if map != nil {
return map!.keys
}
return []
}
pub fun getFaucetsBySet(_ setId: UInt64): [UInt64] {
let map = TenantService.faucetsBySet[setId]
if map != nil {
return map!.keys
}
return []
}
pub fun getSetsByArtifact(_ artifactId: UInt64): [UInt64] {
let map = TenantService.setsByArtifact[artifactId]
if map != nil {
return map!.keys
}
return []
}
pub fun getFaucetsByArtifact(_ artifactId: UInt64): [UInt64] {
let map = TenantService.faucetsByArtifact[artifactId]
if map != nil {
return map!.keys
}
return []
}
pub fun getArtifactsByArchetype(_ archetypeId: UInt64): [UInt64] {
let map = TenantService.artifactsByArchetype[archetypeId]
if map != nil {
return map!.keys
}
return []
}
// The immutable data for an Artifact
//
pub struct Artifact {
pub let id: UInt64
pub let archetypeId: UInt64
pub let name: String
pub let description: String
pub let maxMintCount: UInt64
pub let metadata: {String: TenantService.MetadataField}
init(archetypeId: UInt64, name: String, description: String, maxMintCount: UInt64, metadata: {String: TenantService.MetadataField}) {
self.id = TenantService.generateId(ObjectType.ARTIFACT, TenantService.artifactSeq)
self.archetypeId = archetypeId
self.name = name
self.description = description
self.maxMintCount = maxMintCount
self.metadata = metadata
TenantService.artifactSeq = TenantService.artifactSeq + 1 as UInt64
emit ArtifactCreated(self.id)
}
}
// The mutable data for an Artifact
//
pub resource ArtifactAdmin {
pub let id: UInt64
pub var mintCount: UInt64
pub var printCount: UInt64
pub var closed: Bool
init(id: UInt64) {
self.id = id
self.mintCount = 0
self.printCount = 0
self.closed = false
}
pub fun close() {
if !self.closed {
self.closed = true
emit ArtifactClosed(self.id)
}
}
pub fun logMint(_ count: UInt64) {
pre {
TenantService.closed != true: "The Tenant is closed"
self.closed != true: "The Artifact is closed"
((TenantService.artifacts[self.id]!.maxMintCount == (0 as UInt64))
|| (TenantService.artifacts[self.id]!.maxMintCount >= (self.mintCount + count))): "The Artifact would exceed it's maxMintCount"
}
self.mintCount = self.mintCount + count
}
pub fun logPrint(_ count: UInt64) {
pre {
TenantService.closed != true: "The Tenant is closed"
self.closed != true: "The Artifact is closed"
}
self.printCount = self.printCount + count
}
destroy() {
emit ArtifactDestroyed(self.id)
}
}
// An immutable view for an Artifact and all of it's data
//
pub struct ArtifactView {
pub let id: UInt64
pub let archetypeId: UInt64
pub let name: String
pub let description: String
pub let metadata: {String: TenantService.MetadataField}
pub let maxMintCount: UInt64
pub let mintCount: UInt64
pub let printCount: UInt64
pub let closed: Bool
init(
id: UInt64,
archetypeId: UInt64,
name: String,
description: String,
metadata: {String: TenantService.MetadataField},
maxMintCount: UInt64,
mintCount: UInt64,
printCount: UInt64,
closed: Bool
) {
self.id = id
self.archetypeId = archetypeId
self.name = name
self.description = description
self.metadata = metadata
self.maxMintCount = maxMintCount
self.mintCount = mintCount
self.printCount = printCount
self.closed = closed
}
}
// =====================================
// Set
// =====================================
pub event SetCreated(_ id: UInt64)
pub event SetDestroyed(_ id: UInt64)
pub event SetClosed(_ id: UInt64)
pub fun getSet(_ id: UInt64): Set? {
pre {
TenantService.isObjectType(id, ObjectType.SET): "Id supplied is not for an set"
}
return TenantService.sets[id]
}
pub fun getSetView(_ id: UInt64): SetView? {
pre {
TenantService.isObjectType(id, ObjectType.SET): "Id supplied is not for an set"
}
if TenantService.sets[id] == nil {
return nil
}
let set = TenantService.sets[id]!
let setAdmin = &TenantService.setAdmins[id] as &SetAdmin
return SetView(
id: set.id,
name: set.name,
description: set.description,
metadata: set.metadata,
mintCount: setAdmin.mintCount,
printCount: setAdmin.printCount,
closed: setAdmin.closed
)
}
pub fun getSetViews(_ sets: [UInt64]): [SetView] {
let ret: [SetView] = []
for set in sets {
let element = self.getSetView(set)
if element != nil {
ret.append(element!)
}
}
return ret
}
pub fun getAllSets(): [Set] {
return TenantService.sets.values
}
// The immutable data for an Set
//
pub struct Set {
pub let id: UInt64
pub let name: String
pub let description: String
pub let metadata: {String: TenantService.MetadataField}
init(
name: String,
description: String,
metadata: {String: TenantService.MetadataField}
) {
self.id = TenantService.generateId(ObjectType.SET, TenantService.setSeq)
self.name = name
self.description = description
self.metadata = metadata
TenantService.setSeq = TenantService.setSeq + 1 as UInt64
TenantService.faucetsBySet[self.id] = {}
emit SetCreated(self.id)
}
}
// The mutable data for an Set
//
pub resource SetAdmin {
pub let id: UInt64
pub var mintCount: UInt64
pub var printCount: UInt64
pub var closed: Bool
init(_ id: UInt64) {
self.id = id
self.mintCount = 0
self.printCount = 0
self.closed = false
}
pub fun close() {
if !self.closed {
self.closed = true
emit SetClosed(self.id)
}
}
pub fun logMint(_ count: UInt64) {
pre {
TenantService.closed != true: "The Tenant is closed"
self.closed != true: "The Set is closed"
}
self.mintCount = self.mintCount + count
}
pub fun logPrint(_ count: UInt64) {
pre {
TenantService.closed != true: "The Tenant is closed"
self.closed != true: "The Set is closed"
}
self.printCount = self.printCount + count
}
destroy() {
emit SetDestroyed(self.id)
}
}
// An immutable view for an Set and all of it's data
//
pub struct SetView {
pub let id: UInt64
pub let name: String
pub let description: String
pub let metadata: {String: TenantService.MetadataField}
pub let mintCount: UInt64
pub let printCount: UInt64
pub let closed: Bool
init(
id: UInt64,
name: String,
description: String,
metadata: {String: TenantService.MetadataField},
mintCount: UInt64,
printCount: UInt64,
closed: Bool
) {
self.id = id
self.name = name
self.description = description
self.metadata = metadata
self.mintCount = mintCount
self.printCount = printCount
self.closed = closed
}
}
// =====================================
// Print
// =====================================
pub event PrintCreated(_ id: UInt64)
pub event PrintDestroyed(_ id: UInt64)
pub event PrintClosed(_ id: UInt64)
pub fun getPrint(_ id: UInt64): Print? {
pre {
TenantService.isObjectType(id, ObjectType.PRINT): "Id supplied is not for a print"
}
return TenantService.prints[id]
}
pub fun getPrintView(_ id: UInt64): PrintView? {
pre {
TenantService.isObjectType(id, ObjectType.PRINT): "Id supplied is not for a print"
}
if TenantService.prints[id] == nil {
return nil
}
let print = TenantService.prints[id]!
let printAdmin = &TenantService.printAdmins[id] as &PrintAdmin
return PrintView(
id: print.id,
artifactId: print.artifactId,
setId: print.setId,
name: print.name,
description: print.description,
maxMintCount: print.maxMintCount,
metadata: print.metadata,
serialNumberStart: print.serialNumberStart,
nextNftSerialNumber: printAdmin.nextNftSerialNumber,
mintCount: printAdmin.mintCount,
closed: printAdmin.closed
)
}
pub fun getPrintViews(_ prints: [UInt64]): [PrintView] {
let ret: [PrintView] = []
for print in prints {
let element = self.getPrintView(print)
if element != nil {
ret.append(element!)
}
}
return ret
}
pub fun getAllPrints(): [Print] {
return TenantService.prints.values
}
// The immutable data for an Print
//
pub struct Print {
pub let id: UInt64
pub let artifactId: UInt64
pub let setId: UInt64?
pub let name: String
pub let description: String
pub let maxMintCount: UInt64
pub let metadata: {String: TenantService.MetadataField}
pub let serialNumberStart: UInt64
init(
artifactId: UInt64,
setId: UInt64?,
name: String,
description: String,
maxMintCount: UInt64,
metadata: {String: TenantService.MetadataField}
) {
pre {
maxMintCount > 0 : "maxMintCount must be greater than 0"
}
self.id = TenantService.generateId(ObjectType.PRINT, TenantService.printSeq)
self.artifactId = artifactId
self.setId = setId
self.name = name
self.description = description
self.maxMintCount = maxMintCount
self.metadata = metadata
self.serialNumberStart = TenantService.nextNftSerialNumber[artifactId]!
TenantService.nextNftSerialNumber[artifactId] = self.serialNumberStart + maxMintCount
TenantService.printSeq = TenantService.printSeq + 1 as UInt64
emit PrintCreated(self.id)
}
}
// The mutable data for an Print
//
pub resource PrintAdmin {
pub let id: UInt64
pub var nextNftSerialNumber: UInt64
pub var mintCount: UInt64
pub var closed: Bool
init(_ id: UInt64, _ serialNumberStart: UInt64) {
self.id = id
self.mintCount = 0
self.closed = false
self.nextNftSerialNumber = serialNumberStart
}
pub fun close() {
if !self.closed {
self.closed = true
emit PrintClosed(self.id)
}
}
pub fun getAndIncrementSerialNumber(): UInt64 {
let ret: UInt64 = self.nextNftSerialNumber
self.nextNftSerialNumber = ret + (1 as UInt64)
return ret
}
pub fun logMint(_ count: UInt64) {
pre {
TenantService.closed != true: "The Tenant is closed"
self.closed != true: "The Print is closed"
TenantService.prints[self.id]!.maxMintCount >= (self.mintCount + count): "The Print would exceed it's maxMintCount"
}
self.mintCount = self.mintCount + count
}
destroy() {
emit PrintDestroyed(self.id)
}
}
// An immutable view for an Print and all of it's data
//
pub struct PrintView {
pub let id: UInt64
pub let artifactId: UInt64
pub let setId: UInt64?
pub let name: String
pub let description: String
pub let maxMintCount: UInt64
pub let metadata: {String: TenantService.MetadataField}
pub let serialNumberStart: UInt64
pub let nextNftSerialNumber: UInt64
pub let mintCount: UInt64
pub let closed: Bool
init(
id: UInt64,
artifactId: UInt64,
setId: UInt64?,
name: String,
description: String,
maxMintCount: UInt64,
metadata: {String: TenantService.MetadataField},
serialNumberStart: UInt64,
nextNftSerialNumber: UInt64,
mintCount: UInt64,
closed: Bool
) {
self.id = id
self.artifactId = artifactId
self.setId = setId
self.name = name
self.description = description
self.maxMintCount = maxMintCount
self.metadata = metadata
self.serialNumberStart = serialNumberStart
self.nextNftSerialNumber = nextNftSerialNumber
self.mintCount = mintCount
self.closed = closed
}
}
// =====================================
// Faucet
// =====================================
pub event FaucetCreated(_ id: UInt64)
pub event FaucetMaxMintCountChanged(_ id: UInt64, _ oldMaxMintCount: UInt64, _ newMaxMintCount: UInt64)
pub event FaucetDestroyed(_ id: UInt64)
pub event FaucetClosed(_ id: UInt64)
pub fun getFaucet(_ id: UInt64): Faucet? {
pre {
TenantService.isObjectType(id, ObjectType.FAUCET): "Id supplied is not for a faucet"
}
return TenantService.faucets[id]
}
pub fun getFaucetView(_ id: UInt64): FaucetView? {
pre {
TenantService.isObjectType(id, ObjectType.FAUCET): "Id supplied is not for a faucet"
}
if TenantService.faucets[id] == nil {
return nil
}
let faucet = TenantService.faucets[id]!
let faucetAdmin = &TenantService.faucetAdmins[id] as &FaucetAdmin
return FaucetView(
id: faucet.id,
artifactId: faucet.artifactId,
setId: faucet.setId,
name: faucet.name,
description: faucet.description,
maxMintCount: faucet.maxMintCount,
metadata: faucet.metadata,
mintCount: faucetAdmin.mintCount,
closed: faucetAdmin.closed
)
}
pub fun getFaucetViews(_ faucets: [UInt64]): [FaucetView] {
let ret: [FaucetView] = []
for faucet in faucets {
let element = self.getFaucetView(faucet)
if element != nil {
ret.append(element!)
}
}
return ret
}
pub fun getAllFaucets(): [Faucet] {
return TenantService.faucets.values
}
// The immutable data for an Faucet
//
pub struct Faucet {
pub let id: UInt64
pub let artifactId: UInt64
pub let setId: UInt64?
pub let name: String
pub let description: String
pub let maxMintCount: UInt64
pub let metadata: {String: TenantService.MetadataField}
init(
artifactId: UInt64,
setId: UInt64?,
name: String,
description: String,
maxMintCount: UInt64,
metadata: {String: TenantService.MetadataField}
) {
self.id = TenantService.generateId(ObjectType.FAUCET, TenantService.faucetSeq)
self.artifactId = artifactId
self.setId = setId
self.name = name
self.description = description
self.maxMintCount = maxMintCount
self.metadata = metadata
TenantService.faucetSeq = TenantService.faucetSeq + 1 as UInt64
if self.setId != nil {
let faucetsBySet = TenantService.faucetsBySet[self.setId!]!
faucetsBySet[self.id] = true
}
emit FaucetCreated(self.id)
}
}
// The mutable data for an Faucet
//
pub resource FaucetAdmin {
pub let id: UInt64
pub var mintCount: UInt64
pub var closed: Bool
init(id: UInt64) {
self.id = id
self.mintCount = 0
self.closed = false
}
pub fun close() {
if !self.closed {
self.closed = true
emit FaucetClosed(self.id)
}
}
pub fun logMint(_ count: UInt64) {
pre {
TenantService.closed != true: "The Tenant is closed"
self.closed != true: "The Faucet is closed"
((TenantService.faucets[self.id]!.maxMintCount == (0 as UInt64))
|| (TenantService.faucets[self.id]!.maxMintCount >= (self.mintCount + count))): "The Faucet would exceed it's maxMintCount"
}
self.mintCount = self.mintCount + count
}
destroy() {
emit FaucetDestroyed(self.id)
}
}
// An immutable view for an Faucet and all of it's data
//
pub struct FaucetView {
pub let id: UInt64
pub let artifactId: UInt64
pub let setId: UInt64?
pub let name: String
pub let description: String
pub let maxMintCount: UInt64
pub let metadata: {String: TenantService.MetadataField}
pub let mintCount: UInt64
pub let closed: Bool
init(
id: UInt64,
artifactId: UInt64,
setId: UInt64?,
name: String,
description: String,
maxMintCount: UInt64,
metadata: {String: TenantService.MetadataField},
mintCount: UInt64,
closed: Bool
) {
self.id = id
self.artifactId = artifactId
self.setId = setId
self.name = name
self.description = description
self.maxMintCount = maxMintCount
self.metadata = metadata
self.mintCount = mintCount
self.closed = closed
}
}
// =====================================
// NFT
// =====================================
pub event NFTCreated(_ id: UInt64)
pub event NFTDestroyed(_ id: UInt64)
pub fun getNFTView(_ nft: &NFT): NFTView {
let archetype = self.getArchetypeView(nft.archetypeId)!
let artifact = self.getArtifactView(nft.artifactId)!
var set: SetView? = nil
if nft.setId != nil {
set = self.getSetView(nft.setId!)!
}
var print: PrintView? = nil
if nft.printId != nil {
print = self.getPrintView(nft.printId!)!
}
var faucet: FaucetView? = nil
if nft.faucetId != nil {
faucet = self.getFaucetView(nft.faucetId!)!
}
return NFTView(
id: nft.id,
archetype: archetype,
artifact: artifact,
print: print,
faucet: faucet,
set: set,
serialNumber: nft.serialNumber,
metadata: nft.metadata
)
}
pub fun getNFTViews(_ nfts: [&NFT]): [NFTView] {
let ret: [NFTView] = []
for nft in nfts {
ret.append(self.getNFTView(nft))
}
return ret
}
// The immutable data for an NFT, this is the actual NFT
//
pub resource NFT: NonFungibleToken.INFT {
pub let id: UInt64
pub let archetypeId: UInt64
pub let artifactId: UInt64
pub let printId: UInt64?
pub let faucetId: UInt64?
pub let setId: UInt64?
pub let serialNumber: UInt64
pub let metadata: {String: TenantService.MetadataField}
init(
archetypeId: UInt64,
artifactId: UInt64,
printId: UInt64?,
faucetId: UInt64?,
setId: UInt64?,
metadata: {String: TenantService.MetadataField}
) {
self.id = TenantService.generateId(ObjectType.NFT, TenantService.totalSupply)
self.archetypeId = archetypeId
self.artifactId = artifactId
self.printId = printId
self.faucetId = faucetId
self.setId = setId
self.metadata = metadata
if self.printId != nil {
let printAdmin = &TenantService.printAdmins[self.printId!!] as &PrintAdmin
self.serialNumber = printAdmin.getAndIncrementSerialNumber()
} else {
self.serialNumber = TenantService.nextNftSerialNumber[self.artifactId]!
TenantService.nextNftSerialNumber[self.artifactId] = self.serialNumber + (1 as UInt64)
}
TenantService.totalSupply = TenantService.totalSupply + (1 as UInt64)
if self.setId != nil {
if TenantService.setsByArtifact[self.artifactId] == nil {
TenantService.setsByArtifact[self.artifactId] = {}
}
let setsByArtifact = TenantService.setsByArtifact[self.artifactId]!
setsByArtifact[self.setId!] = true
if TenantService.artifactsBySet[self.setId!] == nil {
TenantService.artifactsBySet[self.setId!] = {}
}
let artifactsBySet = TenantService.artifactsBySet[self.setId!]!
artifactsBySet[self.artifactId] = true
}
if self.faucetId != nil {
if TenantService.faucetsByArtifact[self.artifactId] == nil {
TenantService.faucetsByArtifact[self.artifactId] = {}
}
let faucetsByArtifact = TenantService.faucetsByArtifact[self.artifactId]!
faucetsByArtifact[self.faucetId!] = true
}
if TenantService.artifactsByArchetype[self.archetypeId] == nil {
TenantService.artifactsByArchetype[self.archetypeId] = {}
}
let artifactsByArchetype = TenantService.artifactsByArchetype[self.archetypeId]!
artifactsByArchetype[self.artifactId] = true
emit NFTCreated(self.id)
}
destroy() {
emit NFTDestroyed(self.id)
}
}
// An immutable view for an NFT and all of it's data
//
pub struct NFTView {
pub let id: UInt64
pub let archetype: ArchetypeView
pub let artifact: ArtifactView
pub let print: PrintView?
pub let faucet: FaucetView?
pub let set: SetView?
pub let serialNumber: UInt64
pub let metadata: {String: TenantService.MetadataField}
init(
id: UInt64,
archetype: ArchetypeView,
artifact: ArtifactView,
print: PrintView?,
faucet: FaucetView?,
set: SetView?,
serialNumber: UInt64,
metadata: {String: TenantService.MetadataField}
) {
self.id = id
self.archetype = archetype
self.artifact = artifact
self.print = print
self.faucet = faucet
self.set = set
self.serialNumber = serialNumber
self.metadata = metadata
}
}
// The public version of the collection that accounts can use
// to deposit NFTs into other accounts
//
pub resource interface CollectionPublic {
pub fun deposit(token: @NonFungibleToken.NFT)
pub fun batchDeposit(tokens: @NonFungibleToken.Collection)
pub fun getIDs(): [UInt64]
pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT
pub fun borrowNFTData(id: UInt64): &TenantService.NFT?
pub fun borrowNFTDatas(ids: [UInt64]): [&TenantService.NFT]
}
// The collection where NFTs are stored
//
pub resource Collection: CollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic {
pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}
init() {
self.ownedNFTs <- {}
}
pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
let token <- self.ownedNFTs.remove(key: withdrawID)
?? panic("Cannot withdraw: NFT does not exist in the collection")
emit Withdraw(id: token.id, from: self.owner?.address)
return <-token
}
pub fun batchWithdraw(ids: [UInt64]): @NonFungibleToken.Collection {
var batchCollection <- create Collection()
for id in ids {
batchCollection.deposit(token: <-self.withdraw(withdrawID: id))
}
return <-batchCollection
}
pub fun deposit(token: @NonFungibleToken.NFT) {
let token <- token as! @TenantService.NFT
let id = token.id
let oldToken <- self.ownedNFTs[id] <- token
if self.owner?.address != nil {
emit Deposit(id: id, to: self.owner?.address)
}
destroy oldToken
}
pub fun batchDeposit(tokens: @NonFungibleToken.Collection) {
let keys = tokens.getIDs()
for key in keys {
self.deposit(token: <-tokens.withdraw(withdrawID: key))
}
destroy tokens
}
pub fun getIDs(): [UInt64] {
return self.ownedNFTs.keys
}
pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT {
pre {
TenantService.isObjectType(id, ObjectType.NFT): "Id supplied is not for an nft"
}
return &self.ownedNFTs[id] as &NonFungibleToken.NFT
}
pub fun borrowNFTData(id: UInt64): &TenantService.NFT? {
pre {
TenantService.isObjectType(id, ObjectType.NFT): "Id supplied is not for an nft"
}
if self.ownedNFTs[id] != nil {
let ref = &self.ownedNFTs[id] as auth &NonFungibleToken.NFT
return ref as! &TenantService.NFT
} else {
return nil
}
}
pub fun borrowNFTDatas(ids: [UInt64]): [&TenantService.NFT] {
let nfts: [&TenantService.NFT] = []
for id in ids {
let nft = self.borrowNFTData(id: id)
if nft != nil {
nfts.append(nft!)
}
}
return nfts
}
pub fun getNFTView(id: UInt64): NFTView? {
pre {
TenantService.isObjectType(id, ObjectType.NFT): "Id supplied is not for an nft"
}
let nft = self.borrowNFTData(id: id)
if nft == nil {
return nil
}
return TenantService.getNFTView(nft!)
}
destroy() {
destroy self.ownedNFTs
}
}
pub fun createEmptyCollection(): @NonFungibleToken.Collection {
return <-create TenantService.Collection()
}
// ShardedCollection stores a dictionary of TenantService Collections
// An NFT is stored in the field that corresponds to its id % numBuckets
//
pub resource ShardedCollection: CollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic {
pub var collections: @{UInt64: Collection}
pub let numBuckets: UInt64
init(numBuckets: UInt64) {
self.collections <- {}
self.numBuckets = numBuckets
var i: UInt64 = 0
while i < numBuckets {
self.collections[i] <-! TenantService.createEmptyCollection() as! @Collection
i = i + UInt64(1)
}
}
pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
post {
result.id == withdrawID: "The ID of the withdrawn NFT is incorrect"
}
let bucket = withdrawID % self.numBuckets
let token <- self.collections[bucket]?.withdraw(withdrawID: withdrawID)!
return <-token
}
pub fun batchWithdraw(ids: [UInt64]): @NonFungibleToken.Collection {
var batchCollection <- TenantService.createEmptyCollection()
for id in ids {
batchCollection.deposit(token: <-self.withdraw(withdrawID: id))
}
return <-batchCollection
}
pub fun deposit(token: @NonFungibleToken.NFT) {
let bucket = token.id % self.numBuckets
let collection <- self.collections.remove(key: bucket)!
collection.deposit(token: <-token)
self.collections[bucket] <-! collection
}
pub fun batchDeposit(tokens: @NonFungibleToken.Collection) {
let keys = tokens.getIDs()
for key in keys {
self.deposit(token: <-tokens.withdraw(withdrawID: key))
}
destroy tokens
}
pub fun getIDs(): [UInt64] {
var ids: [UInt64] = []
for key in self.collections.keys {
for id in self.collections[key]?.getIDs() ?? [] {
ids.append(id)
}
}
return ids
}
pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT {
let bucket = id % self.numBuckets
return self.collections[bucket]?.borrowNFT(id: id)!
}
pub fun borrowNFTData(id: UInt64): &TenantService.NFT? {
let bucket = id % self.numBuckets
return self.collections[bucket]?.borrowNFTData(id: id) ?? nil
}
pub fun borrowNFTDatas(ids: [UInt64]): [&TenantService.NFT] {
let nfts: [&TenantService.NFT] = []
for id in ids {
let nft = self.borrowNFTData(id: id)
if nft != nil {
nfts.append(nft!)
}
}
return nfts
}
pub fun getNFTView(id: UInt64): NFTView? {
let bucket = id % self.numBuckets
return self.collections[bucket]?.getNFTView(id: id) ?? nil
}
destroy() {
destroy self.collections
}
}
pub fun createEmptyShardedCollection(numBuckets: UInt64): @ShardedCollection {
return <-create ShardedCollection(numBuckets: numBuckets)
}
// =====================================
// Metadata
// =====================================
// The type of a meta data field
//
pub enum MetadataFieldType: UInt8 {
pub case STRING
pub case NUMBER
pub case BOOLEAN
pub case DATE
pub case DATE_TIME
pub case URL
pub case URL_WITH_HASH
pub case GEO_POINT
}
// a meta data field of variable type
//
pub struct MetadataField {
pub let type: MetadataFieldType
pub let value: AnyStruct
init(_ type: MetadataFieldType, _ value: AnyStruct) {
self.type = type
self.value = value
}
pub fun getStringValue(): String? {
if self.type != MetadataFieldType.STRING {
return nil
}
return self.value as? String
}
pub fun getNumberValue(): String? {
if self.type != MetadataFieldType.NUMBER {
return nil
}
return self.value as? String
}
pub fun getBooleanValue(): Bool? {
if self.type != MetadataFieldType.BOOLEAN {
return nil
}
return self.value as? Bool
}
pub fun getURLValue(): String? {
if self.type != MetadataFieldType.URL {
return nil
}
return self.value as? String
}
pub fun getDateValue(): String? {
if self.type != MetadataFieldType.DATE {
return nil
}
return self.value as? String
}
pub fun getDateTimeValue(): String? {
if self.type != MetadataFieldType.DATE_TIME {
return nil
}
return self.value as? String
}
pub fun getURLWithHashValue(): URLWithHash? {
if self.type != MetadataFieldType.URL_WITH_HASH {
return nil
}
return self.value as? URLWithHash
}
pub fun getGeoPointValue(): GeoPoint? {
if self.type != MetadataFieldType.GEO_POINT {
return nil
}
return self.value as? GeoPoint
}
}
// A url with a hash of the contents found at the url
//
pub struct URLWithHash {
pub let url: String
pub let hash: String?
pub let hashAlgo: String?
init(_ url: String, _ hash: String, _ hashAlgo: String?) {
self.url = url
self.hash = hash
self.hashAlgo = hashAlgo
}
}
// A geo point without any specific projection
//
pub struct GeoPoint {
pub let lat: UFix64
pub let lng: UFix64
init(_ lat: UFix64, _ lng: UFix64) {
self.lat = lat
self.lng = lng
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment