Smart Contract
DapperUtilityCoin
A.ead892083b3e2c6c.DapperUtilityCoin
1import FungibleToken from 0xf233dcee88fe0abe
2import FungibleTokenMetadataViews from 0xf233dcee88fe0abe
3import MetadataViews from 0x1d7e57aa55817448
4
5access(all) contract DapperUtilityCoin : FungibleToken {
6
7 // Total supply of DapperUtilityCoins in existence
8 access(all) var totalSupply: UFix64
9
10 // Event that is emitted when tokens are withdrawn from a Vault
11 access(all) event TokensWithdrawn(amount: UFix64, from: Address?)
12
13 // Event that is emitted when tokens are deposited to a Vault
14 access(all) event TokensDeposited(amount: UFix64, to: Address?)
15
16 // Event that is emitted when new tokens are minted
17 access(all) event TokensMinted(amount: UFix64)
18
19 // Event that is emitted when tokens are destroyed
20 access(all) event TokensBurned(amount: UFix64)
21
22 // Event that is emitted when a new minter resource is created
23 access(all) event MinterCreated(allowedAmount: UFix64)
24
25 // Event that is emitted when a new burner resource is created
26 access(all) event BurnerCreated()
27
28 // Vault
29 //
30 // Each user stores an instance of only the Vault in their storage
31 // The functions in the Vault and governed by the pre and post conditions
32 // in FungibleToken when they are called.
33 // The checks happen at runtime whenever a function is called.
34 //
35 // Resources can only be created in the context of the contract that they
36 // are defined in, so there is no way for a malicious user to create Vaults
37 // out of thin air. A special Minter resource needs to be defined to mint
38 // new tokens.
39 //
40 access(all) resource Vault: FungibleToken.Vault {
41 access(all) event ResourceDestroyed(id: UInt64 = self.uuid, balance: UFix64 = self.balance)
42
43 // holds the balance of a users tokens
44 access(all) var balance: UFix64
45
46 // initialize the balance at resource creation time
47 init(balance: UFix64) {
48 self.balance = balance
49 }
50
51 /// Called when a fungible token is burned via the `Burner.burn()` method
52 access(contract) fun burnCallback() {
53 if self.balance > 0.0 {
54 DapperUtilityCoin.totalSupply = DapperUtilityCoin.totalSupply - self.balance
55 }
56 self.balance = 0.0
57 }
58
59 /// Asks if the amount can be withdrawn from this vault
60 access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool {
61 return amount <= self.balance
62 }
63
64 access(all) view fun getViews(): [Type] {
65 return DapperUtilityCoin.getContractViews(resourceType: nil)
66 }
67
68 access(all) fun resolveView(_ view: Type): AnyStruct? {
69 return DapperUtilityCoin.resolveContractView(resourceType: nil, viewType: view)
70 }
71
72 /// Get the balance of the vault
73 access(all) view fun getBalance(): UFix64 {
74 return self.balance
75 }
76
77 // withdraw
78 //
79 // Function that takes an integer amount as an argument
80 // and withdraws that amount from the Vault.
81 // It creates a new temporary Vault that is used to hold
82 // the money that is being transferred. It returns the newly
83 // created Vault to the context that called so it can be deposited
84 // elsewhere.
85 //
86 access(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @DapperUtilityCoin.Vault {
87 self.balance = self.balance - amount
88 emit TokensWithdrawn(amount: amount, from: self.owner?.address)
89 return <-create Vault(balance: amount)
90 }
91
92 // deposit
93 //
94 // Function that takes a Vault object as an argument and adds
95 // its balance to the balance of the owners Vault.
96 // It is allowed to destroy the sent Vault because the Vault
97 // was a temporary holder of the tokens. The Vault's balance has
98 // been consumed and therefore can be destroyed.
99 access(all) fun deposit(from: @{FungibleToken.Vault}) {
100 let vault <- from as! @DapperUtilityCoin.Vault
101 self.balance = self.balance + vault.balance
102 emit TokensDeposited(amount: vault.balance, to: self.owner?.address)
103 vault.balance = 0.0
104 destroy vault
105 }
106 /// Returns the storage path where the vault should typically be stored
107 access(all) view fun getDefaultStoragePath(): StoragePath? {
108 return /storage/dapperUtilityCoinVault
109 }
110
111 /// Returns the public path where this vault should have a public capability
112 access(all) view fun getDefaultPublicPath(): PublicPath? {
113 return /public/dapperUtilityCoinReceiver
114 }
115
116 /// Returns the public path where this vault's Receiver should have a public capability
117 access(all) view fun getDefaultReceiverPath(): PublicPath? {
118 return nil
119 }
120
121 access(all) fun createEmptyVault(): @DapperUtilityCoin.Vault {
122 return <-create Vault(balance: 0.0)
123 }
124
125 }
126
127 // createEmptyVault
128 //
129 // Function that creates a new Vault with a balance of zero
130 // and returns it to the calling context. A user must call this function
131 // and store the returned Vault in their storage in order to allow their
132 // account to be able to receive deposits of this token type.
133 //
134 access(all) fun createEmptyVault(vaultType: Type): @DapperUtilityCoin.Vault {
135 return <-create Vault(balance: 0.0)
136 }
137
138
139 access(all) resource Administrator {
140 // createNewMinter
141 //
142 // Function that creates and returns a new minter resource
143 //
144 access(all) fun createNewMinter(allowedAmount: UFix64): @Minter {
145 emit MinterCreated(allowedAmount: allowedAmount)
146 return <-create Minter(allowedAmount: allowedAmount)
147 }
148
149 // createNewBurner
150 //
151 // Function that creates and returns a new burner resource
152 //
153 access(all) fun createNewBurner(): @Burner {
154 emit BurnerCreated()
155 return <-create Burner()
156 }
157 }
158
159 // Minter
160 //
161 // Resource object that token admin accounts can hold to mint new tokens.
162 //
163 access(all) resource Minter {
164
165 // the amount of tokens that the minter is allowed to mint
166 access(all) var allowedAmount: UFix64
167
168 // mintTokens
169 //
170 // Function that mints new tokens, adds them to the total supply,
171 // and returns them to the calling context.
172 //
173 access(all) fun mintTokens(amount: UFix64): @DapperUtilityCoin.Vault {
174 pre {
175 amount > UFix64(0): "Amount minted must be greater than zero"
176 amount <= self.allowedAmount: "Amount minted must be less than the allowed amount"
177 }
178 DapperUtilityCoin.totalSupply = DapperUtilityCoin.totalSupply + amount
179 self.allowedAmount = self.allowedAmount - amount
180 emit TokensMinted(amount: amount)
181 return <-create Vault(balance: amount)
182 }
183
184 init(allowedAmount: UFix64) {
185 self.allowedAmount = allowedAmount
186 }
187 }
188
189 // Burner
190 //
191 // Resource object that token admin accounts can hold to burn tokens.
192 //
193 access(all) resource Burner {
194
195 // burnTokens
196 //
197 // Function that destroys a Vault instance, effectively burning the tokens.
198 //
199 // Note: the burned tokens are automatically subtracted from the
200 // total supply in the Vault destructor.
201 //
202 access(all) fun burnTokens(from: @DapperUtilityCoin.Vault) {
203 let vault <- from as! @DapperUtilityCoin.Vault
204 let amount = vault.balance
205 destroy vault
206 emit TokensBurned(amount: amount)
207 }
208 }
209
210 access(all) view fun getContractViews(resourceType: Type?): [Type] {
211 return [
212 Type<FungibleTokenMetadataViews.FTDisplay>(),
213 Type<FungibleTokenMetadataViews.FTVaultData>()
214 ]
215 }
216
217 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
218 switch viewType {
219 case Type<FungibleTokenMetadataViews.FTDisplay>():
220 let media = MetadataViews.Media(
221 file: MetadataViews.HTTPFile(
222 url: "https://assets-global.website-files.com/603804a7f3c274da06bf9153/60380558c03f544189766973_dapper_logo.png"
223 ),
224 mediaType: "image/svg+xml"
225 )
226 let medias = MetadataViews.Medias([media])
227 return FungibleTokenMetadataViews.FTDisplay(
228 name: "Dapper Utility Coin",
229 symbol: "DUC",
230 description: "",
231 externalURL: MetadataViews.ExternalURL("https://www.dapperlabs.com/"),
232 logos: medias,
233 socials: {
234 "twitter": MetadataViews.ExternalURL("https://twitter.com/dapperlabs")
235 }
236 )
237 case Type<FungibleTokenMetadataViews.FTVaultData>():
238 return FungibleTokenMetadataViews.FTVaultData(
239 storagePath: /storage/dapperUtilityCoinVault,
240 receiverPath: /public/dapperUtilityCoinReceiver,
241 metadataPath: /public/dapperUtilityCoinReceiver,
242 receiverLinkedType: Type<&{FungibleToken.Vault}>(),
243 metadataLinkedType: Type<&{FungibleToken.Vault}>(),
244 createEmptyVaultFunction: (fun(): @{FungibleToken.Vault} {
245 return <-DapperUtilityCoin.createEmptyVault(vaultType: Type<@DapperUtilityCoin.Vault>())
246 })
247 )
248 }
249 return nil
250 }
251
252
253 init() {
254 // we're using a high value as the balance here to make it look like we've got a ton of money,
255 // just in case some contract manually checks that our balance is sufficient to pay for stuff
256 self.totalSupply = 999999999.0
257
258 let admin <- create Administrator()
259 let minter <- admin.createNewMinter(allowedAmount: self.totalSupply)
260 self.account.storage.save(<-admin, to: /storage/dapperUtilityCoinAdmin)
261
262
263 // mint tokens
264 let tokenVault <- minter.mintTokens(amount: self.totalSupply)
265 self.account.storage.save(<-tokenVault, to: /storage/dapperUtilityCoinVault)
266 destroy minter
267
268 // Create a public capability to the stored Vault that only exposes
269 // the `balance` field through the `Balance` interface
270 let vaultCap = self.account.capabilities.storage.issue<&{FungibleToken.Vault}>(/storage/dapperUtilityCoinVault)
271 self.account.capabilities.publish(vaultCap, at: /public/dapperUtilityCoinReceiver)
272 }
273}
274