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
ConfidentialOutputStatementandStealthMintStatementcannot be generated within the template. Generating theConfidentialOutputStatementis 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 theconfidential.rsandstealth.rsfiles 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
Without `then()`, the code becomes more obtuse: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);