Skip to main content
In this advanced tutorial, you’ll learn how to interact with the Hedera Token Service (HTS) using System Contracts precompiles on a forked network with Foundry. This guide covers creating HTS tokens, querying token info, and testing ERC-20 level interactions using the hedera-forking emulation layer. This guide shows how to:
  • Create HTS fungible tokens using System Contracts precompiles
  • Query HTS token info (getTokenInfo, getFungibleTokenInfo) on a forked network
  • Read HTS token properties via the ERC-20 interface (name, symbol, decimals, balanceOf)
  • Transfer HTS tokens using ERC-20 methods through the HIP-719 proxy pattern
References:
For a deeper understanding of how Hedera forking works and its limitations, see Forking Hedera Network for Local Testing.
You can take a look at the complete code in the advanced-hts-fork-test-foundry repository.

Prerequisites

  • Completed Part 1 of this tutorial series
  • Foundry installed
  • ECDSA account from the Hedera Portal with at least 20 HBAR (15 HBAR for HTS token creation fee + gas)
  • Familiarity with Hedera System Contracts - more specifically HTS System Contracts precompiles
  • A Hedera JSON-RPC endpoint:
    • mainnet: https://mainnet.hashio.io/api
    • testnet: https://testnet.hashio.io/api

Table of Contents

  1. Step 1: Project Setup
  2. Step 2: Create the HTS Contract and Deploy to Testnet
  3. Step 3: Write Tests for the Forked Network
  4. Step 4: Run Tests on the Forked Network

Step 1: Project Setup

Initialize Project

Create a new directory and initialize the Foundry project:

Install Dependencies

Install OpenZeppelin contracts and the Hedera forking library:
The hedera-forking library requires forge-std >= v1.8.0. If you’re on an older project, update it first with forge update lib/forge-std.

Configure Remappings

Create or update remappings.txt in your project root:
remappings.txt
Note that we are updating the remappings.txt in our root directory of the project and not in the lib directory where the dependencies are installed.

Set Environment Variables

Create a .env file in your project root:
.env
Replace the 0x-your-private-key environment variable with the HEX Encoded Private Key for your ECDSA account. Note that this account MUST exist on testnet and have at least 20 HBAR for the token creation fee and gas.
Load the environment variables:

Configure Foundry

Update your foundry.toml file:
foundry.toml
Why ffi = true? The hedera-forking emulation layer uses Foundry’s FFI cheatcode to shell out to curl and query the Hedera Mirror Node for real token data (balances, metadata, associations). Without ffi = true, the emulation cannot fetch data and HTS calls will fail.Security note: ffi = true allows Foundry to execute shell commands. Only enable this in test profiles, never in production deployment scripts.
Remove the default contracts that come with forge init:

Step 2: Create the HTS Contract and Deploy to Testnet

Create the HTS Interaction Contract

Create a new file src/HTSTokenManager.sol:
src/HTSTokenManager.sol
Key features of this contract:
  • createFungibleTokenPublic - Creates new HTS fungible tokens via the precompile at 0x167
  • mintTokenPublic - Mints additional tokens (requires supply key)
  • transferTokenPublic - Transfers HTS tokens between accounts
  • getTokenInfoPublic / getFungibleTokenInfoPublic - Query token information
  • The contract assigns itself as both the treasury and the supply/admin key holder

Compile the Contract

Create Deployment Script

Create a new file script/DeployHTS.s.sol:
script/DeployHTS.s.sol

Deploy to Testnet

Deployment is a two-step process. The reason is that forge script simulates all transactions locally before broadcasting them to the network. Since the HTS precompile at 0x167 has no EVM bytecode (it’s a native Hedera system contract), the local simulation fails with InvalidFEOpcode when trying to call createFungibleTokenPublic. By splitting the deployment, Step 1 deploys using forge script (standard EVM deploy), and Step 2 uses cast send which sends the transaction directly to the RPC without local simulation. Step 1: Deploy the HTSTokenManager contract:
You should see output similar to:
Save the contract address - you’ll need it for the next step. Step 2: Create the HTS token using cast send:
This sends the transaction directly to Hedera (bypassing local simulation), so the HTS precompile at 0x167 is handled natively by the consensus nodes. Step 3: Get the token address:
Step 4: Note the block number for fork testing:
Save the deployed contract address, token address, and block number! You’ll need these for your fork tests. The contract must exist at the block you’re forking from.
We have already deployed this HTS contract on testnet at https://hashscan.io/testnet/contract/0x22723B710D0A1Bdc83706Dd8085414c0570FaB8b so we will be using this for the remainder of this exercise.

Step 3: Write Tests for the Forked Network

Now we’ll write tests that interact with the deployed HTS contract on the forked testnet. The key difference from the basic ERC-20 tutorial is the htsSetup() call - this activates the HTS emulation layer at address 0x167 so that HTS precompile calls work in the forked environment. Create a new file test/HTSForkTest.t.sol:
Make sure to update the DEPLOYED_HTS_CONTRACT and HTS_TOKEN constants below with the values from your deployment.
test/HTSForkTest.t.sol
Key points about these tests:
  • htsSetup() is critical - Must be the first call in setUp() before any HTS interaction. It deploys the Solidity emulation layer at 0x167 so that HTS precompile calls work.
  • ERC-20 interface - HTS tokens expose standard ERC-20 methods (name, symbol, decimals, balanceOf, transfer, approve, transferFrom) through the HIP-719 proxy pattern. The emulation layer fetches real data from the Hedera Mirror Node via FFI.
  • deal() for balances - Foundry’s deal() cheatcode sets token balances directly, which works with HTS tokens because the emulation layer maps storage slots correctly.
  • vm.prank for impersonation - Act as any account without their private key.
  • Fork verification - Tests confirm the fork is connected, contracts have bytecode, and the HTS emulation layer is active at 0x167.
Foundry vs. Hardhat approach: The Hardhat advanced tutorial tests mintToken and transferToken directly through the HTS precompile because the Hardhat plugin intercepts at the JSON-RPC level. In Foundry, the emulation layer excels at read operations and ERC-20 level interactions. For setting balances in tests, use Foundry’s deal() cheatcode and standard ERC-20 methods (transfer, approve, transferFrom) which work through the HIP-719 proxy redirect pattern.

Step 4: Run Tests on the Forked Network

Run your tests against the forked Hedera testnet:
Pin to a specific block for reproducible tests:
You should see output similar to:

Pin to a Specific Block

For reproducible tests, use --fork-block-number with a block where your contract exists. If you try to fork at a block before your contract was deployed, setUp() will fail because the contract doesn’t exist yet at that block.

Best Practices for HTS Fork Testing with Foundry

  1. Always call htsSetup() first - It must be the very first call in setUp(), before any HTS interaction
  2. Use ffi = true only in test profiles - FFI allows arbitrary shell execution; never enable it in production deployment scripts
  3. Pin your block number - Use --fork-block-number for deterministic, reproducible tests in CI/CD
  4. Use supported methods - Stick to the currently supported HTS methods
  5. Always verify on real network - Fork testing is for development speed; always test on testnet/mainnet before production

Bonus: Real-World SaucerSwap Mainnet Fork Test

The tutorial repository includes a bonus test that demonstrates one of the most powerful use cases for fork testing: interacting with production DeFi contracts on Hedera mainnet without spending real HBAR. The SaucerSwapForkTest.t.sol file forks Hedera mainnet and executes a real token swap through SaucerSwap V2 - swapping WHBAR for USDC at the current mainnet exchange rate, using real liquidity pools.

Run the SaucerSwap Tests

These tests use mainnet (not testnet). No .env configuration is needed - fork tests don’t require a private key because all balances are created locally with Foundry cheatcodes.

The Real Swap Test

The headline test (test_SwapWHBARForUSDCViaSaucerSwap) executes a real swap through SaucerSwap V2’s exactInput function:

How It Works

Where does the WHBAR come from if the test account doesn’t exist on mainnet? Foundry’s deal(token, account, amount) writes directly to the token’s storage slots on the forked EVM. It sets the balance for the given account without any real transfer. The account doesn’t need to exist on mainnet. Similarly, vm.deal(account, amount) sets native HBAR balances locally. Both cheatcodes only affect the fork - mainnet is never touched. How does the swap execute against real liquidity? The fork is a snapshot of mainnet state. The SaucerSwap V2 Router has real bytecode, and the WHBAR/USDC pool has real liquidity deposited by real LPs. When the test calls exactInput, the router reads real pool state (liquidity, tick, price), pulls WHBAR from the trader, swaps through the pool, and sends USDC to the trader - all at the real exchange rate. The entire execution happens locally on the fork. Can I impersonate a real mainnet account instead? Yes. vm.prank(realMainnetAddress) makes the next call appear to come from any address - no private key needed. You could impersonate a whale with millions in HBAR and use their real balances for testing:
Why does this need htsSetup()? Both WHBAR and USDC are HTS tokens. When the SaucerSwap router calls transferFrom on these tokens during the swap, the call goes through the HIP-719 proxy to 0x167. Without htsSetup(), that address returns 0xfe and the entire swap reverts.

Mainnet Addresses

Source: SaucerSwap Contract Deployments

Bonus: Bonzo Finance Mainnet Fork Test (Lending/Borrowing)

The tutorial repository also includes a test that forks Hedera mainnet and interacts with Bonzo Finance - an Aave V2 fork and the first lending/borrowing protocol on Hedera. The test deposits WHBAR as collateral and borrows USDC against it, using real contracts with ~7M USDC in real liquidity.

Run the Bonzo Tests

What It Tests

How the Deposit + Borrow Works

The LendingPool uses Bonzo’s real oracle pricing to calculate collateral value, LTV ratios, and health factors - all against production state on the fork. Why this matters: If you’re building on top of Bonzo (or any Aave V2 fork on Hedera), fork testing lets you test your integration against real protocol state, verify borrowing logic against real oracle prices, and simulate liquidation scenarios - without risking real funds.

Bonzo Mainnet Addresses

Source: Bonzo Lend Contracts

Understanding HTS Fork Testing with Foundry

Why Standard Fork Testing Breaks on Hedera

On standard EVM chains, every contract is on-chain bytecode. When you fork and call any contract, the fork fetches its bytecode and executes it locally. Hedera’s system contracts (HTS at 0x167, Exchange Rate at 0x168, PRNG at 0x169) are native services implemented in the consensus node software - they have no EVM bytecode. When your fork tries to fetch code at 0x167, the JSON-RPC relay returns 0xfe (the INVALID opcode), and your test crashes with InvalidFEOpcode.

How htsSetup() Fixes It

The htsSetup() function from the hedera-forking library:
  1. Deploys the HtsSystemContractJson emulation contract at 0x167 using vm.etch
  2. Creates a MirrorNodeFFI instance that queries the Hedera Mirror Node via curl
  3. Calls vm.allowCheatcodes(0x167) so the emulation can use vm.store, vm.ffi, and vm.parseJson
After htsSetup(), HTS calls work because they hit a Solidity contract that fetches real token data from the Mirror Node.

How the HIP-719 Proxy Pattern Works

Every HTS token address on Hedera contains identical proxy bytecode (defined by HIP-719). When you call token.balanceOf(), the proxy delegates the call to 0x167 via redirectForToken. The emulation contract at 0x167 receives the call, fetches the real balance from the Mirror Node via FFI, and returns it.

Foundry vs. Hardhat Comparison for HTS Fork Testing

Local vs. Remote State


Further Learning & Next Steps

  1. Forking Hedera Network for Local Testing
    Deep dive into how Hedera forking works under the hood
  2. How to Fork Hedera with Foundry (Part 1)
    Start with basic ERC-20 fork testing
  3. How to Fork Hedera with Hardhat - Advanced HTS
    Compare the Hardhat approach to HTS fork testing
  4. hedera-forking Repository
    Explore examples and documentation
  5. Hiero Contracts Repository
    Explore HTS System Contracts interfaces

Writer: Kiran Pachhai, Developer Advocate