Cadence Cookbook

Contribute

Vault Minter

Vault Minter

01 Apr 2022

Contributed by Flow Blockchain

Intermediate

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.


ProgressWorking With Fungible Tokens

100%


Related Recipes

14 Oct 2022
Token Vault
Beginner
01 Apr 2022
Withdrawing Tokens
Beginner
01 Apr 2022
Creating a Vault
Intermediate