Vault Minter
Vault Minter
01 Apr 2022
Contributed by Flow Blockchain
This resource is used by the admin to mint more tokens.
Smart Contract Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
/// ExampleToken.cdc
///
/// The ExampleToken contract is a sample implementation of a fungible token on Flow.
///
/// Fungible tokens behave like everyday currencies -- they can be minted, transferred or
/// traded for digital goods.
///
/// This is a basic implementation of a Fungible Token and is NOT meant to be used in production
/// See the Flow Fungible Token standard for real examples: https://github.com/onflow/flow-ft
access(all) contract ExampleToken {
access(all) entitlement Withdraw
access(all) let VaultStoragePath: StoragePath
access(all) let VaultPublicPath: PublicPath
access(all) var totalSupply: UFix64
/// Balance
///
/// The interface that provides a standard field
/// for representing balance
///
access(all) resource interface Balance {
access(all) var balance: UFix64
}
/// Provider
///
/// The interface that enforces the requirements for withdrawing
/// tokens from the implementing type.
///
/// It does not enforce requirements on `balance` here,
/// because it leaves open the possibility of creating custom providers
/// that do not necessarily need their own balance.
///
access(all) resource interface Provider {
/// withdraw subtracts tokens from the implementing resource
/// and returns a Vault with the removed tokens.
///
/// The function's access level is `access(Withdraw)`
/// So in order to access it, one would either need the object itself
/// or an entitled reference with `Withdraw`.
///
/// @param amount the amount of tokens to withdraw from the resource
/// @return The Vault with the withdrawn tokens
///
access(Withdraw) fun withdraw(amount: UFix64): @Vault {
post {
// `result` refers to the return value
result.balance == amount:
"ExampleToken.Provider.withdraw: Cannot withdraw tokens!"
.concat("The balance of the withdrawn tokens (").concat(result.balance.toString())
.concat(") is not equal to the amount requested to be withdrawn (")
.concat(amount.toString()).concat(")")
}
}
}
/// Receiver
///
/// The interface that enforces the requirements for depositing
/// tokens into the implementing type.
///
/// We do not include a condition that checks the balance because
/// we want to give users the ability to make custom receivers that
/// can do custom things with the tokens, like split them up and
/// send them to different places.
///
access(all) resource interface Receiver {
/// deposit takes a Vault and deposits it into the implementing resource type
///
/// @param from the Vault that contains the tokens to deposit
///
access(all) fun deposit(from: @Vault)
}
/// Vault
///
/// Each user stores an instance of only the Vault in their storage
/// The functions in the Vault are governed by the pre and post conditions
/// in the interfaces when they are called.
/// The checks happen at runtime whenever a function is called.
///
/// Resources can only be created in the context of the contract that they
/// are defined in, so there is no way for a malicious user to create Vaults
/// out of thin air. A special Minter resource or constructor function needs to be defined to mint
/// new tokens.
///
access(all) resource Vault: Balance, Provider, Receiver {
/// keeps track of the total balance of the account's tokens
access(all) var balance: UFix64
/// initialize the balance at resource creation time
init(balance: UFix64) {
self.balance = balance
}
/// withdraw
///
/// Function that takes an integer amount as an argument
/// and withdraws that amount from the Vault.
///
/// It creates a new temporary Vault that is used to hold
/// the money that is being transferred. It returns the newly
/// created Vault to the context that called so it can be deposited
/// elsewhere.
///
access(Withdraw) fun withdraw(amount: UFix64): @Vault {
pre {
self.balance >= amount:
"ExampleToken.Vault.withdraw: Cannot withdraw tokens! "
.concat("The amount requested to be withdrawn (").concat(amount.toString())
.concat(") is greater than the balance of the Vault (")
.concat(self.balance.toString()).concat(").")
}
self.balance = self.balance - amount
return <-create Vault(balance: amount)
}
/// deposit
///
/// Function that takes a Vault object as an argument and adds
/// its balance to the balance of the owners Vault.
///
/// It is allowed to destroy the sent Vault because the Vault
/// was a temporary holder of the tokens. The Vault's balance has
/// been consumed and therefore can be destroyed.
access(all) fun deposit(from: @Vault) {
self.balance = self.balance + from.balance
destroy from
}
}
/// createEmptyVault
///
access(all) fun createEmptyVault(): @Vault {
return <-create Vault(balance: 0.0)
}
// VaultMinter
//
// Resource object that an admin can control to mint new tokens
access(all) resource VaultMinter {
// Function that mints new tokens and deposits into an account's vault
// using their `{Receiver}` reference.
// We say `&{Receiver}` to say that the recipient can be any resource
// as long as it implements the Receiver interface
access(all) fun mintTokens(amount: UFix64, recipient: Capability<&{Receiver}>) {
let recipientRef = recipient.borrow()
?? panic("ExampleToken.VaultMinter.mintTokens: Could not borrow a receiver reference to "
.concat("the specified recipient's ExampleToken.Vault")
.concat(". Make sure the account has set up its account ")
.concat("with an ExampleToken Vault and valid capability."))
ExampleToken.totalSupply = ExampleToken.totalSupply + UFix64(amount)
recipientRef.deposit(from: <-create Vault(balance: amount))
}
}
/// The init function for the contract. All fields in the contract must
/// be initialized at deployment. This is just an example of what
/// an implementation could do in the init function. The numbers are arbitrary.
init() {
self.VaultStoragePath = /storage/CadenceFungibleTokenTutorialVault
self.VaultPublicPath = /public/CadenceFungibleTokenTutorialReceiver
self.totalSupply = 30.0
// create the Vault with the initial balance and put it in storage
// account.save saves an object to the specified `to` path
// The path is a literal path that consists of a domain and identifier
// The domain must be `storage`, `private`, or `public`
// the identifier can be any name
let vault <- create Vault(balance: self.totalSupply)
self.account.storage.save(<-vault, to: self.VaultStoragePath)
// Create a new VaultMinter resource and store it in account storage
self.account.storage.save(<-create VaultMinter(), to: /storage/CadenceFungibleTokenTutorialMinter)
}
}
The VaultMinter resource is designed to allow the controlled minting of new tokens. Access to this resource should be strictly limited, ideally to admins or other highly trusted entities, to maintain the integrity of the token supply.
The VaultMinter provides a mintTokens function that takes two arguments: the amount of tokens to mint and a capability that implements the Receiver interface. Before proceeding, it validates that the provided capability can accept deposits. Once the capability is verified, the specified amount of tokens is added to the total minted supply, ensuring accurate tracking of the token supply.
Finally, the newly minted tokens are encapsulated in a Vault resource and deposited directly into the account associated with the provided Receiver capability.
Transaction Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import "ExampleToken"
// This transaction mints tokens and deposits them into the caller's vault
transaction {
// Local variable for storing the reference to the minter resource
let mintingRef: &ExampleToken.VaultMinter
// Local variable for storing the receiver capability of the caller
var receiver: Capability<&{ExampleToken.Receiver}>
prepare(acct: auth(Storage, Capabilities) &Account) {
// Borrow a reference to the stored, private minter resource
let minter = acct.storage.borrow<&ExampleToken.VaultMinter>(
from: /storage/CadenceFungibleTokenTutorialMinter
) ?? panic("Could not borrow a reference to the minter")
self.mintingRef = minter
// Issue a Receiver capability for the caller's Vault
let receiverCap = acct.capabilities.storage.issue<&{ExampleToken.Receiver}>(
/storage/CadenceFungibleTokenTutorialVault
)
self.receiver = receiverCap
}
execute {
// Mint 30 tokens and deposit them into the caller's Vault
self.mintingRef.mintTokens(amount: 30.0, recipient: self.receiver)
log("30 tokens minted and deposited to the caller's account")
}
}
To execute this transaction, the first step is to verify that the signer of the transaction has access to a VaultMinter resource. This resource is required for minting new tokens. If the VaultMinter is available, you then retrieve the account that will receive the minted tokens. This account must have a valid Receiver capability to accept the tokens.
During the execution phase of the transaction, you specify the amount of tokens to be minted and provide the recipient's Receiver capability as arguments. The VaultMinter will mint the specified tokens, validate the recipient's capability, and securely deposit the newly minted tokens into the recipient's account.
100%