Minter Mods
Minter Mods are a special type of mod that can distribute Totems to users. Unlike regular hooks that observe events, Minter Mods have a callable mint function that controls how Totems are distributed.
How Minting Works
When a user wants to mint Totems:
- A user calls
totems.mint()with optional payment - The Totems contract calls your mod’s
mint()function (must be payable!) - Your mod transfers Totems from its balance to the user (or mints new supply if unlimited)
- The
onMinthook is called on all licensed mods
%%{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 Totems
participant MinterMod
participant OtherMods
User->>Totems: mint(mod, minter, ticker, amount, memo) + payment
Totems->>MinterMod: mint(...details)
MinterMod->>Totems: transfer(...details)
Totems->>OtherMods: onMint(...details)
Totems-->>User: Success The IModMinter Interface
interface IModMinter {
function mint(
string calldata ticker,
address minter,
uint256 amount,
string calldata memo
) external payable;
} | Parameter | Description |
|---|---|
ticker | The Totem’s ticker symbol |
minter | Address requesting the mint |
amount | Number of Totems requested (can be 0!) |
memo | Optional message (used for proxy mod routing) |
msg.value | Native currency payment attached |
Types of Minter Mods
Allocated Minters
When a Totem is created, the creator allocates a specific amount to each minter mod. Your mod receives these Totems and distributes them according to your logic.
contract AllocatedMinter is TotemMod, IModMinter, IModCreated, IModTransfer {
mapping(bytes32 => uint256) public balances;
constructor(address _totemsContract, address payable _seller)
TotemMod(_totemsContract, _seller) {}
function isSetupFor(string calldata ticker) external view override returns (bool) {
return true;
}
// Track initial allocation (will not be a transfer notification!)
function onCreated(
string calldata ticker,
address creator
) external onlyTotems onlyLicensed(ticker) {
bytes32 tickerBytes = TotemsLibrary.tickerToBytes(ticker);
balances[tickerBytes] = TotemsLibrary.getBalance(totemsContract, ticker, address(this));
}
// Track transfers in
function onTransfer(
string calldata ticker,
address from,
address to,
uint256 amount,
string calldata memo
) external onlyTotems onlyLicensed(ticker) {
bytes32 tickerBytes = TotemsLibrary.tickerToBytes(ticker);
if (to == address(this)) {
balances[tickerBytes] += amount;
}
}
function mint(
string calldata ticker,
address minter,
uint256 amount,
string calldata memo
) external payable onlyTotems onlyLicensed(ticker) {
bytes32 tickerBytes = TotemsLibrary.tickerToBytes(ticker);
require(balances[tickerBytes] >= amount, "Allocation exhausted");
TotemsLibrary.transfer(totemsContract, ticker, minter, amount, "");
balances[tickerBytes] -= amount;
}
} Unlimited Minters
Unlimited minters can mint whatever they want.
They’re marked with needsUnlimited: true when published, and when set as an allocation should always have 0 amount
allocated to them.
Warning
Unlimited minters will never be able to receive Totems via transfers.
contract UnlimitedMinter is TotemMod, IModMinter {
constructor(address _totemsContract, address payable _seller)
TotemMod(_totemsContract, _seller) {}
function isSetupFor(string calldata ticker) external view override returns (bool) {
return true;
}
function mint(
string calldata ticker,
address minter,
uint256 amount,
string calldata memo
) external payable onlyTotems onlyLicensed(ticker) {
// Unlimited minters transfer from themselves
// The Totems contract mints new supply as needed
TotemsLibrary.transfer(totemsContract, ticker, minter, amount, "");
}
} Caution
Unlimited minters display prominent warnings in the UI. Users are alerted that supply can increase indefinitely, or become malicious. They also cannot be added via the Proxy Mod. This prevents creators from inflating supply unexpectedly.
How Minted Amount is Determined
The Totems contract automatically calculates how many totems were minted based on the minter type:
- Allocated minters: The minted amount is the minter’s balance decrease (how many totems left the mod)
- Unlimited minters: The minted amount is the supply increase (new totems created)
When an event is emitted at the end of the mint process, the actual minted amount is reported.
Handling Payments
The msg.value contains any native currency sent with the mint request:
function mint(
string calldata ticker,
address minter,
uint256 amount,
string calldata memo
) external payable onlyTotems onlyLicensed(ticker) {
// Example: Require 0.01 ETH per totem
uint256 price = amount * 0.01 ether;
require(msg.value >= price, "Insufficient payment");
TotemsLibrary.transfer(totemsContract, ticker, minter, amount, "");
} Note
Both amount and msg.value can be zero. This enables use cases like free giveaways, mining mechanics, or signature-gated mints.
Minting Less Than Requested
Since the minted amount is calculated from balance/supply changes, you can mint fewer totems than requested:
function mint(
string calldata ticker,
address minter,
uint256 amount,
string calldata memo
) external payable onlyTotems onlyLicensed(ticker) {
uint256 actualAmount = amount / 2;
TotemsLibrary.transfer(totemsContract, ticker, minter, actualAmount, "");
} The Totems contract will detect that fewer totems were transferred and report the actual minted amount.
Custom Minting Interfaces
You can provide a custom minting experience by setting website and websiteTickerPath in your mod’s metadata when publishing. When users click “Mint” on a totem using your mod, they can be redirected to your custom interface instead of the generic Totems UI.
Calling Mint from Your Interface
Always call the Totems contract, not your mod directly:
// Correct - goes through Totems contract
await totemsContract.mint(modAddress, minterAddress, ticker, amount, memo, { value: payment });
// Wrong - will fail the onlyTotems check
await yourModContract.mint(ticker, minterAddress, amount, memo);