·Transfa Team

How to Create a TIP-20 Token on Tempo

Deploy a TIP-20 stablecoin on Tempo using the enshrined token factory, configure roles and supply caps, and query token metadata.

tempotip20tokenssmart-contracts

Tempo uses TIP-20 as its native token standard instead of ERC-20. TIP-20 tokens are not deployed as arbitrary smart contracts. They're created through an enshrined factory precompile, which means every token gets the same interface, the same 6-decimal precision, and access to protocol features like payment lanes and memo transfers.

This guide covers creating a TIP-20 token on Moderato, configuring its roles and supply cap, minting tokens, and querying token data through the Transfa Data API.

Prerequisites

How TIP-20 differs from ERC-20

Key differences:

ERC-20TIP-20
DecimalsDeveloper chooses (usually 18)Always 6
DeploymentAny smart contractFactory precompile only
Address prefixAny0x20c0
Memo supportNone (application-level hack)Protocol-level transferWithMemo
CompliancePer-contractShared policy system (TIP-403)
Fee paymentCannot pay gasUSD-denominated TIP-20 tokens can pay transaction fees
BlockspaceCompetes with all transactionsDedicated payment lanes

TIP-20 tokens share the standard Transfer event signature with ERC-20, so existing tools that listen for ERC-20 transfers will pick up TIP-20 transfers. But the extended features (memos, policies, enshrined roles) are TIP-20-specific.

Create a token with the factory

The TIP-20 Factory lives at 0x20Fc000000000000000000000000000000000000. You call createToken with your token's name, symbol, currency denomination, and a salt for deterministic address generation.

cast send 0x20Fc000000000000000000000000000000000000 \
  "createToken(string,string,string,address,address,bytes32)" \
  "Example USD" "XUSD" "USD" \
  0x0000000000000000000000000000000000000000 \
  $MY_ADDRESS \
  $(cast keccak "my-unique-salt-v1") \
  --rpc-url https://rpc.moderato.tempo.xyz \
  --private-key $PRIVATE_KEY

Parameters:

  • name: Human-readable token name
  • symbol: Short ticker symbol
  • currency: ISO 4217 currency code (e.g., "USD"). This is immutable after creation, so choose carefully. Only USD-denominated tokens can be used to pay transaction fees.
  • quoteToken: The TIP-20 token this pairs with on the Stablecoin DEX. Pass the zero address if you don't need DEX integration.
  • admin: Address that receives DEFAULT_ADMIN_ROLE. This is typically your deployer address.
  • salt: Used for deterministic address derivation. The resulting token address is TIP20_PREFIX || highest64BitsOf(keccak256(sender, salt)).

The first 1,000 token addresses (where the lower bytes are < 1000) are reserved for the protocol. The four testnet stablecoins (pathUSD, AlphaUSD, BetaUSD, ThetaUSD) use these.

Find your token's address

Check the transaction receipt for the token address. Alternatively, you can predict it:

# Get the transaction receipt
cast receipt $TX_HASH --rpc-url https://rpc.moderato.tempo.xyz

The new token address will start with 0x20c0. This prefix identifies any TIP-20 token on-chain.

Configure roles

TIP-20 tokens use a role-based access control system. The admin address set during creation can grant these roles:

RolePermissions
DEFAULT_ADMIN_ROLEGrant/revoke all roles, set supply cap, change transfer policy
ISSUER_ROLEMint and burn tokens
PAUSE_ROLEPause all token transfers
UNPAUSE_ROLEUnpause token transfers
BURN_BLOCKED_ROLEBurn tokens from blocked addresses (compliance)

Grant the issuer role to your minting address:

# keccak256("ISSUER_ROLE")
ISSUER_ROLE=$(cast keccak "ISSUER_ROLE")
 
cast send $TOKEN_ADDRESS \
  "grantRole(bytes32,address)" \
  $ISSUER_ROLE $MINTER_ADDRESS \
  --rpc-url https://rpc.moderato.tempo.xyz \
  --private-key $ADMIN_PRIVATE_KEY

Set a supply cap

By default, tokens are created with no practical supply cap (type(uint128).max). To set a cap:

# Set supply cap to 1,000,000 tokens (1_000_000 * 10^6)
cast send $TOKEN_ADDRESS \
  "setSupplyCap(uint256)" \
  1000000000000 \
  --rpc-url https://rpc.moderato.tempo.xyz \
  --private-key $ADMIN_PRIVATE_KEY

All TIP-20 amounts use 6 decimals. 1,000,000 tokens = 1000000000000 (1e12).

Mint tokens

With the ISSUER_ROLE granted, mint tokens to any address:

# Mint 10,000 XUSD to a recipient
cast send $TOKEN_ADDRESS \
  "mint(address,uint256)" \
  0xRECIPIENT_ADDRESS 10000000000 \
  --rpc-url https://rpc.moderato.tempo.xyz \
  --private-key $MINTER_PRIVATE_KEY

To mint with an attached memo (for audit trails):

cast send $TOKEN_ADDRESS \
  "mintWithMemo(address,uint256,bytes32)" \
  0xRECIPIENT_ADDRESS 10000000000 \
  $(cast to-bytes32 "MINT-2026-001") \
  --rpc-url https://rpc.moderato.tempo.xyz \
  --private-key $MINTER_PRIVATE_KEY

Query token data with the Transfa Data API

Once your token exists on-chain, you can query its metadata, holders, and allowances through the Transfa Data API instead of making multiple RPC calls.

Get token metadata

curl -X GET "https://api.transfa.com/v1/tokens/$TOKEN_ADDRESS?chain=moderato" \
  -H "x-api-key: YOUR_API_KEY"
{
  "data": {
    "address": "0x20c0...",
    "name": "Example USD",
    "symbol": "XUSD",
    "decimals": 6,
    "totalSupply": "10000000000",
    "currency": "USD",
    "supplyCap": "1000000000000",
    "paused": false,
    "transferPolicyId": "1"
  }
}

List token holders

curl -X GET "https://api.transfa.com/v1/tokens/$TOKEN_ADDRESS/holders?chain=moderato" \
  -H "x-api-key: YOUR_API_KEY"

This returns every address holding a non-zero balance of your token, sorted by balance. You can filter with minBalance to exclude dust amounts.

Check allowances

curl -X GET "https://api.transfa.com/v1/tokens/$TOKEN_ADDRESS/allowances?chain=moderato" \
  -H "x-api-key: YOUR_API_KEY"

Returns all active approve() allowances for your token, useful for monitoring which addresses have spending permissions and how much they can move.

Transfer policy and compliance

Every TIP-20 token has a transferPolicyId that references the TIP-403 Policy Registry. By default, new tokens use policy ID 1 (always-allow).

When a transfer occurs, the protocol calls isAuthorized(policyId, from) and isAuthorized(policyId, to) on the registry. Both must return true or the transaction reverts with PolicyForbids().

To change the transfer policy:

cast send $TOKEN_ADDRESS \
  "changeTransferPolicyId(uint64)" \
  42 \
  --rpc-url https://rpc.moderato.tempo.xyz \
  --private-key $ADMIN_PRIVATE_KEY

This allows issuers to implement compliance controls (KYC, sanctions screening) at the protocol level without modifying the token contract itself.

What to read next