03 Resources
3.5 Working with Resource Containers

While we can identify a resource by its
When a resource is created and comes into existence, it doesn’t need to immediately have a supply with it. However, when a resource with no supply is minted, a transaction is created where the resource’s supply is immediately transferred to a temporary resource container such as a bucket. This bucket must then be dealt with by the time the transaction end, given its transient nature and must then be deposited into a permanent resource container such as a vault. Resource containers are intuitive abstractions which not only help us imagine how resources move between places in a digital environment, but also provides safety by helping us make conscious decisions.
ResourceAddress and map it to its ResourceManager to interact or retrieve some information about the resource, we can’t move resources around with these. To move a a supply of a resource we must use resource containers. Resource containers such as buckets or vaults serve an important purpose within Scrypto's asset-oriented design when it comes to managing a resource’s supply. When a resource is created and comes into existence, it doesn’t need to immediately have a supply with it. However, when a resource with no supply is minted, a transaction is created where the resource’s supply is immediately transferred to a temporary resource container such as a bucket. This bucket must then be dealt with by the time the transaction end, given its transient nature and must then be deposited into a permanent resource container such as a vault. Resource containers are intuitive abstractions which not only help us imagine how resources move between places in a digital environment, but also provides safety by helping us make conscious decisions.
//Buckets
Buckets are designed to be used to move a resource’s supply between components, but are dropped at the end of a given transaction. If the resource’s supply has yet to be dealt with by depositing it into a vault by the time the bucket drops, a dangling error occurs. This serves an imperative condition which allows us to move resources around, yet promotes conscientious habits to always account for the supply or resources we wish to move around.
Each bucket is also designed to only carry one kind of resource. For example, if we have a supply of “Special Tokens” in one bucket, a supply of “Hello Tokens” cannot be put into that bucket. This prevents conflation of what supply of resource is contained within each particular bucket and prevents the wrong kind of resource to accidentally be put into a bucket that it doesn’t belong to.
Further, buckets also have methods which we can call to inspect the contents of the bucket such as the
Each bucket is also designed to only carry one kind of resource. For example, if we have a supply of “Special Tokens” in one bucket, a supply of “Hello Tokens” cannot be put into that bucket. This prevents conflation of what supply of resource is contained within each particular bucket and prevents the wrong kind of resource to accidentally be put into a bucket that it doesn’t belong to.
Further, buckets also have methods which we can call to inspect the contents of the bucket such as the
ResourceAddress of the resource it contains, the amount, its type, and more. As such, buckets provide useful utilities to manage and account for the supply of resource it holds as it’s being moved around within and between components.//Vaults
The primary function of a vault is to act as permanent resource containers designed for the safe storage of a resource's supply. Similar to buckets, vaults are constrained to containing only one specific type of resource. If an attempt is made to deposit a bucket of one resource type into a vault intended for a different resource, an error will occur, ensuring accurate tracking of resources being moved.
Within a blueprint, you can determine the vaults your components will have and what resources those vaults will accept. These vaults will be bounded within the component and can’t be transferred. Vaults have similar methods a bucket has to inspect and manage resources within a component.
Within a blueprint, you can determine the vaults your components will have and what resources those vaults will accept. These vaults will be bounded within the component and can’t be transferred. Vaults have similar methods a bucket has to inspect and manage resources within a component.
//Using Resource Containers
Below are some examples which demonstrates how resource containers can be used. They’re not fully exhaustive, but will give you a working concept to where you can apply this to your own use case.
Accepting a Bucket of Resource
When users are interacting with DeFi applications, users are likely going to send tokens to the applications in some form. So, the basic design of accepting tokens is shown as so:
pub fn accept_tokens(&mut self, bucket: Bucket) { self.token_vault.put(bucket);}This is straightforward implementation where our method will accept a bucket as an argument and is then deposited to the component vault, by calling
.put() method on the vault itself and passing the bucket into it.Creating Safety Measures of Resources Accepted
Since buckets have methods to inspect the resource it contained, we can call
.resource_address() on the bucket to view its resource identifier against the vault which we expect the resource to be deposited to. It’s worth highlighting that, by default, this isn’t actually necessary since the Radix Engine won’t allow incorrect bucket of resource to be deposited to the vault that isn’t supposed to accept it. Although, we can use assertions to create friendly messages.pub fn accept_tokens(&mut self, bucket: Bucket) { // Not required by default, but we can use assertions to send friendly // messages to the user! assert_eq!( bucket.resource_address(), self.token_vault.resource_address(), "The token you sent is incorrect. Please send the correct token!" ); self.token_vault.put(bucket);}Line 3-7: We use the macro
assert_eq! to make sure the ResourceAddress of the bucket is the same as the vault. Else, it will send a friendly message to the user to pass the correct token.Returning a Bucket of Resource
When we need to send back tokens to the user, we’ll return the user a bucket of tokens.
pub fn receive_tokens(&mut self, amount: Decimal) -> Bucket { let return_tokens: Bucket = self.token_vault.take(amount); // States that we are returning the Bucket of tokens from // our `return_token` variable return_tokens}Line 1: In our method signature, we’ll request the user’s input of the amount of tokens the user would like to receive from this component and it will return a bucket of those tokens.Line 2: We use the
We don’t have to create a variable for this logic, but it does help our code look more clear.
.take() method the vault provides and pass the amount that the user will request as an argument.We don’t have to create a variable for this logic, but it does help our code look more clear.
Creating an Empty Bucket
Our method might have a condition where if the condition is met, it will return a bucket of resource, if not then nothing happens. Typically, this is a design to incentivize users to interact with your dApp by rewarding certain method calls.
pub fn give_tokens(&mut self, bucket_of_token: Bucket) -> Bucket { if bucket_of_token.is_empty() { let bucket = self.special_token_manager.create_empty_bucket(); return bucket } else { self.gift_vault.put(bucket_of_token); let bucket = self.special_token_manager.mint(1); return bucket }}Returning Overpayments
If our component sells a service or tokens, there can be times where the user may accidentally send more payments than what’s required. For friendly customer experience, we can return the overpayment back to the user!
pub fn buy_something(&mut self, mut bucket: Bucket) -> Bucket { let price: Decimal = dec!(10); // Just another way to use assertions to make sure the user sends // us enough funds! assert!( bucket.amount() > dec!(10), "Not enough tokens sent!" ); // We take the amount we need and put it into another `Bucket` let payment: Bucket = bucket.take(price); // Then deposit the payment `Bucket` into our `Vault`. self.token_vault.put(payment); // But we return any excess payments (if any) back to the user! bucket}Line 1: Note that we need to make our bucket mutable so that we can modify its content later. We also return a bucket for any overpayments available.
//Using Granular Resource Container Types
Up to this point, we’ve generally been using the bucket or vault type in our examples, and this works well. However, Scrypto also offers granular resource container type. This means that a bucket can also be a
In the last section, we had a method
FungibleBucket or a NonFungibleBucket. Similarly with a vault, we can also use FungibleVault and NonFungibleVault. Being that Scrypto has a strong type system, we can use these granular types to our advantage to help us ensure that we are handling the right types and that if we expect some kind of value from a method call, that these are the types that we expect to receive without having to do extra validation.In the last section, we had a method
list_nft where we show that we can use the ResourceManager to inspect the resource type of buckets passed in and make sure it’s the type we expect. In this case, for an NFT Marketplace, we’d like resources that are passed in to be of a NonFungible type.pub fn list_nft(&mut self, nft_bucket: Bucket, price: Decimal) { // Asserting that the Bucket passed in is of NonFungible type assert_eq!( nft_bucket.resource_manager().resource_type(), ResourceType::NonFungible {..}, "The resource you passed in must be a NonFungible!" ); info!( "You've listed your {:?} resource for {:?}! Let's see if it sells!", nft_bucket.resource_address(), price ); .. }This is a suitable way to ensure our components receive the expected resource type. Although, if we use granular resource containers, the Radix Engine can handle this for us without having to write custom checks:
pub fn list_nft(&mut self, nft_bucket: NonFungibleBucket, price: Decimal) { info!( "You've listed your {:?} resource for {:?}! Let's see if it sells!", nft_bucket.resource_address(), price ); .. }Since we changed the expected resource to pass in from a bucket to
While there’s nothing wrong with using regular resource containers types, granular resource container types offer better structure to our code. Methods that are only available for non-fungible resources are contained within its
NonFungibleBucket, the Radix Engine will already be checking the resource pass in for us to ensure the expected resource type.While there’s nothing wrong with using regular resource containers types, granular resource container types offer better structure to our code. Methods that are only available for non-fungible resources are contained within its
NonFungible type and likewise for fungible resources.