Skip to content

Templates Overview

In this guide, you will:

  • Understand the basic structure of a Tari Ootle template.
  • Learn some high-level details of what the #[template] macro does and why it’s needed.
  • Learn about component instantiation.
  • Learn some basics: constructors, methods and functions.
  • Learn about error handling in templates.

Templates are compiled to WASM and deployed to the Tari L2 network. They define the logic and structure for components, which are the on-chain instances that hold state.

A Tari Ootle Template typically follows this structure:

use tari_template_lib::prelude::*;
#[template]
mod template {
use super::*;
// Component struct definition
pub struct MyComponent {
// Define your component's state variables here
// e.g., my_value: u64,
// e.g., my_resource_address: ResourceAddress,
}
impl MyComponent {
// Constructor: initializes and creates a new component instance
// A constructor is a normal function that returns Self.
// When invoked, it creates a new component instance on-chain.
// See "Component constructors" below for more details.
pub fn new(initial_state: u64) -> Self {
Self {
// Initialize state
// my_value: initial_state,
// Create a new resource with no initial supply
// my_resource_address: ResourceBuilder::public_fungible()
// .with_token_symbol("TOK")
// .build()
}
}
// Public method belonging to the component instance
// Use the CallMethod instruction in a transaction to invoke methods
pub fn do_something(&mut self, value: u64) {
// Logic to modify component state or interact with resources
// self.my_value += value;
}
// Private helper functions: internal logic not exposed publicly
fn calculate_internal(&self) -> u64 {
// ...
0
}
// Example of a public function that returns a value.
// Note the lack of the `&self` parameter.
pub fn this_is_a_function(name: String) -> String {
format!("Hello, {}!", name)
}
}
}

The #[template] attribute macro performs several critical functions:

  • ABI Generation: It generates the necessary Application Binary Interface (ABI) that allows the Tari Ootle validator engine to understand and interact with your compiled WASM module.
  • Method Exposure: It exposes public functions within your impl block as callable methods of the component.
  • State Management: It ensures that your component’s state (defined within its struct) is properly encoded and communicated to the Ootle validator engine.

The state of your component is defined by the fields within its struct. These fields are persisted on the blockchain. NOTE: that a component can also be an enum.

Important Considerations:

  • Serializable Types: All fields in your component struct must be serde serializable and deserializable. The tari_template_lib provides various types (Amount, ResourceAddress, etc.) that are already compatible. For custom types, you may need to implement serde::Serialize and serde::Deserialize.
  • Mutability: To modify component state, methods must take &mut self.
  • Access: Component state should generally be managed internally via methods. Access to component state from another template is possible (if the requisite transaction signatures are provided), but is generally discouraged.

Error handling in Tari Ootle Templates is done by panicking. When a panic occurs, the transaction will fail and no state changes will be committed.

pub fn do_something(value: u64) {
assert_ne!(value, 0, "Value cannot be zero");
if !value.is_power_of_two() || value > 1024 {
panic!("Value must be a power of two and less than 1024");
}
self.my_value += value;
}

Constructors are normal template functions that create the component on-chain.

impl MyComponent {
// Any function that returns Self will implicitly create a
// component instance on-chain when invoked.
pub fn new(initial_value: u64) -> Self {
Self {
my_value: initial_value,
}
}
// This function explicitly creates a component instance on-chain.
// Equivalent to the implicit constructor above.
pub fn explicit_constructor() -> Component<Self> {
Component::new(Self {
my_value: 42,
}).create()
}
pub fn custom(addr: ComponentAddressAllocation) -> Component<Self> {
Component::new(Self {
my_value: 42,
})
.with_address_allocation(addr)
.with_access_rules(ComponentAccessRules::new()
// Allow anyone to call the do_something method
.method("do_something", rule![allow_all])
.default(rule![deny_all])
)
.with_owner_rule(OwnerRule::OwnedBySigner) // This is the default
.create()
}
}
  • Methods: These are the entry points for external interaction with your component. They can be called by using the CallMethod instruction in transactions, or can be invoked by other components.
  • Functions: These are standalone functions that can be called on the template itself (i.e. without an instance of the component). They can be usually useful for constructing components, but can be utility functions or other shared logic that you want to expose. They can be called using the CallFunction instruction in transactions, or can be invoked by other components or templates.
  • Private methods/functions (fn): These are regular private functions used within your component’s implementation. They cannot be called from outside the component.

For more advanced topics like authorization, events, and testing, refer to the other guides.

  • Now that you know a little about templates, let’s build a simple Guessing Game.