Skip to content

Designing a Rate-Limited Token Minting System

Published:
Nicolas Assouad

Introduction

At Spiko, minting is the core mechanism behind primary issuance: when an investor subscribes to one of our tokenized money market funds, new fund tokens (USTBL, EUTBL, etc.) are created on-chain and sent to their wallet. This happens daily, across multiple EVM networks.

A compromised key or a software bug could mint unbounded tokens in a single transaction.

Our solution is a dedicated Minter contract that sits between our relayer and the Token contract. It enforces per-token daily limits and automatically blocks any mint that would exceed them, requiring human approval before tokens are created. This article covers how the contract works, the design patterns behind it, and how our application orchestrates the full minting flow.

For context on our broader smart contract architecture, see our previous article on smart contracts. For how our relayer and indexer, see Spiko’s Blockchain Infrastructure.

Contract Architecture

The Minter contract is the only path to minting new tokens. Our relayer cannot call token.mint() directly — it must go through the Minter, which enforces rate limits and access control.

Minter flow

Access control is enforced by the PermissionManager. Each function on the Minter requires a specific role:

FunctionRequired Role
initiateMintmint-initiator
approveMintmint-approver
cancelMintmint-approver
setDailyLimitadmin
setMaxDelayadmin

The key design choice is that the person who initiates a mint is not the person who can approve a blocked one. This separation of duties is enforced on-chain. Even if a mint-initiator key is compromised, the attacker can only mint up to the daily limit — anything above requires a mint-approver to act.

The Minting Flow

A mint operation is identified by its hash. The Minter contract computes this hash using the user’s address, the token, the amount, and a random salt. This allows the relayer to track the status of each mint operation by computing the same hash off-chain and matching it against indexed events.

The Minter contract implements a two-step flow to process a mint operation.

Step 1: Initiate

The relayer calls initiateMint with the investor’s address, the token, the amount, and a random salt. The contract checks if the current daily usage plus this amount would exceed the configured daily limit.

Two outcomes:

Step 2: Approve or Cancel

When a mint is blocked, a mint-approver reviews it and either approves or cancels. If nobody acts before the deadline, the operation transitions to EXPIRED. An expired mint can only be canceled — there is no path from expired to executed.

The status is not stored as an explicit enum. It’s derived from a single uint256 deadline value. If the deadline is zero, it’s NULL. If it’s in the future, it’s PENDING. If it’s in the past, it’s EXPIRED. If it’s type(uint256).max, it’s DONE.

Security Considerations

The Minter contract provides several layers of protection:

Final Thoughts

The Minter contract is a core building block of our on-chain infrastructure. It provides a robust, elegant and auditable mechanism to enforce rate limits on token minting, protecting against both software bugs and key compromises.

This pattern fits naturally into the broader infrastructure described in our previous articles. As we add new tokens and expand to new networks, the same contract architecture scales with minimal changes.


Next Post
Stellar Integration