ThaiChain
Back to home

ThaiChain Documentation

Complete guide for interacting with ThaiChain using Foundry Cast. Learn how to query blocks, transactions, tokens, deploy contracts, and manage fees.

Download SKILL.md for AI agents

A ready-to-use skill file for vibe-coding agents (Claude Code, Cursor, etc.) — drop it into your project to give the agent full ThaiChain context.

Network Information

Chain ID
7
Block Time
0.5s
Max Throughput
~100,000 TPS
Consensus
Simplex BFT with DKG
RPC Endpoint
https://rpc.thaichain.org

Table of Contents

01. Install Foundry

Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust. castis Foundry's command-line tool for interacting with EVM smart contracts, sending transactions, and getting chain data.

Install via foundryup

Bash — macOS / Linux
curl -L https://getfoundry.sh/install | bash

After installation, restart your terminal and run:

Bash
foundryup

Verify Installation

Bash
cast --version

02. Gas Fee Model

Important: ThaiChain has no volatile native gas token. Fees are paid in TIP-20 stablecoins — not native ETH.

Because fees are paid in a stablecoin, you must hold a fee token balance (e.g. TCH) — not native ETH — to transact.

03. Setup

Common Variables

Bash
# RPC endpoint
RPC=https://rpc.thaichain.org

# Token & system addresses
TCH=0x20C0000000000000000000000000000000000000
TIP20_FACTORY=0x20fc000000000000000000000000000000000000
FEE_MANAGER=0xfeEC000000000000000000000000000000000000
STABLECOIN_DEX=0xdec0000000000000000000000000000000000000

# Private key (store as env variable)
export PRIVATE_KEY=<your_private_key>

Predeployed System Contracts

AddressNamePurpose
0x20C0...0000TCHFirst stablecoin (default fee token)
0x20fc...0000TIP-20 FactoryCreate new TIP-20 tokens
0xfeEC...0000Fee ManagerFee payments, Fee AMM & fee token settings
0xdec0...0000Stablecoin DEXEnshrined DEX for stablecoin swaps
0x403c...0000TIP-403 RegistryTransfer policy registry
0xcA11...CA11Multicall3Batch multiple calls
0xba5E...a5EdCreateXDeterministic CREATE2 deployment
0x0000...8ba3Permit2Token approvals & transfers

04. TIP-20 Token Standard

TIP-20 extends ERC-20 with payment-native features:

05. Create a TIP-20 Token

Call createToken on the TIP-20 Factory:

Bash
cast send 0x20fc000000000000000000000000000000000000 \
  "createToken(string,string,string,address,address,bytes32)" \
  "My USD Coin" "MUSD" "USD" \
  0x20C0000000000000000000000000000000000000 \
  $YOUR_ADDRESS \
  0x0000000000000000000000000000000000000000000000000000000000000000 \
  --rpc-url https://rpc.thaichain.org \
  --private-key $PRIVATE_KEY

Args: name, symbol, currency, quoteToken, admin, salt. The quoteToken must be TCH (0x20C0...0000) — passing address(0) reverts with InvalidQuoteToken.

The factory deploys the token deterministically from salt + admin — same wallet with the same salt reverts TokenAlreadyExists; bump the salt for each new token. The caller gets all roles (issuer/pause/etc.). The new token's address is in topics[1] of the TokenCreated event (zero-padded — take the last 40 hex chars).

Bash — read token address from the event
TOPIC=$(cast logs --address 0x20fc000000000000000000000000000000000000 \
  --from-block latest \
  "TokenCreated(address,string,string,string,address,address,bytes32)" \
  --rpc-url https://rpc.thaichain.org \
  | grep -oE '0x0{24}[0-9a-fA-F]{40}' | head -1)
TOKEN="0x${TOPIC:26}"
echo "Token address: $TOKEN"

06. View Token Info

Bash
# View all token info at once
echo "Name:         $(cast call $TCH 'name()(string)' --rpc-url $RPC)"
echo "Symbol:       $(cast call $TCH 'symbol()(string)' --rpc-url $RPC)"
echo "Currency:     $(cast call $TCH 'currency()(string)' --rpc-url $RPC)"
echo "Decimals:     $(cast call $TCH 'decimals()(uint8)' --rpc-url $RPC)"
echo "Total Supply: $(cast call $TCH 'totalSupply()(uint256)' --rpc-url $RPC)"

Note: TIP-20 tokens always use 6 decimals (THAICHAIN-TIP21)

07. View Balance

Bash
cast call $TCH "balanceOf(address)(uint256)" $ADMIN --rpc-url $RPC

Conversion Table (6 decimals)

AmountRaw Value
1 TCH1000000
10 TCH10000000
100 TCH100000000
1,000 TCH1000000000
10,000 TCH10000000000

08. Mint Token

Note: You must have ISSUER_ROLE to mint tokens.

Bash
# Mint 100 TCH (100 * 10^6 = 100000000)
cast send $TCH \
  "mint(address,uint256)" \
  $RECIPIENT 100000000 \
  --private-key $PRIVATE_KEY \
  --rpc-url $RPC

09. Transfer Token

Bash
# Transfer 1 TCH (1,000,000 units) to recipient
cast send $TCH \
  "transfer(address,uint256)(bool)" \
  $RECIPIENT 1000000 \
  --private-key $PRIVATE_KEY \
  --rpc-url $RPC

10. Add Token to Fee AMM

To let users pay gas in your TIP-20 token, the Fee AMMneeds liquidity to convert it into the validator's fee token (TCH). The Fee AMM lives inside the Fee Manager precompile (0xfeec000000000000000000000000000000000000) — not the Stablecoin DEX (which is an orderbook for trading).

Symptom of missing liquidity: transactions from accounts whose fee token is your token revert with insufficient liquidity in FeeAMM pool to swap fee tokens — even for a simple transfer.

Step 1 — Approve both tokens for the Fee Manager

Bash
# Your token (example: 10 tokens, 6 decimals)
cast send <TOKEN_ADDRESS> "approve(address,uint256)" \
  0xfeec000000000000000000000000000000000000 10000000 \
  --rpc-url https://rpc.thaichain.org --private-key $PRIVATE_KEY

# TCH (validator token)
cast send 0x20C0000000000000000000000000000000000000 \
  "approve(address,uint256)" \
  0xfeec000000000000000000000000000000000000 10000000 \
  --rpc-url https://rpc.thaichain.org --private-key $PRIVATE_KEY

Step 2 — Mint Fee AMM liquidity

Bash
# mint(userToken, validatorToken, amountValidatorToken, to)
# You specify how much TCH to add; the AMM pairs it with your token (~1:1, both USD-stable).
cast send 0xfeec000000000000000000000000000000000000 \
  "mint(address,address,uint256,address)" \
  <TOKEN_ADDRESS> \
  0x20C0000000000000000000000000000000000000 \
  10000000 \
  $YOUR_ADDRESS \
  --rpc-url https://rpc.thaichain.org --private-key $PRIVATE_KEY

Step 3 — Verify pool reserves

Bash
cast call 0xfeec000000000000000000000000000000000000 \
  "getPool(address,address)(uint128,uint128)" \
  <TOKEN_ADDRESS> \
  0x20C0000000000000000000000000000000000000 \
  --rpc-url https://rpc.thaichain.org

How much liquidity? Fees are tiny (~0.0002 USD per tx), so a small pool goes a long way — 10 TCH covers tens of thousands of transactions. Users paying fees in your token push it into the pool and TCH flows out to validators, keeping the pool balanced automatically.

Optional: to also make your token tradeable, add orderbook liquidity on the Stablecoin DEX (createPair + place bid/ask orders) — that is a separate system from the Fee AMM.

11. Fee Manager

Fee Manager precompile is located at 0xfeEC000000000000000000000000000000000000

Fee Flow

User sends transaction
    │
    ▼
collect_fee_pre_tx  → Deduct gas fee from user
    │
    ▼
Transaction executes
    │
    ▼
collect_fee_post_tx → Accumulate to collected_fees[validator][token]
    │
    ▼
distributeFees()   → Transfer accumulated fees → validator address

Check Validator Fees

Bash
# Accumulated fees
cast call $FEE_MANAGER \
  "collectedFees(address,address)(uint256)" \
  $VALIDATOR $TCH --rpc-url $RPC

# Validator fee token
cast call $FEE_MANAGER \
  "validatorTokens(address)(address)" \
  $VALIDATOR --rpc-url $RPC

Distribute Fees

Bash
# Anyone can call — transfers accumulated fees to validator
cast send $FEE_MANAGER \
  "distributeFees(address,address)" \
  $VALIDATOR $TCH \
  --private-key $PRIVATE_KEY --rpc-url $RPC

12. Set Your Fee Token

Every account has a default fee token (initially TCH). If you hold a different TIP-20 stablecoin, you can switch your fee token to it — as long as the Fee AMM has liquidity for that token. This lets an account transact without ever holding TCH.

Check your current fee token

Bash
cast call 0xfeec000000000000000000000000000000000000 \
  "userTokens(address)(address)" \
  $YOUR_ADDRESS --rpc-url https://rpc.thaichain.org

Set your fee token

Bash
# Example: pay fees in your USD stablecoin
cast send 0xfeec000000000000000000000000000000000000 \
  "setUserToken(address)" \
  <TOKEN_ADDRESS> \
  --rpc-url https://rpc.thaichain.org --private-key $PRIVATE_KEY

Note: the setUserToken transaction itself pays fees in your current fee token. Brand-new accounts with zero fee-token balance need a sponsored first transaction (see Gas Sponsorship) or a small TCH top-up to get started.

13. Gas Sponsorship

ThaiChain supports Tempo's fee sponsorship model — a third party (fee payer) signs the feePayerSignatureso users don't need to hold fee tokens.

Client-side (viem)

TypeScript
import { Account, withRelay } from "viem/tempo";

const account = Account.fromSecp256k1(privateKey);
const client = createWalletClient({
  account,
  chain: thaichain,
  transport: withRelay(
    http("https://rpc.thaichain.org"),
    http("https://sponsor.thaichain.org"),
    { policy: "sign-and-broadcast" },
  ),
});

await client.sendTransaction({
  to, data, value,
  feePayer: true, // ← enables sponsorship
});

Self-hosted Fee Payer

Deploy the fee-payer worker (Cloudflare Worker + Hono) with:

Env
SPONSOR_PRIVATE_KEY=<sponsor_wallet_key>
TEMPO_ENV=thaichain
TEMPO_RPC_URL=https://rpc.thaichain.org

14. Deploy Smart Contracts

Deploy with forge create

Bash
forge create src/MyContract.sol:MyContract \
  --rpc-url https://rpc.thaichain.org \
  --private-key $PRIVATE_KEY \
  --constructor-args 0x20C0000000000000000000000000000000000000 \
  --gas-limit 10000000 --slow

Deploy with forge script

Bash
forge script script/Deploy.s.sol \
  --rpc-url https://rpc.thaichain.org \
  --broadcast \
  --private-key $PRIVATE_KEY \
  --slow

⚠️ Gas limit gotcha: forge script may run out of gas. Add --gas-limit 10000000 --skip-simulation or use forge create per contract.

UUPS Upgradeable Proxy (two-step)

Bash
# Step 1 — Deploy implementation
forge create src/MyContractV2.sol:MyContractV2 \
  --rpc-url https://rpc.thaichain.org \
  --private-key $PRIVATE_KEY --gas-limit 10000000

# Step 2 — Deploy proxy
INIT_DATA=$(cast abi-encode "initialize(address)" 0xADMIN_ADDRESS)

forge create @openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol:ERC1967Proxy \
  --rpc-url https://rpc.thaichain.org \
  --private-key $PRIVATE_KEY --gas-limit 5000000 \
  --constructor-args 0xIMPLEMENTATION_ADDRESS $INIT_DATA

15. Verify Contracts

Bash
forge verify-contract 0xYOUR_CONTRACT_ADDRESS \
  src/MyContract.sol:MyContract \
  --chain-id 7 \
  --verifier sourcify \
  --verifier-url https://contracts.thaichain.org

16. Tempo Transactions (0x76)

ThaiChain uses Tempo's transaction envelope (type 0x76) which extends EIP-1559 with:

JSON-RPC Methods

Bash
# Fill missing fields (nonce, gas, fee payer)
eth_fillTransaction

# Broadcast and wait for receipt
eth_sendRawTransactionSync

# Sign-only policy
eth_signRawTransaction

17. Decode Data

Decode Function Selector

Bash
cast 4byte 0xa6c07924
# Output: distributeFees(address,address)

Decode Calldata

Bash
cast pretty-calldata 0xa6c079240000000000000000000000005266dfa5ae013674f8fdc832b7c601b838d94ee600000000000000000000000020c0000000000000000000000000000000000000

18. Example Scripts

Check All Tokens

Bash
#!/bin/bash
RPC=https://rpc.thaichain.org
TOKENS=(
  "0x20C0000000000000000000000000000000000000"
)

for TOKEN in "${TOKENS[@]}"; do
  echo "=== Token: $TOKEN ==="
  echo "Name:       $(cast call $TOKEN 'name()(string)' --rpc-url $RPC)"
  echo "Symbol:     $(cast call $TOKEN 'symbol()(string)' --rpc-url $RPC)"
  echo "Currency:   $(cast call $TOKEN 'currency()(string)' --rpc-url $RPC)"
  echo "Decimals:   $(cast call $TOKEN 'decimals()(uint8)' --rpc-url $RPC)"
  echo "Supply:     $(cast call $TOKEN 'totalSupply()(uint256)' --rpc-url $RPC)"
  echo ""
done

Check Validator Status

Bash
#!/bin/bash
RPC=https://rpc.thaichain.org
FEE_MANAGER=0xfeec000000000000000000000000000000000000
TCH=0x20C0000000000000000000000000000000000000
VALIDATORS=(
  "0x5266Dfa5ae013674f8FdC832b7c601B838D94eE6"
)

for V in "${VALIDATORS[@]}"; do
  echo "=== Validator: $V ==="
  echo "Fee Token:    $(cast call $FEE_MANAGER 'validatorTokens(address)(address)' $V --rpc-url $RPC)"
  echo "Collected Fees: $(cast call $FEE_MANAGER 'collectedFees(address,address)(uint256)' $V $TCH --rpc-url $RPC)"
  echo ""
done

19. Use Case: Issue a Stablecoin

End-to-end: create a TIP-20 token, enable it as a gas fee token, and mint supply — the exact flow used to launch wrapped USDC on ThaiChain. Examples sign with --ledger; replace it with --private-key $PRIVATE_KEYif you don't use a Ledger.

Step 1 — Create the token via the TIP-20 Factory

Bash
cast send 0x20fc000000000000000000000000000000000000 \
  "createToken(string,string,string,address,address,bytes32)" \
  "J Point" "JPOINT" "USD" \
  0x20C0000000000000000000000000000000000000 \
  0xYOUR_WALLET \
  0x0000000000000000000000000000000000000000000000000000000000000002 \
  --rpc-url https://rpc.thaichain.org --ledger

quoteToken must be TCH (address(0) reverts). The salt must be unique per wallet — reusing one reverts TokenAlreadyExists.

Step 2 — Get the token address

Open the transaction on https://exp.thaichain.org — the new token address is in the TokenCreated event. Then export it:

Bash
export TOKEN=0x...   # your new token address

Step 3 — Grant the Minter role

The factory does not always auto-assign ISSUER_ROLE. Grant it to your wallet (required before minting):

Bash
cast send $TOKEN \
  "grantRole(bytes32,address)" \
  0x114e74f6ea3bd819998f78687bfcb11b140da08e9b7d222fa9c1f1ba1f2aa122 \
  0xYOUR_WALLET \
  --rpc-url https://rpc.thaichain.org --ledger

Step 4 — Enable the token for gas fees (Fee AMM)

Seed the Fee AMM with TCH so fees paid in your token can be converted for validators. First approve TCH for the Fee Manager:

Bash
cast send 0x20C0000000000000000000000000000000000000 \
  "approve(address,uint256)" \
  0xfeec000000000000000000000000000000000000 10000000 \
  --rpc-url https://rpc.thaichain.org --ledger

Then allow your token to be swapped into TCH for fees by minting Fee AMM liquidity:

Bash
cast send 0xfeec000000000000000000000000000000000000 \
  "mint(address,address,uint256,address)" \
  $TOKEN \
  0x20C0000000000000000000000000000000000000 \
  10000000 \
  0xYOUR_WALLET \
  --rpc-url https://rpc.thaichain.org --ledger

Only TCH is taken — your token's side of the pool starts at zero and fills automatically as users pay fees in it. 10 TCH covers tens of thousands of transactions.

Step 5 — Mint your token

Bash
# Mint 1,000 tokens (6 decimals → 1000 * 10^6)
cast send $TOKEN "mint(address,uint256)" \
  0xYOUR_WALLET 1000000000 \
  --rpc-url https://rpc.thaichain.org --ledger

Result: any account holding only your token can now transact — fees (~$0.0002/tx) are charged in your token and auto-converted through the Fee AMM. To also make the token tradeable, add orderbook liquidity on the Stablecoin DEX (separate system).

20. Useful Resources

Building with an AI agent?

Download the SKILL.md file and add it to your project. It gives Claude Code, Cursor, and other vibe-coding agents full context about ThaiChain — chain config, gas model, system contracts, and all the commands above.

Download SKILL.md
↑ Back to top