Skip to content

ResourceBuilder Module

The ResourceBuilder module allow developers to create resources on the Ootle. There are four builder methods available

Method What it does When to use
fungible() Creates a publicly visible fungible resource. Balances are stored in vaults and visible to anyone with ledger access. Use when transparency is acceptable or required (e.g., utility tokens, in-game currencies).
non_fungible() Creates a publicly visible non-fungible resource. Each token has a unique ID and is visible in vaults. Use for unique items like collectibles, digital art, identity or governance badges.
confidential() Creates a partially confidential fungible resource. Balances are split into revealed and blinded outputs; requires special viewing mechanisms for full balance visibility. Use when partial privacy is needed. Revealed funds can be used for fees or refunds, while blinded outputs preserve privacy.
stealth() Creates a fully confidential fungible resource. Does not use vaults; each unit exists as a confidential substate on the ledger. Use for maximum privacy. Ideal when you need transaction-level confidentiality without vault visibility.

Construction

Initializing a new resource can be done in two ways:

Method What it does When to use
.build() Creates resource definition only When you want to mint tokens/NFTs later or on-demand
.initial_supply() Creates resource and mints supply When you want to mint and distribute tokens/NFTs immediately

You may also chain various configuration methods in the builder, such as the token details or minting rules.

Note: user either .build() or .initial_supply(), not both.

Example 1

Create a new fungible token, immediately mints an initial set of tokens and returns a Bucket containing the tokens.

// Create a fungible token called "DemoToken" with symbol "DMT"
let initial_supply_bucket = ResourceBuilder::fungible()
    .with_metadata("name", "DemoToken")
    .with_metadata("description", "A demo fungible token with admin-only minting and burning.")
    .with_token_symbol("DMT")
    // Only addresses presenting the admin badge can mint new tokens
    .mintable(rule!(require(admin_badge_address)))
    // Only addresses presenting the admin badge can burn tokens
    .burnable(rule!(require(admin_badge_address)))
    // Anyone can deposit tokens to vaults or accounts (using rule!(allow_all) directly)
    .depositable(rule!(allow_all))
    // Anyone can withdraw tokens from vaults or accounts (using rule!(allow_all) directly)
    .withdrawable(rule!(allow_all))
    // Mint an initial supply of 10,000 tokens and return a Bucket containing them
    .initial_supply(dec!(10_000));

.build()

Builds a Resource definition and returns a ResourceAddress which can then be used as a reference for the ResourceManager to manage the resource.

Example

// Set up the resource with metadata, divisibility, and access rules as needed
let resource_address = ResourceBuilder::fungible()
    .with_token_symbol("MY_TOKEN")
    .with_divisibility(8)
    .mintable(rule!(allow_all)) // Allows anyone to mint new tokens
    .build(); // This creates the resource and returns its address

initial_supply()

Builds a resource internally and then mints a number of the resources as an initial supply. Depending on the builder method, the argument required for .initial_supply will differ. The method will typically return a Bucket, which has its own chainable methods for retrieving information prior to depositing it, such as bucket.resource_address.

Builder Method initial_supply Signature Input Type Description
fungible() initial_supply<A: Into<Amount>>(initial_supply: A) Amount or compatible type (e.g. u64, Decimal) Mints a fungible resource with the specified amount. Publicly visible.
non_fungible() initial_supply<I: IntoIterator<Item = NonFungibleId>>(initial_supply: I) Iterator of NonFungibleId Mints NFTs with null immutable and mutable data.
non_fungible() initial_supply_with_data<I, T, U>(initial_supply: I) Iterator of (NonFungibleId, (&T, &U)) where T, U: Serialize Mints NFTs with custom immutable and mutable data.
confidential() initial_supply(initial_supply_proof: ConfidentialOutputStatement) ConfidentialOutputStatement Mints a confidential fungible resource using a blinded output proof. Partially private.
stealth() initial_supply(initial_supply: StealthMintStatement) StealthMintStatement Mints a stealth resource, fully confidential. Returns a ResourceAddress, not a Bucket.

Note: the ConfidentialOutputStatement and StealthMintStatement cannot be generated within the template. Generating the ConfidentialOutputStatement is non-trivial, and requires use of several cryptographic libraries and techniques. While we do not cover its construction in this documentation, examples are available in the test suite for the Tari templates, located in the official repo under https://github.com/tari-project/tari-ootle/blob/development/crates/engine/tests/. You can review the tests in the confidential.rs and stealth.rs files respectively.

Example

A typical example of using the non-fungible `ResourceBuilder`:
// Sample metadata and IDs
let mut metadata = Metadata::new();
metadata.insert("name", "My NFT Collection");
metadata.insert("description", "A unique collection of NFTs.");

let my_initial_nft = NonFungibleId::from_u64(1);

// Example: conditionally add an image URL
let add_image = true;

let bucket = ResourceBuilder::non_fungible()
    .with_token_symbol("MYNFT")
    .with_metadata(metadata)
    .then(|builder| {
        if add_image {
            builder.with_image_url("https://example.com/nft.png".into())
        } else {
            builder
        }
    })
    .mintable(rule!(allow_all))
    .initial_supply([(my_initial_nft, Metadata::from("First NFT"))]);

// To get the ResourceAddress of the new non-fungible resource:
let resource_address = bucket.resource_address();

Configuration

then()

The then() method allows for conditional configuration within a ResourceBuilder pattern. Using .then ensures declarative contract logic, helps minimise control flow issues and is aimed at improving template readability. then

Example code using then() to mint stealth resources with an optional `view_key` if provided
let bucket = ResourceBuilder::stealth()
    .mintable(rule!(allow_all))
    .then(|builder| {
        if let Some(key) = view_key {
            builder.with_view_key(key)
        } else {
            builder
        }
    })
    .initial_supply(initial_supply);
Without `then()`, the code becomes more obtuse:
let mut builder = ResourceBuilder::stealth();
builder = builder.mintable(rule!(allow_all));

if let Some(key) = view_key {
    builder = builder.with_view_key(key);
}

let bucket = builder.initial_supply(initial_supply);

with_owner_rule

Used to set the owner of a Resource. Ownership determines who, by default, has access to specific privileges such as minting or burning (although these can be customised via AccessRules). By default, when a Resource is created, if no owner rule is set, the resource uses [OwnerRule::OwnedBySigner].

When a transaction attempts to interact with the resource, the Ootle will confirm that the transaction adheres to the OwnerRule.

Owner Rule Description Permissions & Use Cases
OwnedBySigner Owned by the signer of the transaction that creates the resource. The default setting if no with_owner_rule is specified. All privileged actions (mint, burn, update, etc.) restricted to the creator's signature.
None No owner is set; resource management is entirely governed by explicit access rules (if any). No privileged actions possible unless explicit access rules are set. If none, resource is immutable after creation.
ByAccessRule(AccessRule) Ownership is determined by satisfying a specific access rule (role, multi-sig, condition, etc.). Privileged actions available to anyone meeting the access rule. Flexible for multi-sig, DAOs, role-based or conditional ownership.
ByPublicKey(RistrettoPublicKeyBytes) Ownership is assigned to a specific public key, regardless of who creates the resource. Privileged actions restricted to the holder of the specified public key. Useful for delegation, escrow, or external agents.

Example

In the below example, a non-fungible resource is being created with an OwnerRule that requires the user to have a specific NFT badge in order to access specifica priviliges on the newly created NFT.

// Define an access rule: only addresses with a specific badge can own this NFT
let owner_access_rule = rule!(require(badge_resource_address));
ResourceBuilder::non_fungible()
.with_owner_rule(OwnerRule::ByAccessRule(owner_access_rule))
.with_metadata("name", "Access Rule NFT")
.initial_supply([("nft_id_1", Metadata::default())])

with_access_rules

The .with_access_rules method is used during resource creation in Tari to define the full set of access control policies for a resource in a single step. Instead of setting rules for each privileged action individually, this method accepts a ResourceAccessRules object—which maps every possible action (such as minting, burning, updating metadata, or changing access rules) to its corresponding AccessRule. By using .with_access_rules, you ensure that every aspect of resource security is explicitly defined right from the start, providing both clarity and flexibility in how your resource can be interacted with.

If the developer does not invoke the with_access_rules method, a new instance of the ResourceBuilder will use the following default values:

Action Field Name Default Description
Mint mintable DenyAll Create new tokens
Burn burnable DenyAll Destroy tokens
Recall recallable DenyAll Force-withdraw tokens from others
Withdraw withdrawable AllowAll Take tokens from vaults
Deposit depositable AllowAll Put tokens into vaults
Update NFT data update_non_fungible_data AllowAll Modify mutable NFT metadata
Update access rules update_access_rules DenyAll Change access control later
Freeze freeze DenyAll Freeze vaults from withdrawing

The following AccessRules can be applied:

Access Rule Variant Description Who Gets Access Use Case Example
AccessRule::AllowAll No restriction Everyone (any caller) Open tokens: anyone can withdraw/deposit without authentication
AccessRule::DenyAll Fully restricted No one, not even the creator Lock down critical actions like minting or rule updates
AccessRule::Restricted(...) Requires one or more conditions Only callers who satisfy the condition(s) Used to enforce roles, ownership, or context-aware control

AccessRule::Restricted(...) has the following variants:

RestrictedAccessRule Meaning / Condition Required Use Case Example
Require(ResourceAddress) Caller must own a specific fungible badge or token Badge-based access control (e.g. minting allowed for badge holders)
Require(NonFungibleAddress) Caller must own a specific non-fungible token (NFT) Admin-only access via NFT (e.g. token ID #1)
Require(ScopedToComponent(ComponentAddress)) Action must be invoked from a specific component Only allow a smart contract (component) to mint or burn
Require(ScopedToTemplate(TemplateAddress)) Action must be invoked from components of a given template Any DAO contract from a certain template can perform the action
AllOf([...]) All listed rules must be satisfied (logical AND) "Must have admin badge and be inside DAO component"
AnyOf([...]) Any one of the listed rules must be satisfied (logical OR) "Must have badge A or badge B"
Resource example built to allow minting by the owner of a NFT badge
let admin_badge_address = ResourceAddress::from("resource_admin_badge_address");
let minting_rule = AccessRule::Restricted(
    RestrictedAccessRule::Require(RuleRequirement::Resource(admin_badge_address))
);

// Construct a custom set of access rules
let access_rules = ResourceAccessRules::new()
    .mintable(minting_rule)
    .burnable(AccessRule::DenyAll) // Prevent anyone from burning
    .withdrawable(AccessRule::AllowAll) // Allow all users to withdraw
    .depositable(AccessRule::AllowAll); // Allow all users to deposit

// Create a fungible resource with the specified access rules
let bucket = ResourceBuilder::fungible()
    .with_access_rules(access_rules)
    .metadata("name", "MyToken")
    .metadata("symbol", "MYT")
    .initial_supply(1_000_000);
### `with_address_allocation` The `with_address_allocation` method can be used to assign a specific resource address to the - with_address_allocation - mintable - burnable - with_token_symbol - add_metadata - with_metadata - with_image_url - with_authorization_hook *[TVM]: Tari Virtual Machine