---
name: thaichain
description: Build and interact with smart contracts on ThaiChain (Tempo-compatible, chain ID 7). Covers RPC, gas fee model (TIP-20 stablecoin fees via Fee AMM), predeployed system contracts, TIP-20 token creation, adding tokens as fee tokens, and deploying contracts with Foundry.
---

# ThaiChain — Developer Guide

ThaiChain is a Tempo-compatible EVM chain (chain ID 7). It uses **TIP-20 stablecoins for gas fees** instead of a volatile native token. This guide covers everything a coding agent needs to build on ThaiChain.

## Chain Configuration

| Property | Value |
|----------|-------|
| **Chain ID** | `7` |
| **Network name** | ThaiChain |
| **RPC URL** | `https://rpc.thaichain.org` |
| **Explorer** | `https://exp.thaichain.org` |
| **Native currency** | TCH (not used for gas — see Fee Model below) |
| **Block time** | 0.5s |
| **Max throughput** | ~100,000 TPS |
| **Consensus** | Simplex BFT with DKG |
| **Gas model** | Tempo Transactions (type `0x76`) — pay fees in TIP-20 stablecoins |

### viem chain definition (TypeScript)

```typescript
import { defineChain } from "viem";

export const thaichain = defineChain({
  id: 7,
  name: "ThaiChain",
  nativeCurrency: { name: "TCH TOKEN", symbol: "TCH", decimals: 18 },
  rpcUrls: { default: { http: ["https://rpc.thaichain.org"] } },
  blockExplorers: {
    default: { name: "ThaiChain Explorer", url: "https://exp.thaichain.org" },
  },
  contracts: {
    multicall3: { address: "0xcA11bde05977b3631167028862bE2a173976CA11" },
  },
  feeToken: "0x20C0000000000000000000000000000000000000" as const, // TCH
});
```

## Gas Fee Model (Important — NOT native gas)

ThaiChain has **no volatile native gas token**. Fees are paid in TIP-20 stablecoins.

- **Default fee token:** TCH (`0x20C0000000000000000000000000000000000000`)
- **Transaction type:** `0x76` (Tempo transaction envelope with `feePayer` field)
- **Fee AMM:** auto-converts any USD-denominated TIP-20 token to the validator's preferred fee token
- **Sponsorship:** a `feePayer` field allows third parties to cover fees (gas sponsorship)
- **Any-token gas:** Any TIP-20 token can pay for gas — the protocol auto-converts via the Fee AMM

### Implication for `cast`/`forge`
Because fees are paid in a stablecoin, you must hold a fee token balance (e.g. TCH) — not native ETH — to transact. The `--private-key` account must have TIP-20 tokens approved/spendable for the FeeManager, or you must use a `feePayer` (sponsor).

## Predeployed System Contracts

| Contract | Address | Purpose |
|----------|---------|---------|
| **TCH** | `0x20C0000000000000000000000000000000000000` | First stablecoin (default fee token) |
| **TIP-20 Factory** | `0x20fc000000000000000000000000000000000000` | Create new TIP-20 tokens |
| **Fee Manager** | `0xfeec000000000000000000000000000000000000` | Fee payments & conversions |
| **Stablecoin DEX** | `0xdec0000000000000000000000000000000000000` | Enshrined DEX for stablecoin swaps |
| **TIP-403 Registry** | `0x403c000000000000000000000000000000000000` | Transfer policy registry |
| **Multicall3** | `0xcA11bde05977b3631167028862bE2a173976CA11` | Batch multiple calls |
| **CreateX** | `0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed` | Deterministic CREATE2 deployment |
| **Permit2** | `0x000000000022d473030f116ddee9f6b43ac78ba3` | Token approvals & transfers |

ABIs available via SDK:
```typescript
import { Abis } from "viem/tempo";
const tip20FactoryAbi = Abis.tip20Factory;
const feeManagerAbi = Abis.feeManager;
const feeAmmAbi = Abis.feeAmm;
const stablecoinDexAbi = Abis.stablecoinDex;
```

## TIP-20 Token Standard

TIP-20 extends ERC-20 with payment-native features:
- **Pay fees** in any USD-denominated TIP-20 token (via Fee AMM)
- **RBAC roles:** `ISSUER_ROLE` (mint/burn), `PAUSE_ROLE`, `UNPAUSE_ROLE`, `BURN_BLOCKED_ROLE`
- **Transfer memos** (32-byte reference attached to transfers)
- **Currency declaration** — token tracks an asset (e.g. `"USD"`)
- **Supply caps** and **pause/unpause** controls
- **Reward distribution** system for holders
- **Compliance** via TIP-403 Policy Registry (whitelist/blacklist)

### Create a TIP-20 Token

Call `createToken` on the TIP-20 Factory:

```bash
cast send 0x20fc000000000000000000000000000000000000 \
  "createToken(string,string,uint8,string,uint256)" \
  "My USD Coin" "MUSD" 6 "USD" 1000000000000 \
  --rpc-url https://rpc.thaichain.org \
  --private-key $PRIVATE_KEY
```

Args: `name, symbol, decimals, currency, maxSupply` (use `type(uint256).max` for uncapped).

The factory deploys the token deterministically and assigns the caller all roles (issuer/pause/etc.). Read the new token's address from the `TokenCreated` event logs.

### Mint TIP-20 Tokens

Once created, the issuer can mint:

```bash
cast send <TOKEN_ADDRESS> "mint(address,uint256)" \
  0xRECIPIENT 1000000000 \
  --rpc-url https://rpc.thaichain.org \
  --private-key $ISSUER_PRIVATE_KEY
```

## Add a Token to the Fee AMM (use it for gas fees)

To let users pay gas in a custom TIP-20 token (beyond the default TCH), add liquidity to the Stablecoin DEX so the Fee AMM can convert it. The general flow:

1. **Approve** your TIP-20 token for the Stablecoin DEX.
2. **Add liquidity** pairing your token with TCH (or another listed USD token).

```bash
# 1. Approve token for the DEX
cast send <TOKEN_ADDRESS> "approve(address,uint256)" \
  0xdec0000000000000000000000000000000000000 \
  $(cast max-uint256) \
  --rpc-url https://rpc.thaichain.org \
  --private-key $PRIVATE_KEY

# 2. Add liquidity (pair TOKEN + TCH)
#    Exact function signature depends on the DEX version — check Abis.stablecoinDex
cast send 0xdec0000000000000000000000000000000000000 \
  "addLiquidity(address,uint256,address,uint256)" \
  <TOKEN_ADDRESS> 1000000000 \
  0x20C0000000000000000000000000000000000000 1000000000 \
  --rpc-url https://rpc.thaichain.org \
  --private-key $PRIVATE_KEY
```

After liquidity exists, the Fee AMM will accept your token for fee payments and auto-convert to the validator's preferred fee token. Verify the token is listed as a fee token via the Fee Manager.

> Always confirm the exact function signatures against `Abis.stablecoinDex` and `Abis.feeManager` in `viem/tempo` — the DEX interface may evolve.

## 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 Accumulated Fees

```bash
# Check how much fee a validator has accumulated for a token
cast call 0xfeec000000000000000000000000000000000000 \
  "collectedFees(address,address)(uint256)" \
  $VALIDATOR 0x20C0000000000000000000000000000000000000 \
  --rpc-url https://rpc.thaichain.org
```

### Check Validator Fee Token

```bash
cast call 0xfeec000000000000000000000000000000000000 \
  "validatorTokens(address)(address)" \
  $VALIDATOR \
  --rpc-url https://rpc.thaichain.org
```

### Claim Accumulated Fees (Distribute)

```bash
# Anyone can call — transfers accumulated fees to validator
cast send 0xfeec000000000000000000000000000000000000 \
  "distributeFees(address,address)" \
  $VALIDATOR 0x20C0000000000000000000000000000000000000 \
  --private-key $PRIVATE_KEY \
  --rpc-url https://rpc.thaichain.org
```

### Check FeeManager Balance

```bash
# Check how much TCH FeeManager holds (= total accumulated fees for all validators)
cast call 0x20C0000000000000000000000000000000000000 \
  "balanceOf(address)(uint256)" \
  0xfeec000000000000000000000000000000000000 \
  --rpc-url https://rpc.thaichain.org
```

## Deploying Smart Contracts with Foundry

### Install Foundry

```bash
curl -L https://getfoundry.sh/install | bash
```

After installation, restart your terminal and run:

```bash
foundryup
```

Verify installation:

```bash
cast --version
```

### Setup

```bash
mkdir my-project && cd my-project
forge init
cd lib && forge install OpenZeppelin/openzeppelin-contracts && cd ..
```

`foundry.toml`:
```toml
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
solc_version = "0.8.24"
remappings = [
  "@openzeppelin/contracts/=lib/openzeppelin-contracts/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` (recommended for complex flows)

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

**⚠️ Gas limit gotcha:** `forge script` batches multiple operations and may run out of gas. If deployment fails with "call gas cost exceeds gas limit", either:
- Split into multiple scripts/steps, or
- Use `forge create` for each contract individually, or
- Add `--gas-limit 10000000` and `--skip-simulation` flags.

### Deploy with Ledger hardware wallet

```fish
# fish shell — use `env VAR=value` prefix (fish doesn't support inline env)
forge create src/MyContract.sol:MyContract \
  --rpc-url https://rpc.thaichain.org \
  --ledger \
  --sender 0xYOUR_LEDGER_ADDRESS \
  --gas-limit 10000000 \
  --slow \
  --constructor-args 0x...
```

### Deploy UUPS Upgradeable Proxy (two-step)

Because `forge script` may hit gas limits with proxy deployments, deploy in two steps:

**Step 1 — Deploy implementation:**
```bash
forge create src/MyContractV2.sol:MyContractV2 \
  --rpc-url https://rpc.thaichain.org \
  --private-key $PRIVATE_KEY \
  --gas-limit 10000000
# Note the Implementation address
```

**Step 2 — Deploy proxy:**
```bash
# Encode initialize() calldata
INIT_DATA=$(cast abi-encode "initialize(address,uint256,uint256,address)" \
  0x20C0000000000000000000000000000000000000 \
  300000000 \
  7776000 \
  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
```

The **proxy address** is what users/dApps interact with — it remains constant across future upgrades. Use `upgradeToAndCall(newImpl, "")` (admin-only) to upgrade logic without changing the address or losing state.

## Verifying Contracts

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

Or use the explorer UI at `https://exp.thaichain.org`.

## Common `cast` Examples

```bash
RPC=https://rpc.thaichain.org
TCH=0x20C0000000000000000000000000000000000000

# Read TIP-20 balance
cast call $TCH "balanceOf(address)(uint256)" 0xUSER --rpc-url $RPC

# Read TIP-20 decimals/symbol/name
cast call $TCH "decimals()(uint8)" --rpc-url $RPC
cast call $TCH "symbol()(string)" --rpc-url $RPC

# Transfer TIP-20
cast send $TCH "transfer(address,uint256)" 0xRECIPIENT 1000000 \
  --rpc-url $RPC --private-key $PRIVATE_KEY

# Approve a spender
cast send $TCH "approve(address,uint256)" 0xSPENDER $(cast max-uint256) \
  --rpc-url $RPC --private-key $PRIVATE_KEY

# Read contract storage slot
cast storage 0xCONTRACT 0x0 --rpc-url $RPC

# Get block
cast block latest --rpc-url $RPC

# Estimate gas (note: ThaiChain uses fee token, not native gas)
cast estimate 0xCONTRACT "transfer(address,uint256)" 0xRECIPIENT 1000 --rpc-url $RPC
```

## View Token Info

TIP-20 tokens have fields: `name`, `symbol`, `currency`, `decimals`, `totalSupply`

```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)

### Conversion Table (6 decimals)

| Amount | Raw Value |
|--------|-----------|
| 1 TCH | `1000000` |
| 10 TCH | `10000000` |
| 100 TCH | `100000000` |
| 1,000 TCH | `1000000000` |
| 10,000 TCH | `10000000000` |

## Decode Data

### Decode Function Selector (4 bytes)

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

### Decode Calldata

```bash
cast pretty-calldata 0xa6c079240000000000000000000000005266dfa5ae013674f8fdc832b7c601b838d94ee600000000000000000000000020c0000000000000000000000000000000000000
```

Output:
```
Possible methods:
 - distributeFees(address,address)
 ------------
 [000]: 0000000000000000000000005266dfa5ae013674f8fdc832b7c601b838d94ee6  (validator)
 [020]: 00000000000000000000000020c0000000000000000000000000000000000000  (token)
```

## Gas Sponsorship (Fee Payer)

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

**Self-hosted fee-payer:** Deploy the [fee-payer worker](https://github.com/tempoxyz/tempo) (Cloudflare Worker + Hono) with:
- `SPONSOR_PRIVATE_KEY` — sponsor wallet that holds fee tokens
- `TEMPO_ENV=thaichain`
- `TEMPO_RPC_URL=https://rpc.thaichain.org`

**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
});
```

See: https://tempo.xyz/developers/docs/guide/payments/sponsor-user-fees

## Working with Tempo Transactions (type 0x76)

ThaiChain uses Tempo's transaction envelope (type `0x76`) which extends EIP-1559 with:
- `feeToken` — which TIP-20 token pays the fee
- `feePayerSignature` — optional sponsor signature
- `calls[]` — multiple calls in one tx (batching)
- `accessList`, `authorizationList` — standard fields

Standard `cast send` works for simple cases. For batching/sponsorship/scheduling, use the viem Tempo SDK or the JSON-RPC methods:
- `eth_fillTransaction` — fill missing fields (nonce, gas, fee payer)
- `eth_sendRawTransactionSync` — broadcast and wait for receipt
- `eth_signRawTransaction` — sign-only policy

## 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
```

## Useful Resources

- **Tempo Docs:** https://tempo.xyz/developers/docs
- **Predeployed Contracts:** https://tempo.xyz/developers/docs/quickstart/predeployed-contracts
- **TIP-20 Overview:** https://tempo.xyz/developers/docs/protocol/tip20/overview
- **Mint Stablecoins Guide:** https://tempo.xyz/developers/docs/guide/issuance/mint-stablecoins
- **Sponsor User Fees:** https://tempo.xyz/developers/docs/guide/payments/sponsor-user-fees
- **Contract Verification:** https://tempo.xyz/developers/docs/quickstart/verify-contracts
- **Tempo SDK (viem/tempo):** https://viem.sh/tempo
- **tempo-std interfaces:** https://github.com/tempoxyz/tempo-std

## Quick Reference Card

```bash
# Env vars
export RPC_URL=https://rpc.thaichain.org
export EXPLORER=https://exp.thaichain.org
export CHAIN_ID=7
export TCH=0x20C0000000000000000000000000000000000000
export TIP20_FACTORY=0x20fc000000000000000000000000000000000000
export FEE_MANAGER=0xfeec000000000000000000000000000000000000
export STABLECOIN_DEX=0xdec0000000000000000000000000000000000000
export MULTICALL3=0xcA11bde05977b3631167028862bE2a173976CA11

# Deploy
forge create src/Contract.sol:Contract --rpc-url $RPC_URL --private-key $KEY --gas-limit 10000000 --slow

# Interact
cast call $CONTRACT "functionName(args)" --rpc-url $RPC_URL
cast send $CONTRACT "functionName(args)" --rpc-url $RPC_URL --private-key $KEY

# Verify
forge verify-contract $ADDRESS src/Contract.sol:Contract --chain-id 7 --verifier sourcify --verifier-url https://contracts.thaichain.org
```
