Cadence Cookbook

Contribute

Creating a Vault

Creating a Vault

01 Apr 2022

Contributed by Flow Blockchain

Intermediate

This explains the function that would be used for you to create a new vault in others accounts.

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 createEmptyVault function is a simple utility within the ExampleToken contract that creates and returns a new instance of the Vault resource. This new Vault is initialized with a balance of 0.0 tokens, meaning it starts empty. The function uses the create Vault(balance: 0.0) syntax to create this new Vault resource. The resulting Vault is then returned as a reference (@Vault), which can be used to store and manage tokens later. This function is typically used when setting up a new account or when a fresh vault is needed to hold tokens, ensuring that the vault starts with no tokens in it.

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 configures an account to store and receive tokens defined by // the ExampleToken contract. transaction { prepare(acct: auth(Storage, Capabilities) &Account) { // Create a new empty Vault object let vaultA <- ExampleToken.createEmptyVault() // Store the vault in the account storage acct.storage.save<@ExampleToken.Vault>(<-vaultA, to: /storage/MainVault) log("Empty Vault stored") // Create a public capability to the Vault for the Receiver interface let receiverCap = acct.capabilities.storage.issue<&ExampleToken.Vault>(/storage/MainVault) acct.capabilities.publish(receiverCap, at: /public/MainReceiver) log("Receiver capability created") // Create a public capability to the Vault for the Balance interface let balanceCap = acct.capabilities.storage.issue<&ExampleToken.Vault>(/storage/MainVault) acct.capabilities.publish(balanceCap, at: /public/MainBalance) log("Balance capability created") // Check that the capabilities were created correctly let account = getAccount(acct.address) assert(account.capabilities.borrow<&ExampleToken.Vault>(/public/MainReceiver) != nil, message: "Vault Receiver Reference was not created correctly") } }

This transaction sets up an account to store and manage tokens using the ExampleToken contract. It begins by creating a new, empty Vault to hold tokens, which is initialized with a balance of 0.0. This vault is then stored in the account's private storage.

After that, the transaction creates two public capabilities: one for the Receiver interface, allowing others to deposit tokens into the vault, and another for the Balance interface, enabling others to check the vault's balance. These capabilities are published to public paths so they can be accessed externally.

Finally, the transaction checks that the public capability for receiving tokens was correctly created by attempting to borrow it from the account.


ProgressWorking With Fungible Tokens

Up Next: Vault Minter

75%


Related Recipes

14 Oct 2022
Token Vault
Beginner
01 Apr 2022
Withdrawing Tokens
Beginner
01 Apr 2022
Vault Minter
Intermediate