Skip to content

Resources

In this guide, you will:

  • Understand the four types of resources in Tari Ootle: Public Fungible, Public Non-Fungible, Confidential, and Stealth.
  • Learn how to create fungible and non-fungible resources using ResourceBuilder.
  • Explore the specialized features and privacy benefits of stealth resources.
  • Master resource operations such as minting, burning, depositing, and withdrawing.
  • Understand the role of Vaults as secure on-chain containers for assets.
  • Learn how to use Buckets as temporary containers during transaction execution.
  • Implement a practical example of minting and burning tokens within a template.

In the Tari Ootle ecosystem, Resources represent digital assets, while Components are the on-chain entities that manage these assets and implement business logic. Understanding how to create and interact with them is fundamental to building powerful Templates.

Resources are digital assets on the Tari blockchain. There are four resource types:

  • Public Fungible: Interchangeable assets where each unit is identical (e.g., tokens). These resources are not private and can be viewed by anyone on the blockchain in the same way as Bitcoin, Ethereum etc.
  • Public Non-Fungible: Unique assets where each unit has distinct properties (e.g., NFTs). These resources are not private and can be viewed by anyone on the blockchain.
  • Confidential: Tokens can be transferred in confidential outputs. These outputs are stored within vaults, and therefore only hide the amounts i.e. everyone can see this vault holds some amount of a resource, but the actual amount is hidden.
  • Stealth: Tokens can be transferred in confidential UTXOs. The native tTARI token is an example of a stealth resource. Stealth resources provide enhanced privacy for asset holdings. Learn more about stealth resources in the Stealth Resources guide.

Fungible resources are typically used for tokens, currencies, or any asset where individual units are identical and interchangeable and privacy is not useful.

use tari_template_lib::prelude::*;
// ..snip..
impl MyComponent {
pub fn create_fungible_token() -> ResourceAddress {
// Create a new fungible resource.
let token_address = ResourceBuilder::public_fungible()
// Set the token symbol, typically 3-4 letters.
.with_token_symbol("MFT")
.metadata("url", "https://token.example")
// Set the number of decimal places (optional, default is 8)
.with_divisibility(10)
// No initial supply — tokens can be minted later by the owner.
.build();
token_address
}
}

Non-fungible resources are unique assets, each with its own distinct identity and optional metadata.

use tari_template_lib::prelude::*;
// ..snip..
impl MyComponent {
pub fn create_nft_resource() -> ResourceAddress {
// Create a new non-fungible resource
let nft_address = ResourceBuilder::non_fungible()
.metadata("name", "My Awesome NFTs")
.build();
nft_address
}
}

Stealth resources represent the highest level of confidentiality within the Tari Ootle ecosystem. Unlike other resource types, funds associated with stealth resources are not kept in traditional vaults. Instead, each output is a confidential UTXO substate that lives directly on the ledger. This mechanism provides enhanced privacy for transactions and asset holdings.

use tari_template_lib::prelude::*;
// ..snip..
impl MyComponent {
pub fn create_stealth_resource() -> ResourceAddress {
// Create a new non-fungible resource
let nft_address = ResourceBuilder::stealth()
.metadata("name", "Stealth Tokens")
.with_token_symbol("STLTH")
.with_divisibility(6)
.build();
nft_address
}
}

Resources have various actions that can be performed on them. Each of these can be restricted by access rules as required.

Each action below is gated by two rules: an AccessRule that says who can perform the action today, and an UpdateRule that says who (if anyone) may change that access rule later. The updater is fixed at creation time. See the Authorization guide for the full UpdateRule semantics (Locked, Owner, AccessRule).

  • Minting: Creating new units of a resource. Default: DenyAll, updater Locked.
  • Burning: Destroying units of a resource. Default: DenyAll, updater Locked.
  • Depositing/Withdrawing: Moving resources into or out of vaults. Default: AllowAll, updater Locked.
  • Recall: Lets a privileged caller withdraw resources from any vault on the ledger. Useful for regulatory compliance, recovering lost tokens, or implementing certain business logic. Explorers should make it clear when this is anything other than DenyAll. Default: DenyAll, updater Locked — once locked, even the owner cannot enable recall later.
  • Freezable: Lets a privileged caller freeze resources in any vault on the ledger. Default: DenyAll, updater Locked.
  • Update Metadata: Controls who can change the resource’s metadata (the token symbol stays immutable). Default: DenyAll, updater Owner.
  • Update Non-Fungible Data: Applies to non-fungible resources. Controls who can update the mutable data of non-fungible tokens. Default: AllowAll, updater Owner.

These operations are typically exposed through methods on the ResourceManager. To change an individual access rule after creation, call ResourceManager::update_access_rule(action, new_rule); the engine authorizes the caller against the field’s UpdateRule.

Vaults are secure containers used to hold resources. Every component can have many or no vaults. A vault can only ever hold one resource.

use tari_template_lib::prelude::*;
impl MyComponent {
// Inside a component method:
pub fn deposit_funds(&mut self, bucket: Bucket) {
// Deposit resources from a Bucket into the vault.
// If the Bucket contains a different resource than the vault,
// the transaction will panic and fail.
self.fungible_vault.deposit(bucket);
}
pub fn withdraw_funds(&mut self, amount: Amount) -> Bucket {
// Withdraw resources from a vault into a Bucket
self.fungible_vault.withdraw(amount)
}
pub fn withdraw_nft(&mut self) -> Bucket {
// Withdraw resources from a vault into a Bucket
self.nft_vault.withdraw_non_fungible(NonFungibleId::from_string("nft1"))
}
pub fn deposit_nft(&mut self, bucket: Bucket) {
// No different from depositing fungible tokens — the vault
// checks the bucket contains the correct resource.
self.nft_vault.deposit(bucket);
}
}

Buckets are temporary containers for “some amount” of a single resource. They only exist at runtime / during the execution of a transaction. All buckets MUST be either deposited into a vault, consumed by a stealth transfer or burnt. If a bucket is not consumed by the end of a transaction, the transaction will fail and no state changes will be committed.

use tari_template_lib::prelude::*;
#[template]
mod template {
use super::*;
pub struct MyBurnToken {
// Store the tokens in a vault controlled by this component manages
tokens: Vault,
}
impl MyBurnToken {
pub fn new(initial_supply: Option<Amount>) -> Component<Self> {
const INITIAL_SUPPLY: Amount =
amount![10_000_000_000_000_000_000_000_000_000];
// Mint a new fungible stealth token with an initial supply
let bucket = ResourceBuilder::stealth()
// Set the token symbol (optional)
.with_token_symbol("BRN")
// Add custom metadata (optional)
.metadata("description", "A token that you (yes, you) can burn")
.with_divisibility(6) // 6 decimal places
// Burnable by anyone (for demonstration purposes)
.burnable(rule![allow_all])
.initial_supply(initial_supply.unwrap_or(INITIAL_SUPPLY));
Component::new(Self {
tokens: Vault::from_bucket(bucket),
})
.with_access_rules(ComponentAccessRules::new()
// Allow anyone to call the burn_one method
.method("burn_one", rule![allow_all])
// Deny all other methods by default
.default(rule![deny_all])
)
.create()
}
pub fn burn_one(&self) {
// Withdraw 0.000001 token from the vault into a bucket
// If the vault is empty, this will panic and the
// transaction will fail
let bucket = self.tokens.withdraw(1u64);
// Burn the token in the bucket
bucket.burn();
let caller_pub_key = CallerContext::transaction_signer_public_key();
emit_event("burn_one", metadata![
"caller" => caller_pub_key.to_string(),
]);
}
pub fn mint_more(&self, amount: Amount) {
// Mint new tokens into a bucket
let bucket = self.tokens.get_resource_manager().mint(amount);
// Deposit the newly minted tokens into the vault
self.tokens.deposit(bucket);
let caller_pub_key = CallerContext::transaction_signer_public_key();
emit_event("mint_more", metadata![
"caller" => caller_pub_key.to_string(),
"amount" => amount.to_string(),
]);
}
}
}