Smart Contract

OzoneToken

A.256599e1b091be12.OzoneToken

Deployed

5h ago
Mar 02, 2026, 06:17:16 AM UTC

Dependents

0 imports
1//SPDX-License-Identifier : CC-BY-NC-4.0
2
3import FungibleToken from 0xf233dcee88fe0abe
4import FungibleTokenMetadataViews from 0xf233dcee88fe0abe
5import MetadataViews from 0x1d7e57aa55817448
6
7access(all) contract OzoneToken: FungibleToken {
8
9    // Event that is emitted when the contract is created
10    access(all) event TokensInitialized(initialSupply: UFix64)
11
12    // Event that is emitted when tokens are withdrawn from a Vault
13    access(all) event TokensWithdrawn(amount: UFix64, from: Address?)
14
15    // Event that is emitted when tokens are deposited to a Vault
16    access(all) event TokensDeposited(amount: UFix64, to: Address?)
17
18    // Event that is emitted when new tokens are minted
19    access(all) event TokensMinted(amount: UFix64)
20
21    // Event that is emitted when tokens are destroyed
22    access(all) event TokensBurned(amount: UFix64)
23
24    // Event that is emitted when a new burner resource is created
25    access(all) event BurnerCreated()
26
27    // The storage path for the admin resource
28    access(all) let AdminStoragePath: StoragePath
29
30    // The storage path for ozone token vault
31    access(all) let OzoneTokenVaultPath: StoragePath
32
33    // The public path for ozone token receiver
34    access(all) let OzoneTokenReceiverPath: PublicPath
35
36    // The public path for ozone token balance
37    access(all) let OzoneTokenBalancePath: PublicPath
38
39    // The storage Path for minters' MinterProxy
40    access(all) let MinterProxyStoragePath: StoragePath
41
42    // The public path for minters' MinterProxy capability
43    access(all) let MinterProxyPublicPath: PublicPath
44
45    // Event that is emitted when a new minter resource is created
46    access(all) event MinterCreated()
47
48    // Total supply of ozonetoken in existence
49    access(all) var totalSupply: UFix64
50
51    /// Function that resolves a metadata view for this contract.
52    ///
53    /// @param view: The Type of the desired view.
54    /// @return A structure representing the requested view.
55    ///
56    access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
57        switch viewType {
58            case Type<FungibleTokenMetadataViews.FTView>():
59                return FungibleTokenMetadataViews.FTView(
60                    ftDisplay: self.resolveContractView(resourceType: nil, viewType: Type<FungibleTokenMetadataViews.FTDisplay>()) as! FungibleTokenMetadataViews.FTDisplay?,
61                    ftVaultData: self.resolveContractView(resourceType: nil, viewType: Type<FungibleTokenMetadataViews.FTVaultData>()) as! FungibleTokenMetadataViews.FTVaultData?
62                )
63            case Type<FungibleTokenMetadataViews.FTDisplay>():
64                let media = MetadataViews.Media(
65                        file: MetadataViews.HTTPFile(
66                        url: ""
67                    ),
68                    mediaType: ""
69                )
70                let medias = MetadataViews.Medias([media])
71                return FungibleTokenMetadataViews.FTDisplay(
72                    name: "Ozone Metaverse Token",
73                    symbol: "OZONE",
74                    description: "Ozone is the enterprise grade platform for virtual worlds building. Simple to use - Powerful - 100% browser based.",
75                    externalURL: MetadataViews.ExternalURL("https://ozonemetaverse.io"),
76                    logos: medias,
77                    socials: {
78                        "twitter": MetadataViews.ExternalURL("https://twitter.com/ozonemetaverse"),
79                            "discord": MetadataViews.ExternalURL("https://discord.gg/ozonemetaverse")
80                    }
81                )
82            case Type<FungibleTokenMetadataViews.FTVaultData>():
83                return FungibleTokenMetadataViews.FTVaultData(
84                    storagePath: self.OzoneTokenVaultPath,
85                    receiverPath: self.OzoneTokenReceiverPath,
86                    metadataPath: self.OzoneTokenBalancePath,
87                    receiverLinkedType: Type<&OzoneToken.Vault>(),
88                    metadataLinkedType: Type<&OzoneToken.Vault>(),
89                    createEmptyVaultFunction: (fun(): @{FungibleToken.Vault} {
90                        return <-OzoneToken.createEmptyVault(vaultType: Type<@OzoneToken.Vault>())
91                    })
92                )
93            case Type<FungibleTokenMetadataViews.TotalSupply>():
94                return FungibleTokenMetadataViews.TotalSupply(
95                    totalSupply: OzoneToken.totalSupply
96                )
97        }
98        return nil
99    }
100
101    /// Function that returns all the Metadata Views implemented by a Non Fungible Token
102    ///
103    /// @return An array of Types defining the implemented views. This value will be used by
104    ///         developers to know which parameter to pass to the resolveView() method.
105    ///
106    access(all) view fun getContractViews(resourceType: Type?): [Type] {
107        return [
108            Type<FungibleTokenMetadataViews.FTView>(),
109            Type<FungibleTokenMetadataViews.FTDisplay>(),
110            Type<FungibleTokenMetadataViews.FTVaultData>(),
111            Type<FungibleTokenMetadataViews.TotalSupply>()
112        ]
113    }
114
115    // Vault
116    //
117    // Each user stores an instance of only the Vault in their storage
118    // The functions in the Vault are governed by the pre and post conditions
119    // in FungibleToken when they are called.
120    // The checks happen at runtime whenever a function is called.
121    //
122    // Resources can only be created in the context of the contract that they
123    // are defined in, so there is no way for a malicious user to create Vaults
124    // out of thin air. A special Minter resource needs to be defined to mint
125    // new tokens.
126    //
127    access(all) resource Vault: FungibleToken.Provider, FungibleToken.Receiver, FungibleToken.Balance, FungibleToken.Vault {
128
129        // holds the balance of a users tokens
130        access(all) var balance: UFix64
131
132        // initialize the balance at resource creation time
133        init(balance: UFix64) {
134            self.balance = balance
135        }
136
137        /// Called when a fungible token is burned via the `Burner.burn()` method
138        access(contract) fun burnCallback() {
139            if self.balance > 0.0 {
140                OzoneToken.totalSupply = OzoneToken.totalSupply - self.balance
141            }
142            self.balance = 0.0
143        }
144
145        // withdraw
146        //
147        // Function that takes an integer amount as an argument
148        // and withdraws that amount from the Vault.
149        // It creates a new temporary Vault that is used to hold
150        // the money that is being transferred. It returns the newly
151        // created Vault to the context that called so it can be deposited
152        // elsewhere.
153        //
154        access(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @{FungibleToken.Vault} {
155            self.balance = self.balance - amount
156            emit TokensWithdrawn(amount: amount, from: self.owner?.address)
157            return <-create Vault(balance: amount)
158        }
159        
160        access(all) view fun getViews(): [Type] {
161            return OzoneToken.getContractViews(resourceType: nil)
162        }
163
164        access(all) fun resolveView(_ view: Type): AnyStruct? {
165            return OzoneToken.resolveContractView(resourceType: nil, viewType: view)
166        }
167
168        /// Asks if the amount can be withdrawn from this vault
169        access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool {
170            return amount <= self.balance
171        }
172
173        access(all) fun createEmptyVault(): @OzoneToken.Vault {
174            return <-create Vault(balance: 0.0)
175        }
176
177        /// Returns whether the specified type can be deposited
178        access(all) view fun isSupportedVaultType(type: Type): Bool {
179            return self.getSupportedVaultTypes()[type] ?? false
180        }
181    
182        // deposit
183        //
184        // Function that takes a Vault object as an argument and adds
185        // its balance to the balance of the owners Vault.
186        // It is allowed to destroy the sent Vault because the Vault
187        // was a temporary holder of the tokens. The Vault's balance has
188        // been consumed and therefore can be destroyed.
189        access(all) fun deposit(from: @{FungibleToken.Vault}) {
190            let vault <- from as! @OzoneToken.Vault
191            self.balance = self.balance + vault.balance
192            emit TokensDeposited(amount: vault.balance, to: self.owner?.address)
193            vault.balance = 0.0
194            destroy vault
195        }
196    }
197
198    // createEmptyVault
199    //
200    // Function that creates a new Vault with a balance of zero
201    // and returns it to the calling context. A user must call this function
202    // and store the returned Vault in their storage in order to allow their
203    // account to be able to receive deposits of this token type.
204    //
205    access(all) fun createEmptyVault(vaultType: Type): @OzoneToken.Vault {
206        return <-create Vault(balance: 0.0)
207    }
208
209    // Minter
210    //
211    // Resource object that can mint new tokens.
212    // The admin stores this and passes it to the minter account as a capability wrapper resource.
213    //
214    access(all) resource Minter {
215
216        // mintTokens
217        //
218        // Function that mints new tokens, adds them to the total supply,
219        // and returns them to the calling context.
220        //
221        access(all) fun mintTokens(amount: UFix64): @OzoneToken.Vault {
222            pre {
223                amount > 0.0: "Amount minted must be greater than zero"
224            }
225            OzoneToken.totalSupply = OzoneToken.totalSupply + amount
226            emit TokensMinted(amount: amount)
227            return <-create Vault(balance: amount)
228        }
229
230    }
231
232    // Burner
233    //
234    // Resource object that token admin accounts can hold to burn tokens.
235    //
236    access(all) resource Burner {
237
238        // burnTokens
239        //
240        // Function that destroys a Vault instance, effectively burning the tokens.
241        //
242        // Note: the burned tokens are automatically subtracted from the
243        // total supply in the Vault destructor.
244        //
245        access(all) fun burnTokens(from: @OzoneToken.Vault) {
246            let vault <- from
247            let amount = vault.balance
248            destroy vault
249            emit TokensBurned(amount: amount)
250        }
251    }
252
253    access(all) resource interface MinterProxyPublic {
254        access(all) fun setMinterCapability(cap: Capability<&Minter>)
255    }
256
257    // MinterProxy
258    //
259    // Resource object holding a capability that can be used to mint new tokens.
260    // The resource that this capability represents can be deleted by the admin
261    // in order to unilaterally revoke minting capability if needed.
262
263    access(all) resource MinterProxy: MinterProxyPublic {
264
265        // access(self) so nobody else can copy the capability and use it.
266        access(self) var minterCapability: Capability<&Minter>?
267
268        // Anyone can call this, but only the admin can create Minter capabilities,
269        // so the type system constrains this to being called by the admin.
270        access(all) fun setMinterCapability(cap: Capability<&Minter>) {
271            self.minterCapability = cap
272        }
273
274        access(all) fun mintTokens(amount: UFix64): @OzoneToken.Vault {
275            return <- self.minterCapability!
276            .borrow()!
277            .mintTokens(amount:amount)
278        }
279
280        init() {
281            self.minterCapability = nil
282        }
283
284    }
285
286    // createMinterProxy
287    //
288    // Function that creates a MinterProxy.
289    // Anyone can call this, but the MinterProxy cannot mint without a Minter capability,
290    // and only the admin can provide that.
291    //
292    access(all) fun createMinterProxy(): @MinterProxy {
293        return <- create MinterProxy()
294    }
295
296    // Administrator
297    //
298    // A resource that allows new minters to be created
299    //
300    // We will only want one minter for now, but might need to add or replace them in future.
301    // The Minter/Minter Proxy structure enables this.
302    // Ideally we would create this structure in a single function, generate the paths from the address
303    // and cache all of this information to enable easy revocation but String/Path comversion isn't yet supported.
304    //
305    access(all) resource Administrator {
306
307        // createNewMinter
308        //
309        // Function that creates a Minter resource.
310        // This should be stored at a unique path in storage then a capability to it wrapped
311        // in a MinterProxy to be stored in a minter account's storage.
312        // This is done by the minter account running:
313        // transactions/ozonetoken/minter/setup_minter_account.cdc
314        // then the admin account running:
315        // transactions/ozonetoken/admin/deposit_minter_capability.cdc
316        //
317        access(all) fun createNewMinter(): @Minter {
318            emit MinterCreated()
319            return <- create Minter()
320        }
321
322        // createNewBurner
323        //
324        // Function that creates and returns a new burner resource
325        //
326        access(all) fun createNewBurner(): @Burner {
327            emit BurnerCreated()
328            return <-create Burner()
329        }
330    }
331
332    init() {
333        self.AdminStoragePath = /storage/OzonetokenAdmin
334        self.MinterProxyPublicPath = /public/OzonetokenMinterProxy
335        self.MinterProxyStoragePath = /storage/OzonetokenMinterProxy
336
337        self.OzoneTokenVaultPath = /storage/OzonetokenVault
338        self.OzoneTokenReceiverPath = /public/OzonetokenReceiver
339        self.OzoneTokenBalancePath = /public/OzonetokenBalance
340
341        self.totalSupply = 0.0
342
343        let admin <- create Administrator()
344
345        // Emit an event that shows that the contract was initialized
346        emit TokensInitialized(initialSupply: 0.0)
347
348        let minter <- admin.createNewMinter()
349
350        let mintedVault <- minter.mintTokens(amount: 1000000.0)
351
352        destroy minter
353
354        self.account.storage.save(<-admin, to: self.AdminStoragePath)
355        self.account.storage.save(<-mintedVault, to: self.OzoneTokenVaultPath)
356    
357        let capRes = self.account.capabilities.storage.issue<&OzoneToken.Vault>(self.OzoneTokenVaultPath)
358        self.account.capabilities.publish(capRes, at: self.OzoneTokenReceiverPath)
359
360        let capBalance = self.account.capabilities.storage.issue<&OzoneToken.Vault>(self.OzoneTokenVaultPath)
361        self.account.capabilities.publish(capBalance, at: self.OzoneTokenBalancePath)
362    }
363}
364