Required Actions

Sometimes you need the Totem creator to perform additional setup steps after creating their Totem.

Required Actions let you inform the creator of these steps and enforce that they are completed during Totem creation through our UIs. This ensures that your mod functions correctly and that the creator has a smooth experience.

Setup Verification

Your mod must always implement the isSetupFor function, even if you have no required actions. This allows external systems(UIs, other contracts, indexers) to verify whether a Totem has completed setup for your mod.

function isSetupFor(string calldata ticker) external view override returns (bool) {
    // Return true if setup is complete for this ticker
    return totemsPerMine[ticker] > 0;
}

The TotemMod base contract defines this as a virtual function that you must override:

function isSetupFor(string calldata ticker) virtual external view returns (bool);

The onlySetup Modifier

For functions that should only work after setup is complete, you can use the onlySetup modifier provided by TotemMod:

function mine(string calldata ticker) external onlySetup(ticker) {
    // This will revert with "Mod is not setup for this totem" if isSetupFor returns false
    // ... mining logic
}

Struct Definitions

struct ModRequiredAction {
    string signature;              // Function signature, e.g., "setup(string ticker, uint256 amount)"
    ModActionField[] inputFields;  // Field definitions for each parameter
    uint256 cost;                  // msg.value required (0 if not payable)
    string reason;                 // Explain why this action is needed
}

struct ModActionField {
    string name;                   // Parameter name, e.g., "ticker"
    ModActionFieldInputMode mode;  // How the value is provided
    string value;                  // Predefined value (only for STATIC mode)
    string description;            // Human-readable description for UIs
    uint256 min;                   // Minimum value for numeric inputs (0 = no minimum)
    uint256 max;                   // Maximum value for numeric inputs (0 = no maximum)
    bool isTotems;                 // Whether this field expects totem amounts (auto-scaled by decimals)
}

enum ModActionFieldInputMode {
    DYNAMIC,  // User provides this value
    STATIC,   // Predefined value you specify
    TOTEM     // Auto-filled with the totem's ticker
}

Field Modes

Field modes determine how each parameter value is provided during Totem creation:

  • DYNAMIC - The Totem creator enters this value. Use for configurable parameters.
  • STATIC - You specify the value. Use for hardcoded defaults the creator can’t change.
  • TOTEM - Auto-filled with the totem ticker. Use for the ticker parameter.

Totem Amount Fields

When a uint256 field represents a totem amount (e.g., totemsToMint), you can mark it as a “Totem amount” field in the publish UI. This enables the isTotems option.

When isTotems is enabled:

  • The creator enters a human-readable amount (e.g., 100 for 100 tokens)
  • The UI automatically multiplies by the totem’s decimals before sending the transaction
  • For example, if the totem has 8 decimals, entering 100 sends 10000000000 (100 × 10^8)

This provides a better UX for creators who don’t need to think about decimal precision, and less explanation on your part about how to enter amounts for your mod’s setup function.

Note

The isTotems option is only available for uint256 fields in DYNAMIC mode. Your contract should expect the value in the totem’s smallest unit (wei-equivalent).

Validation Functions

Every required action function must have a corresponding validation function prefixed with can. This allows UIs to validate parameters before the creator commits to a transaction.

%%{init: {'theme': 'dark', 'mirrorActors': false, 'themeVariables': { 'primaryColor': '#1e293b', 'primaryTextColor': '#e5e7eb', 'primaryBorderColor': '#38bdf8', 'lineColor': '#38bdf8', 'actorTextColor': '#e5e7eb', 'actorBkg': '#1e293b', 'actorBorder': '#38bdf8', 'signalColor': '#38bdf8', 'signalTextColor': '#e5e7eb', 'activationBkgColor': '#1e293b', 'activationBorderColor': '#38bdf8', 'sequenceNumberColor': '#e5e7eb', 'noteBkgColor': '#000000', 'noteTextColor': '#9ca3af', 'noteBorderColor': '#4b5563' }}}%%
sequenceDiagram
    participant User
    participant UI
    participant Mod
    participant Totems

    User->>UI: Select mod & fill setup fields
    UI->>Mod: canSetup(...params)
    Mod-->>UI: error / success
    User->>Totems: create(...params)
    Totems-->>User: Totem created
    User->>Mod: setup(...params)
    Mod-->>User: Should never fail
// Main function
function setup(
    string calldata ticker,
    uint256 _totemsPerMine
) external onlyCreator(ticker) onlyLicensed(ticker) {
    (bool valid, string memory reason) = _validateSetup(_totemsPerMine);
    require(valid, reason);
    totemsPerMine[ticker] = _totemsPerMine;
}

// Validation function (required)
function canSetup(
    string calldata ticker,
    uint256 _totemsPerMine
) public pure returns (bool valid, string memory reason) {
    return _validateSetup(_totemsPerMine);
}

function _validateSetup(uint256 _totemsPerMine) internal pure returns (bool, string memory) {
    if (_totemsPerMine == 0) {
        return (false, "totemsPerMine must be greater than zero");
    }
    return (true, "");
}

The validation function:

  • Has the same parameters as the main function
  • Returns (bool valid, string memory reason)
  • Is view or pure (no state changes)
  • Uses the naming convention: setupcanSetup, configurecanConfigure

Important

The ticker parameter is always passed as an empty string ("") when calling validation functions. This prevents frontrunning attacks where someone could observe a pending totem creation transaction and create a totem with the same ticker first. Your validation logic should not depend on the ticker value.

This means your actual setup function shouldn’t rely on it either!

Function Signatures

The signature must be the canonical function signature without the function keyword, modifiers, visibility, or return types:

// Correct
setup(string ticker, uint256 amount)

// Wrong - includes keyword
function setup(string ticker, uint256 amount)

// Wrong - includes modifiers
setup(string ticker, uint256 amount) external

Tip

On the publish page, you can paste your contract’s ABI JSON and select the function. The signature will be generated automatically. The ABI is only used locally to generate the signature, it’s never stored or sent anywhere.

Complete Example

contract MiningMod is TotemMod, IModMinter {
    mapping(string => uint256) public totemsPerMine;

    constructor(address _totemsContract, address payable _seller)
        TotemMod(_totemsContract, _seller) {}

    // Required action - creator configures mining rate
    function setup(
        string calldata ticker,
        uint256 _totemsPerMine
    ) external onlyCreator(ticker) onlyLicensed(ticker) {
        (bool valid, string memory reason) = _validateSetup(_totemsPerMine);
        require(valid, reason);
        totemsPerMine[ticker] = _totemsPerMine;
    }

    function canSetup(
        string calldata ticker,
        uint256 _totemsPerMine
    ) public pure returns (bool valid, string memory reason) {
        return _validateSetup(_totemsPerMine);
    }

    function _validateSetup(uint256 _totemsPerMine) internal pure returns (bool, string memory) {
        if (_totemsPerMine == 0) {
            return (false, "totemsPerMine must be greater than zero");
        }
        return (true, "");
    }

    // Implement isSetupFor - required for mods with required actions
    function isSetupFor(string calldata ticker) external view override returns (bool) {
        return totemsPerMine[ticker] > 0;
    }

    // Minting only works after setup
    function mint(string calldata ticker) external onlySetup(ticker) {
        // ... minting logic using totemsPerMine[ticker]
    }
}
<-
Security Checklist
Publishing
->