Smart Contract

FungibleToken

A.f233dcee88fe0abe.FungibleToken

Deployed

1d ago
Feb 25, 2026, 01:40:38 PM UTC

Dependents

3783177 imports
1/**
2
3# The Flow Fungible Token standard
4
5## `FungibleToken` contract
6
7If a users wants to deploy a new token contract, their contract
8needs to implement the FungibleToken interface and their tokens
9need to implement the interfaces defined in this contract.
10
11/// Contributors (please add to this list if you contribute!):
12/// - Joshua Hannan - https://github.com/joshuahannan
13/// - Bastian Müller - https://twitter.com/turbolent
14/// - Dete Shirley - https://twitter.com/dete73
15/// - Bjarte Karlsen - https://twitter.com/0xBjartek
16/// - Austin Kline - https://twitter.com/austin_flowty
17/// - Giovanni Sanchez - https://twitter.com/gio_incognito
18/// - Deniz Edincik - https://twitter.com/bluesign
19/// - Jonny - https://github.com/dryruner
20///
21/// Repo reference: https://github.com/onflow/flow-ft
22
23## `Vault` resource interface
24
25Each fungible token resource type needs to implement the `Vault` resource interface.
26
27## `Provider`, `Receiver`, and `Balance` resource interfaces
28
29These interfaces declare pre-conditions and post-conditions that restrict
30the execution of the functions in the Vault.
31
32It gives users the ability to make custom resources that implement
33these interfaces to do various things with the tokens.
34For example, a faucet can be implemented by conforming
35to the Provider interface.
36
37*/
38
39import ViewResolver from 0x1d7e57aa55817448
40import Burner from 0xf233dcee88fe0abe
41
42/// FungibleToken
43///
44/// Fungible Token implementations should implement the fungible token
45/// interface.
46access(all) contract interface FungibleToken: ViewResolver {
47
48    // An entitlement for allowing the withdrawal of tokens from a Vault
49    access(all) entitlement Withdraw
50
51    /// The event that is emitted when tokens are withdrawn
52    /// from any Vault that implements the `Vault` interface
53    access(all) event Withdrawn(type: String,
54                                amount: UFix64,
55                                from: Address?,
56                                fromUUID: UInt64,
57                                withdrawnUUID: UInt64,
58                                balanceAfter: UFix64)
59
60    /// The event that is emitted when tokens are deposited to
61    /// any Vault that implements the `Vault` interface
62    access(all) event Deposited(type: String,
63                                amount: UFix64,
64                                to: Address?,
65                                toUUID: UInt64,
66                                depositedUUID: UInt64,
67                                balanceAfter: UFix64)
68
69    /// Event that is emitted when the global `Burner.burn()` method
70    /// is called with a non-zero balance
71    access(all) event Burned(type: String, amount: UFix64, fromUUID: UInt64)
72
73    /// Balance
74    ///
75    /// The interface that provides a standard field
76    /// for representing balance
77    ///
78    access(all) resource interface Balance: Burner.Burnable {
79        access(all) var balance: UFix64
80
81        // This default implementation needs to be in a separate interface
82        // from the one in `Vault` so that the conditions get enforced
83        // in the correct one
84        access(contract) fun burnCallback() {
85            self.balance = 0.0
86        }
87    }
88
89    /// Provider
90    ///
91    /// The interface that enforces the requirements for withdrawing
92    /// tokens from the implementing type.
93    ///
94    /// It does not enforce requirements on `balance` here,
95    /// because it leaves open the possibility of creating custom providers
96    /// that do not necessarily need their own balance.
97    ///
98    access(all) resource interface Provider {
99
100        /// Function to ask a provider if a specific amount of tokens
101        /// is available to be withdrawn
102        /// This could be useful to avoid panicing when calling withdraw
103        /// when the balance is unknown
104        /// Additionally, if the provider is pulling from multiple vaults
105        /// it only needs to check some of the vaults until the desired amount
106        /// is reached, potentially helping with performance.
107        ///
108        /// @param amount the amount of tokens requested to potentially withdraw
109        /// @return Bool Whether or not this amount is available to withdraw
110        /// 
111        access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool
112
113        /// withdraw subtracts tokens from the implementing resource
114        /// and returns a Vault with the removed tokens.
115        ///
116        /// The function's access level is `access(Withdraw)`
117        /// So in order to access it, one would either need the object itself
118        /// or an entitled reference with `Withdraw`.
119        ///
120        /// @param amount the amount of tokens to withdraw from the resource
121        /// @return The Vault with the withdrawn tokens
122        ///
123        access(Withdraw) fun withdraw(amount: UFix64): @{Vault} {
124            post {
125                // `result` refers to the return value
126                result.balance == amount:
127                    "FungibleToken.Provider.withdraw: Cannot withdraw tokens!"
128                    .concat("The balance of the withdrawn tokens (").concat(result.balance.toString())
129                    .concat(") is not equal to the amount requested to be withdrawn (")
130                    .concat(amount.toString()).concat(")")
131            }
132        }
133    }
134
135    /// Receiver
136    ///
137    /// The interface that enforces the requirements for depositing
138    /// tokens into the implementing type.
139    ///
140    /// We do not include a condition that checks the balance because
141    /// we want to give users the ability to make custom receivers that
142    /// can do custom things with the tokens, like split them up and
143    /// send them to different places.
144    ///
145    access(all) resource interface Receiver {
146
147        /// deposit takes a Vault and deposits it into the implementing resource type
148        ///
149        /// @param from the Vault that contains the tokens to deposit
150        ///
151        access(all) fun deposit(from: @{Vault})
152
153        /// getSupportedVaultTypes returns a dictionary of Vault types
154        /// and whether the type is currently supported by this Receiver
155        ///
156        /// @return {Type: Bool} A dictionary that indicates the supported types
157        ///                      If a type is not supported, it should be `nil`, not false
158        ///
159        access(all) view fun getSupportedVaultTypes(): {Type: Bool}
160
161        /// Returns whether or not the given type is accepted by the Receiver
162        /// A vault that can accept any type should just return true by default
163        ///
164        /// @param type The type to query about
165        /// @return Bool Whether or not the vault type is supported
166        ///
167        access(all) view fun isSupportedVaultType(type: Type): Bool
168    }
169
170    /// Vault
171    /// Conforms to all other interfaces so that implementations
172    /// only have to conform to `Vault`
173    ///
174    access(all) resource interface Vault: Receiver, Provider, Balance, ViewResolver.Resolver, Burner.Burnable {
175
176        /// Field that tracks the balance of a vault
177        access(all) var balance: UFix64
178
179        /// Called when a fungible token is burned via the `Burner.burn()` method
180        /// Implementations can do any bookkeeping or emit any events
181        /// that should be emitted when a vault is destroyed.
182        /// Many implementations will want to update the token's total supply
183        /// to reflect that the tokens have been burned and removed from the supply.
184        /// Implementations also need to set the balance to zero before the end of the function
185        /// This is to prevent vault owners from spamming fake Burned events.
186        access(contract) fun burnCallback() {
187            pre {
188                emit Burned(type: self.getType().identifier, amount: self.balance, fromUUID: self.uuid)
189            }
190            post {
191                self.balance == 0.0:
192                    "FungibleToken.Vault.burnCallback: Cannot burn this Vault with Burner.burn(). "
193                    .concat("The balance must be set to zero during the burnCallback method so that it cannot be spammed.")
194            }
195        }
196
197        /// getSupportedVaultTypes
198        /// The default implementation is included here because vaults are expected
199        /// to only accepted their own type, so they have no need to provide an implementation
200        /// for this function
201        ///
202        access(all) view fun getSupportedVaultTypes(): {Type: Bool} {
203            // Below check is implemented to make sure that run-time type would
204            // only get returned when the parent resource conforms with `FungibleToken.Vault`. 
205            if self.getType().isSubtype(of: Type<@{FungibleToken.Vault}>()) {
206                return {self.getType(): true}
207            } else {
208                // Return an empty dictionary as the default value for resource who don't
209                // implement `FungibleToken.Vault`, such as `FungibleTokenSwitchboard`, `TokenForwarder` etc.
210                return {}
211            }
212        }
213
214        /// Checks if the given type is supported by this Vault
215        access(all) view fun isSupportedVaultType(type: Type): Bool {
216            return self.getSupportedVaultTypes()[type] ?? false
217        }
218
219        /// withdraw subtracts `amount` from the Vault's balance
220        /// and returns a new Vault with the subtracted balance
221        ///
222        access(Withdraw) fun withdraw(amount: UFix64): @{Vault} {
223            pre {
224                self.balance >= amount:
225                    "FungibleToken.Vault.withdraw: Cannot withdraw tokens! "
226                    .concat("The amount requested to be withdrawn (").concat(amount.toString())
227                    .concat(") is greater than the balance of the Vault (")
228                    .concat(self.balance.toString()).concat(").")
229            }
230            post {
231                result.getType() == self.getType(): 
232                    "FungibleToken.Vault.withdraw: Cannot withdraw tokens! "
233                    .concat("The withdraw method tried to return an incompatible Vault type <")
234                    .concat(result.getType().identifier).concat(">. ")
235                    .concat("It must return a Vault with the same type as self <")
236                    .concat(self.getType().identifier).concat(">.")
237
238                // use the special function `before` to get the value of the `balance` field
239                // at the beginning of the function execution
240                //
241                self.balance == before(self.balance) - amount:
242                    "FungibleToken.Vault.withdraw: Cannot withdraw tokens! " 
243                    .concat("The sender's balance after the withdrawal (")
244                    .concat(self.balance.toString())
245                    .concat(") must be the difference of the previous balance (").concat(before(self.balance.toString()))
246                    .concat(") and the amount withdrawn (").concat(amount.toString()).concat(")")
247
248                emit Withdrawn(
249                        type: result.getType().identifier,
250                        amount: amount,
251                        from: self.owner?.address,
252                        fromUUID: self.uuid,
253                        withdrawnUUID: result.uuid,
254                        balanceAfter: self.balance
255                )
256            }
257        }
258
259        /// deposit takes a Vault and adds its balance to the balance of this Vault
260        ///
261        access(all) fun deposit(from: @{FungibleToken.Vault}) {
262            // Assert that the concrete type of the deposited vault is the same
263            // as the vault that is accepting the deposit
264            pre {
265                from.isInstance(self.getType()): 
266                    "FungibleToken.Vault.deposit: Cannot deposit tokens! "
267                    .concat("The type of the deposited tokens <")
268                    .concat(from.getType().identifier)
269                    .concat("> has to be the same type as the Vault being deposited into <")
270                    .concat(self.getType().identifier)
271                    .concat(">. Check that you are withdrawing and depositing to the correct paths in the sender and receiver accounts ")
272                    .concat("and that those paths hold the same Vault types.")
273            }
274            post {
275                emit Deposited(
276                        type: before(from.getType().identifier),
277                        amount: before(from.balance),
278                        to: self.owner?.address,
279                        toUUID: self.uuid,
280                        depositedUUID: before(from.uuid),
281                        balanceAfter: self.balance
282                )
283                self.balance == before(self.balance) + before(from.balance):
284                    "FungibleToken.Vault.deposit: Cannot deposit tokens! " 
285                    .concat("The receiver's balance after the deposit (")
286                    .concat(self.balance.toString())
287                    .concat(") must be the sum of the previous balance (").concat(before(self.balance.toString()))
288                    .concat(") and the amount deposited (").concat(before(from.balance).toString()).concat(")")
289            }
290        }
291
292        /// createEmptyVault allows any user to create a new Vault that has a zero balance
293        ///
294        /// @return A Vault of the same type that has a balance of zero
295        access(all) fun createEmptyVault(): @{Vault} {
296            post {
297                result.balance == 0.0:
298                    "FungibleToken.Vault.createEmptyVault: Empty Vault creation failed! "
299                    .concat("The newly created Vault must have zero balance but it has a balance of ")
300                    .concat(result.balance.toString())
301
302                result.getType() == self.getType():
303                    "FungibleToken.Vault.createEmptyVault: Empty Vault creation failed! "
304                    .concat("The type of the new Vault <")
305                    .concat(result.getType().identifier)
306                    .concat("> has to be the same type as the Vault that created it <")
307                    .concat(self.getType().identifier)
308                    .concat(">.")
309            }
310        }
311    }
312
313    /// createEmptyVault allows any user to create a new Vault that has a zero balance
314    ///
315    /// @return A Vault of the requested type that has a balance of zero
316    access(all) fun createEmptyVault(vaultType: Type): @{FungibleToken.Vault} {
317        post {
318            result.balance == 0.0:
319                "FungibleToken.createEmptyVault: Empty Vault creation failed! "
320                .concat("The newly created Vault must have zero balance but it has a balance of (")
321                .concat(result.balance.toString()).concat(")")
322
323            result.getType() == vaultType:
324                "FungibleToken.Vault.createEmptyVault: Empty Vault creation failed! "
325                .concat("The type of the new Vault <")
326                .concat(result.getType().identifier)
327                .concat("> has to be the same as the type that was requested <")
328                .concat(vaultType.identifier)
329                .concat(">.")
330        }
331    }
332}