- 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
- Repo: hashgraph/hedera-forking
- HTS System Contracts: hiero-contracts
- Supported methods: README - Supported Methods
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
- mainnet:
Table of Contents
- Step 1: Project Setup
- Step 2: Create the HTS Contract and Deploy to Testnet
- Step 3: Write Tests for the Forked Network
- 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:Configure Remappings
Create or updateremappings.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
Configure Foundry
Update yourfoundry.toml file:
foundry.toml
forge init:
Step 2: Create the HTS Contract and Deploy to Testnet
Create the HTS Interaction Contract
Create a new filesrc/HTSTokenManager.sol:
src/HTSTokenManager.sol
createFungibleTokenPublic- Creates new HTS fungible tokens via the precompile at0x167mintTokenPublic- Mints additional tokens (requires supply key)transferTokenPublic- Transfers HTS tokens between accountsgetTokenInfoPublic/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 filescript/DeployHTS.s.sol:
script/DeployHTS.s.sol
Deploy to Testnet
Deployment is a two-step process. The reason is thatforge 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:
cast send:
0x167 is handled natively by the consensus nodes.
Step 3: Get the token address:
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 thehtsSetup() 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
htsSetup()is critical - Must be the first call insetUp()before any HTS interaction. It deploys the Solidity emulation layer at0x167so 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’sdeal()cheatcode sets token balances directly, which works with HTS tokens because the emulation layer maps storage slots correctly.vm.prankfor 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, 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
-
Always call
htsSetup()first - It must be the very first call insetUp(), before any HTS interaction -
Use
ffi = trueonly in test profiles - FFI allows arbitrary shell execution; never enable it in production deployment scripts -
Pin your block number - Use
--fork-block-numberfor deterministic, reproducible tests in CI/CD - Use supported methods - Stick to the currently supported HTS methods
- 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. TheSaucerSwapForkTest.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’sdeal(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:
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
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 at0x167, 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:
- Deploys the
HtsSystemContractJsonemulation contract at0x167usingvm.etch - Creates a
MirrorNodeFFIinstance that queries the Hedera Mirror Node via curl - Calls
vm.allowCheatcodes(0x167)so the emulation can usevm.store,vm.ffi, andvm.parseJson
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 calltoken.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
-
Forking Hedera Network for Local Testing
Deep dive into how Hedera forking works under the hood -
How to Fork Hedera with Foundry (Part 1)
Start with basic ERC-20 fork testing -
How to Fork Hedera with Hardhat - Advanced HTS
Compare the Hardhat approach to HTS fork testing -
hedera-forking Repository
Explore examples and documentation -
Hiero Contracts Repository
Explore HTS System Contracts interfaces