Templates Overview

Ootle / Guides / Templates Overview

Templates Overview

Covered in this guide

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.
  • Learn about component-less, stateless templates.

Tip

This guide provides a foundational understanding of Tari Ootle Templates. If you want, you can skip to the next guide: Building a Guessing Game.

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

Template Structure

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] Macro

Tip

TL;DR add this macro to your template module to make it work. Some details below that are useful to know, but you can get started without it.

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.

Component State

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.

Note

The first pub struct (or pub enum) declared in the module is the one treated as the component, and it also gives the template its name. Any additional structs or enums you declare are ordinary data types — useful as method arguments or return values — not components. (The exception is a stateless template, where no struct is interpreted as a component at all.)

Important Considerations:

  • Encodable Types: All fields in your component struct must be CBOR-encodable via minicbor. The #[template] macro automatically derives the encoding (minicbor::Encode, minicbor::Decode, minicbor::CborLen) — and assigns the per-field #[n(N)] tags — for every struct and enum declared in the template module, so you usually don’t need to do anything. The tari_template_lib types (Amount, ResourceAddress, etc.) are already compatible. For custom types declared outside the module, derive those traits yourself. No serde is required.
  • 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.

Caution

Be mindful of the size and complexity of your component’s state. Large or complex state can lead to higher transaction costs and may impact performance.

Note

A template defines the logic and structure for components, but it is an instance of this that hold state on-chain. This instance is called a component. You can have multiple components instantiated from the same template, each with its own state.

Errors

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;
}

Component constructors

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 vs Functions

  • 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.

Stateless templates

Not every template needs a component. Some templates are just a library of pure functions — spend-time scripts, resource auth-hook helpers, validation or math utilities — with no state to persist and nothing to instantiate. For these, add the stateless flag to the macro:

use tari_template_lib::prelude::*;


#[template(stateless)]
mod math {
    // The public API is the set of free `pub fn` items in the module.
    pub fn add(a: u32, b: u32) -> u32 {
        a + b
    }


    pub fn sum(values: Vec<u32>) -> u32 {
        values.into_iter().sum()
    }


    // Private helpers are still allowed; they are not exposed.
    fn helper(a: u32) -> u32 {
        a + 1
    }
}

A stateless template differs from a regular one in a few ways:

  • No component struct. The public API is the set of free pub fn items in the module — no struct is interpreted as a component, and there is no on-chain state.
  • The template name is the module identifier. In the example above the template is named math (not a struct name). You invoke its functions with the CallFunction instruction, exactly like template functions on a regular template.
  • No component is ever created. Calling a stateless function only returns its result; it never creates a component substate.
  • Restrictions are enforced at compile time. Methods (&self / &mut self), -> Self constructors and #[migration] functions are rejected with a clear error, since none of those make sense without a component.

You can still declare struct/enum types inside a stateless module to use as function arguments or return values — they are treated purely as data types and receive the usual CBOR derives. The stateless flag also composes with skip_cbor_derives (e.g. #[template(stateless, skip_cbor_derives)]) if you want to manage the wire format of those types yourself.

Note

Stateless mode is an ergonomics and intent-enforcement feature: it lets you write pure-function templates without an unused struct, and the compiler guarantees no state or component sneaks in.

Next Steps

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