EVM Quickstart
This guide will help you understand how to build Mods for Totems on EVM-compatible chains using Solidity.
Development Environment
You can use any Solidity development framework you’re comfortable with:
- Hardhat - JavaScript/TypeScript based, great plugin ecosystem
- Foundry - Rust-based, fast compilation and testing
Both work, so choose whichever fits your workflow.
Installation
npm install @totems/evm Or with Foundry:
forge install nsjames/totems-evm Package Structure
Hardhat / npm:
@totems/evm/
├── mods/ TotemMod.sol, TotemsLibrary.sol
├── interfaces/ ITotems.sol, IMarket.sol, ITotemTypes.sol, IRelayFactory.sol
├── test/ helpers.ts (test utilities)
└── contracts/ Totems.sol, ModMarket.sol, ProxyMod.sol, etc. Tip
Everything you need for building mods is MIT licensed, however the core protocol contracts (used for the test helper) are under an AGPL-3.0 license which requires derivative works to also be open source to ensure the protocol remains available to all, forever.
Foundry:
After installing, add these remappings to your foundry.toml:
remappings = [
"@totems/evm/=lib/totems-evm/package/"
] Then use the same import paths as Hardhat:
import "@totems/evm/mods/TotemMod.sol"; Architecture Overview
The Totems EVM implementation uses a monolithic multi-token pattern:
%%{init: {'theme': 'dark', 'flowchart': { 'subGraphTitleMargin': { 'top': 10 } }, 'themeVariables': { 'primaryColor': '#1e293b', 'primaryTextColor': '#e5e7eb', 'primaryBorderColor': '#38bdf8', 'lineColor': '#38bdf8', 'clusterBkg': 'transparent', 'clusterBorder': '#38bdf8' }}}%%
flowchart LR
User["User"] --> Totems["Totems Contract"]
Totems -->|hooks| Mods["Mod Contracts"]
Totems --> Market["ModMarket"]
classDef default fill:#1e293b,stroke:#38bdf8,stroke-width:2px,color:#e5e7eb;
classDef modStyle fill:#1e293b,stroke:#34d399,stroke-width:2px,color:#e5e7eb;
class Mods modStyle; All Totems share a single contract (multi-token pattern) rather than deploying separate contracts per token. This means your Mod interacts with one canonical Totems contract address per chain.
Building a Mod
Every Mod extends the TotemMod base contract and implements one or more hook interfaces.
Base Contract
import "@totems/evm/mods/TotemMod.sol";
contract MyMod is TotemMod {
constructor(address _totemsContract, address payable _seller)
// Pass in the Totems contract address and your seller address
// to the parent constructor
TotemMod(_totemsContract, _seller){}
} The TotemMod base gives you:
| Name | Type | Description |
|---|---|---|
totemsContract | Variable | The Totems contract instance |
getSeller() | Pre-implemented Function | The address that receives licensing fees |
isSetupFor(ticker) | Virtual Function | Indicates if mod is set up for a given totem |
onlyTotems | Modifier | Restricts calls to Totems contract only |
onlyLicensed | Modifier | Restricts calls to licensed totems only |
onlyCreator | Modifier | Restricts calls to the totem creator |
Warning
Without the getSeller() function anyone would be able to publish your mod on the market and receive the licensing fees!
Make sure you publish your mod with the same address/wallet you’ll be using to receive payments, and publish your
mod to the market from that wallet.
Hook Interfaces
Implement any combination of these interfaces to respond to totem events:
| Interface | When it’s called |
|---|---|
IModCreated | When a Totem is created |
IModMint | After totems are minted |
IModTransfer | After totem transfers |
IModBurn | After totems are burned |
IModMinter | Called to mint totems |
IModTransferOwnership | When totem ownership changes |
Example: Transfer Freeze Mod
Here’s a simple Mod that allows the Totem creator to freeze/unfreeze transfers:
pragma solidity ^0.8.28;
import "@totems/evm/mods/TotemMod.sol";
import "@totems/evm/mods/TotemsLibrary.sol";
contract FreezerMod is TotemMod, IModTransfer {
mapping(bytes32 => bool) public frozen;
constructor(address _totemsContract, address payable _seller)
TotemMod(_totemsContract, _seller){}
function isSetupFor(string calldata ticker) external view override returns (bool) {
// This mod is always ready to go for any totem
return true;
}
// Modifier ensures only the Totem creator can toggle freeze
function toggle(string calldata ticker) external onlyCreator(ticker) {
bytes32 tickerBytes = TotemsLibrary.tickerToBytes(ticker);
frozen[tickerBytes] = !frozen[tickerBytes];
}
// Called on every transfer - revert if frozen
function onTransfer(
string calldata ticker,
address from,
address to,
uint256 amount,
string calldata memo
) external onlyTotems onlyLicensed(ticker) {
bytes32 tickerBytes = TotemsLibrary.tickerToBytes(ticker);
require(!frozen[tickerBytes], "Transfers are frozen");
}
} Important
Always use the onlyTotems and onlyLicensed(ticker) modifiers on your hook functions. This ensures your mod is
only called by the Totems contract and is properly licensed, preventing unauthorized use and ensuring you receive
payment for your mod’s usage.
See the TotemsLibrary reference for all available helper functions.
Next Steps
These docs are structured so that each section builds on the previous one. Following them in order will give you the complete picture of how to build, test, and publish mods.