Smart Contract

MoxyVaultToken

A.123cb47fe122f6e3.MoxyVaultToken

Deployed

2d ago
Feb 25, 2026, 01:32:32 AM UTC

Dependents

25 imports
1import FungibleToken from 0xf233dcee88fe0abe
2import MoxyData from 0x123cb47fe122f6e3
3 
4
5pub contract MoxyVaultToken: FungibleToken {
6
7    /// Total supply of MoxyVaultTokens in existence
8    pub var totalSupply: UFix64
9    access(contract) var totalSupplies: @MoxyData.OrderedDictionary
10    pub var numberOfHolders: UInt
11
12    /// TokensInitialized
13    ///
14    /// The event that is emitted when the contract is created
15    pub event TokensInitialized(initialSupply: UFix64)
16
17    /// TokensWithdrawn
18    ///
19    /// The event that is emitted when tokens are withdrawn from a Vault
20    pub event TokensWithdrawn(amount: UFix64, from: Address?)
21
22    /// TokensDeposited
23    ///
24    /// The event that is emitted when tokens are deposited to a Vault
25    pub event TokensDeposited(amount: UFix64, to: Address?)
26
27    /// TokensMinted
28    ///
29    /// The event that is emitted when new tokens are minted
30    pub event TokensMinted(amount: UFix64)
31
32    /// TokensBurned
33    ///
34    /// The event that is emitted when tokens are destroyed
35    pub event TokensBurned(amount: UFix64)
36
37    /// MinterCreated
38    ///
39    /// The event that is emitted when a new minter resource is created
40    pub event MinterCreated(allowedAmount: UFix64)
41
42    /// BurnerCreated
43    ///
44    /// The event that is emitted when a new burner resource is created
45    pub event BurnerCreated()
46
47    pub event MVToMOXYConverterCreated(conversionAmount: UFix64, timestamp: UFix64)
48
49    /// Vault
50    ///
51    /// Each user stores an instance of only the Vault in their storage
52    /// The functions in the Vault and governed by the pre and post conditions
53    /// in FungibleToken when they are called.
54    /// The checks happen at runtime whenever a function is called.
55    ///
56    /// Resources can only be created in the context of the contract that they
57    /// are defined in, so there is no way for a malicious user to create Vaults
58    /// out of thin air. A special Minter resource needs to be defined to mint
59    /// new tokens.
60    ///
61    pub resource Vault: FungibleToken.Provider, FungibleToken.Receiver, FungibleToken.Balance, DailyBalancesInterface, ReceiverInterface {
62
63        /// The total balance of this vault
64        pub var balance: UFix64
65        access(contract) var dailyBalances: @MoxyData.OrderedDictionary
66        
67        // initialize the balance at resource creation time
68        init(balance: UFix64) {
69            self.balance = balance
70            self.dailyBalances <- MoxyData.createNewOrderedDictionary()
71            if (balance > 0.0) {
72                self.dailyBalances.setAmountFor(timestamp: getCurrentBlock().timestamp, amount: balance)
73            }
74        }
75
76        pub fun getDailyBalanceFor(timestamp: UFix64): UFix64? {
77            return self.dailyBalances.getValueOrMostRecentFor(timestamp: timestamp)
78        }
79
80        pub fun getDailyBalancesChangesUpTo(timestamp: UFix64): {UFix64:UFix64} {
81            return self.dailyBalances.getValueChangesUpTo(timestamp: timestamp)
82        }
83
84        pub fun getDailyBalanceChange(timestamp: UFix64): Fix64 {
85            return self.dailyBalances.getValueChange(timestamp: timestamp)
86        }
87
88        pub fun getLastTimestampAdded(): UFix64? {
89            return self.dailyBalances.getLastKeyAdded()
90        }
91
92        pub fun getFirstTimestampAdded(): UFix64? {
93            return self.dailyBalances.getFirstKeyAdded()
94        }
95
96        /// withdraw
97        ///
98        /// Function that takes an amount as an argument
99        /// and withdraws that amount from the Vault.
100        ///
101        /// It creates a new temporary Vault that is used to hold
102        /// the money that is being transferred. It returns the newly
103        /// created Vault to the context that called so it can be deposited
104        /// elsewhere.
105        ///
106        pub fun withdraw(amount: UFix64): @FungibleToken.Vault {
107            panic("MV token can't be withdrawn")
108        }
109
110        access(account) fun withdrawAmount(amount: UFix64): @FungibleToken.Vault {
111            let vault <- self.vaultToConvert(amount: amount) 
112            return <- vault
113        }
114
115        /// deposit
116        ///
117        /// Function that takes a Vault object as an argument and adds
118        /// its balance to the balance of the owners Vault.
119        ///
120        /// It is allowed to destroy the sent Vault because the Vault
121        /// was a temporary holder of the tokens. The Vault's balance has
122        /// been consumed and therefore can be destroyed.
123        ///
124
125        pub fun deposit(from: @FungibleToken.Vault) {
126            panic("MV tokens can't be directly deposited.")
127        }
128
129        // Deposit keeping original daily balances that cames from the vault
130        access(account) fun depositAmount(from: @FungibleToken.Vault) {
131            let vault <- from as! @MoxyVaultToken.Vault
132
133            if (self.owner != nil && self.balance == 0.0 && vault.balance > 0.0) {
134                MoxyVaultToken.numberOfHolders = MoxyVaultToken.numberOfHolders + 1
135            }
136
137            let dailyBalances = vault.dailyBalances.getDictionary()
138
139            for time in dailyBalances.keys {
140                self.dailyBalances.setAmountFor(timestamp: time, amount: dailyBalances[time]!)
141            }
142
143            self.balance = self.balance + vault.balance
144
145            emit TokensDeposited(amount: vault.balance, to: self.owner?.address)
146            
147            vault.dailyBalances.withdrawValueFromOldest(amount: vault.balance)
148            vault.balance = 0.0
149
150            destroy vault
151        }
152
153
154        access(account) fun depositDueConversion(from: @FungibleToken.Vault) {
155            let timestamp = getCurrentBlock().timestamp
156            return self.depositFor(from: <-from, timestamp: timestamp)
157        }
158
159        access(contract) fun depositFor(from: @FungibleToken.Vault, timestamp: UFix64) {
160            let vault <- from as! @MoxyVaultToken.Vault
161
162            if (self.owner != nil && self.balance == 0.0 && vault.balance > 0.0) {
163                MoxyVaultToken.numberOfHolders = MoxyVaultToken.numberOfHolders + 1
164            }
165
166            self.dailyBalances.setAmountFor(timestamp: timestamp, amount: vault.balance)
167
168            self.balance = self.balance + vault.balance
169
170            emit TokensDeposited(amount: vault.balance, to: self.owner?.address)
171            
172            vault.dailyBalances.withdrawValueFromOldest(amount: vault.balance)
173            vault.balance = 0.0
174
175            destroy vault
176        }
177
178        access(contract) fun depositWithAges(balance: UFix64, ages: {UFix64:UFix64}) {
179            post {
180                total == balance : "Cannot assigning ages, please check amounts."
181            }
182
183            if (self.owner != nil && self.balance == 0.0 && balance > 0.0) {
184                MoxyVaultToken.numberOfHolders = MoxyVaultToken.numberOfHolders + 1
185            }
186
187            var total = 0.0
188            for time in ages.keys {
189                self.dailyBalances.setAmountFor(timestamp: time, amount: ages[time]!)
190                total = total + ages[time]!
191            }
192
193            self.balance = self.balance + balance
194        }
195
196        pub fun createNewMVConverter(privateVaultRef: Capability<&MoxyVaultToken.Vault>, allowedAmount: UFix64): @MVConverter {
197            return <- create MVConverter(privateVaultRef: privateVaultRef, allowedAmount: allowedAmount, address: self.owner!.address)
198        }
199
200        access(contract) fun vaultToConvert(amount: UFix64): @FungibleToken.Vault {
201            // Withdraw can only be done when a conversion MV to MOX is requested
202            // withdraw are done from oldest deposits to newer deposits
203            let balanceBefore = self.balance
204
205            let dict = self.dailyBalances.withdrawValueFromOldest(amount: amount)
206            self.balance = self.balance - amount
207            
208            if (self.balance == 0.0 && balanceBefore > 0.0 && self.owner != nil) {
209                MoxyVaultToken.numberOfHolders = MoxyVaultToken.numberOfHolders - 1
210            }
211            
212            emit TokensWithdrawn(amount: amount, from: self.owner?.address)
213            let vault <- MoxyVaultToken.createEmptyVault()
214            
215            vault.depositWithAges(balance: amount, ages: dict)
216        
217            return <- vault
218        }
219
220        
221        destroy() {
222            destroy self.dailyBalances
223            MoxyVaultToken.totalSupply = MoxyVaultToken.totalSupply - self.balance
224        }
225    }
226
227    /// createEmptyVault
228    ///
229    /// Function that creates a new Vault with a balance of zero
230    /// and returns it to the calling context. A user must call this function
231    /// and store the returned Vault in their storage in order to allow their
232    /// account to be able to receive deposits of this token type.
233    ///
234    pub fun createEmptyVault(): @Vault {
235        return <-create Vault(balance: 0.0)
236    }
237
238    pub resource Administrator {
239
240        /// createNewMinter
241        ///
242        /// Function that creates and returns a new minter resource
243        ///
244        access(account) fun createNewMinter(allowedAmount: UFix64): @Minter {
245            emit MinterCreated(allowedAmount: allowedAmount)
246            return <-create Minter(allowedAmount: allowedAmount)
247        }
248
249        /// createNewBurner
250        ///
251        /// Function that creates and returns a new burner resource
252        ///
253        access(account) fun createNewBurner(): @Burner {
254            emit BurnerCreated()
255            return <-create Burner()
256        }
257    }
258
259    /// Minter
260    ///
261    /// Resource object that token admin accounts can hold to mint new tokens.
262    ///
263    pub resource Minter {
264
265        /// The amount of tokens that the minter is allowed to mint
266        pub var allowedAmount: UFix64
267
268        /// mintTokens
269        ///
270        /// Function that mints new tokens, adds them to the total supply,
271        /// and returns them to the calling context.
272        ///
273        pub fun mintTokens(amount: UFix64): @MoxyVaultToken.Vault {
274            let timestamp = getCurrentBlock().timestamp
275            return <-self.mintTokensFor(amount: amount, timestamp: timestamp)
276        }
277
278        pub fun mintTokensFor(amount: UFix64, timestamp: UFix64): @MoxyVaultToken.Vault {
279            pre {
280                amount > 0.0: "Amount minted must be greater than zero"
281                amount <= self.allowedAmount: "Amount minted must be less than the allowed amount"
282            }
283
284            if (!MoxyVaultToken.totalSupplies.canUpdateTo(timestamp: timestamp)) {
285                panic("Cannot mint MV token for events before the last registerd")
286            } 
287
288            MoxyVaultToken.totalSupplies.setAmountFor(timestamp: timestamp, amount: amount)
289
290            MoxyVaultToken.totalSupply = MoxyVaultToken.totalSupply + amount
291
292            self.allowedAmount = self.allowedAmount - amount
293
294            emit TokensMinted(amount: amount)
295            let vault <-create Vault(balance: amount)
296            
297            return <- vault
298        }
299
300        init(allowedAmount: UFix64) {
301            self.allowedAmount = allowedAmount
302        }
303    }
304
305    /// Burner
306    ///
307    /// Resource object that token admin accounts can hold to burn tokens.
308    ///
309    pub resource Burner {
310
311        /// burnTokens
312        ///
313        /// Function that destroys a Vault instance, effectively burning the tokens.
314        ///
315        /// Note: the burned tokens are automatically subtracted from the
316        /// total supply in the Vault destructor.
317        ///
318        pub fun burnTokens(from: @FungibleToken.Vault) {
319            let vault <- from as! @MoxyVaultToken.Vault
320            let amount = vault.balance
321            destroy vault
322            emit TokensBurned(amount: amount)
323        }
324    }
325
326    pub resource MVConverter: Converter {
327        pub var privateVaultRef: Capability<&MoxyVaultToken.Vault>
328        pub var allowedAmount: UFix64
329        pub var address: Address
330
331        pub fun getDailyVault(amount: UFix64): @FungibleToken.Vault {
332            pre {
333                amount > 0.0: "Amount to burn must be greater than zero"
334                amount <= self.allowedAmount: "Amount to burn must be equal or less than the allowed amount. Allowed amount: ".concat(self.allowedAmount.toString()).concat(" amount: ").concat(amount.toString())
335            }
336            self.allowedAmount = self.allowedAmount - amount
337            let vault <- self.privateVaultRef.borrow()!.vaultToConvert(amount: amount) as! @MoxyVaultToken.Vault
338            
339            return <-vault
340        }
341
342        init(privateVaultRef: Capability<&MoxyVaultToken.Vault>, allowedAmount: UFix64, address: Address ) {
343            self.privateVaultRef = privateVaultRef
344            self.allowedAmount = allowedAmount
345            self.address = address
346        }
347    }
348
349    pub resource interface DailyBalancesInterface {
350        pub fun getDailyBalanceFor(timestamp: UFix64): UFix64? 
351        pub fun getDailyBalanceChange(timestamp: UFix64): Fix64
352        pub fun getLastTimestampAdded(): UFix64?
353        pub fun getFirstTimestampAdded(): UFix64?
354        pub fun getDailyBalancesChangesUpTo(timestamp: UFix64): {UFix64:UFix64} 
355    }
356
357    pub resource interface ReceiverInterface {
358        access(account) fun depositDueConversion(from: @FungibleToken.Vault)
359        access(account) fun depositAmount(from: @FungibleToken.Vault)
360    }
361
362    pub resource interface Converter {
363        pub fun getDailyVault(amount: UFix64): @FungibleToken.Vault
364    }
365
366    pub fun getLastTotalSupplyTimestampAdded(): UFix64? {
367        return self.totalSupplies.getLastKeyAdded()
368    }
369
370    pub fun getTotalSupplyFor(timestamp: UFix64): UFix64 {
371        return self.totalSupplies.getValueOrMostRecentFor(timestamp: timestamp)
372    }
373
374    pub fun getDailyChangeTo(timestamp: UFix64): Fix64 {
375        return self.totalSupplies.getValueChange(timestamp: timestamp)
376    }
377
378    access(contract) fun destroyTotalSupply(orderedDictionary: @MoxyData.OrderedDictionary) {
379        self.totalSupplies.destroyWith(orderedDictionary: <-orderedDictionary)
380    }
381
382    pub let moxyVaultTokenVaultStorage: StoragePath
383    pub let moxyVaultTokenVaultPrivate: PrivatePath
384    pub let moxyVaultTokenAdminStorage: StoragePath
385    pub let moxyVaultTokenReceiverPath: PublicPath
386    pub let moxyVaultTokenBalancePath: PublicPath
387    pub let moxyVaultTokenDailyBalancePath: PublicPath
388    pub let moxyVaultTokenReceiverTimestampPath: PublicPath
389    // Paths for Locked tonkens 
390    pub let moxyVaultTokenLockedVaultStorage: StoragePath
391    pub let moxyVaultTokenLockedVaultPrivate: PrivatePath
392    pub let moxyVaultTokenLockedBalancePath: PublicPath
393    pub let moxyVaultTokenLockedReceiverPath: PublicPath
394
395    init() {
396        self.totalSupply = 0.0
397        self.totalSupplies <- MoxyData.createNewOrderedDictionary()
398        self.numberOfHolders = 0
399
400        self.moxyVaultTokenVaultStorage = /storage/moxyVaultTokenVault
401        self.moxyVaultTokenVaultPrivate = /private/moxyVaultTokenVault
402        self.moxyVaultTokenAdminStorage = /storage/moxyVaultTokenAdmin
403        self.moxyVaultTokenReceiverPath = /public/moxyVaultTokenReceiver
404        self.moxyVaultTokenBalancePath = /public/moxyVaultTokenBalance
405        self.moxyVaultTokenDailyBalancePath = /public/moxyVaultTokenDailyBalance
406        self.moxyVaultTokenReceiverTimestampPath = /public/moxyVaultTokenReceiverTimestamp
407        // Locked vaults
408        self.moxyVaultTokenLockedVaultStorage = /storage/moxyVaultTokenLockedVault
409        self.moxyVaultTokenLockedVaultPrivate = /private/moxyVaultTokenLockedVault
410        self.moxyVaultTokenLockedBalancePath = /public/moxyVaultTokenLockedBalance
411        self.moxyVaultTokenLockedReceiverPath = /public/moxyVaultTokenLockedReceiver
412
413        // Create the Vault with the total supply of tokens and save it in storage
414        //
415        let vault <- create Vault(balance: self.totalSupply)
416        self.account.save(<-vault, to: self.moxyVaultTokenVaultStorage)
417
418        // Private access to MoxyVault token Vault
419        self.account.link<&MoxyVaultToken.Vault>(
420            self.moxyVaultTokenVaultPrivate,
421            target: self.moxyVaultTokenVaultStorage
422        )
423
424        // Create a public capability to the stored Vault that only exposes
425        // the `deposit` method through the `Receiver` interface
426        //
427        self.account.link<&{FungibleToken.Receiver}>(
428            self.moxyVaultTokenReceiverPath,
429            target: self.moxyVaultTokenVaultStorage
430        )
431        // Link to receive tokens in a specific timestamp
432        self.account.link<&{MoxyVaultToken.ReceiverInterface}>(
433            self.moxyVaultTokenReceiverTimestampPath,
434            target: self.moxyVaultTokenVaultStorage
435        )
436
437        // Create a public capability to the stored Vault that only exposes
438        // the `balance` field through the `Balance` interface
439        //
440        self.account.link<&MoxyVaultToken.Vault{FungibleToken.Balance}>(
441            self.moxyVaultTokenBalancePath,
442            target: self.moxyVaultTokenVaultStorage
443        )
444        self.account.link<&MoxyVaultToken.Vault{DailyBalancesInterface}>(
445            self.moxyVaultTokenDailyBalancePath,
446            target: self.moxyVaultTokenVaultStorage
447        )
448
449        let admin <- create Administrator()
450        self.account.save(<-admin, to: self.moxyVaultTokenAdminStorage)
451
452        // Emit an event that shows that the contract was initialized
453        //
454        emit TokensInitialized(initialSupply: self.totalSupply)
455    }
456}
457 
458