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. This guide covers creating HTS tokens, minting, transferring, and understanding the limitations of the forking emulation layer. This guide shows how to:
  • Create HTS fungible tokens using System Contracts precompiles
  • Mint and transfer HTS tokens on a forked network
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 repository.

Prerequisites


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 Supported HTS Methods
  4. Step 4: Run Tests on the Forked Network

Step 1: Project Setup

Initialize Project

Create a new directory and initialize the project:

Install Dependencies

Create or update your package.json with all required dependencies:
package.json
Note the addition of @hashgraph/smart-contracts which provides the HTS System Contracts interfaces and helper contracts. Then install all dependencies:
Why these specific versions?The @hashgraph/system-contracts-forking plugin requires Hardhat 2.22.x. Newer versions of Hardhat (2.28+) introduced breaking changes that cause a No known hardfork for execution error when forking Hedera networks.
  • hardhat@2.22.19 - Last compatible version before breaking changes
  • @nomicfoundation/hardhat-toolbox@5.0.0 - Compatible with Hardhat 2.22.x
  • @hashgraph/system-contracts-forking@0.1.2 - The Hedera forking plugin
  • @hashgraph/smart-contracts - HTS System Contracts interfaces
  • --legacy-peer-deps - Required to resolve dependency conflicts between these versions
Verify Hardhat is installed correctly:

Create Project Structure

Create the necessary directories:

Configure TypeScript

Create tsconfig.json in your project root:
tsconfig.json

Configure Hardhat

Create hardhat.config.ts in your project root:
hardhat.config.ts
Important configuration notes:
  • HEDERA_RPC_URL - Loaded from Hardhat configuration variables
  • HEDERA_PRIVATE_KEY - Loaded securely from configuration variables
  • hederaTestnet - Network configuration for deploying to real testnet
  • hardhat.forking - Configuration for forking testnet locally
  • blockNumber - Pin to a block where your deployed contract exists
  • chainId: 296 - Required for testnet (295 for mainnet)
  • workerPort: 10001 - Any free port for the worker that intercepts Hardhat calls
  • @ts-ignore - Required because chainId and workerPort are custom properties not in Hardhat’s type definitions
  • Optimizer is enabled for gas efficiency

Set Configuration Variables

Now that hardhat.config.ts exists, you can set the configuration variables. Hardhat allows you to securely store sensitive values using configuration variables:
When prompted, enter: https://testnet.hashio.io/api
When prompted, enter the HEX Encoded Private Key for your ECDSA account from the Hedera Portal.
Make sure your ECDSA account exists on testnet and has sufficient HBAR for deployment. You can fund your testnet account using the Hedera Portal.

Step 2: Create the HTS Contract and Deploy to Testnet

Create the HTS Interaction Contract

Create a new file contracts/HTSTokenManager.sol:
contracts/HTSTokenManager.sol
Key features of this contract:
  • createFungibleTokenPublic - Creates new HTS fungible tokens
  • mintTokenPublic - Mints additional tokens (requires supply key)
  • transferTokenPublic - Supported HTS transfer method
  • getTokenInfoPublic / getFungibleTokenInfoPublic - Query token information

Compile the Contract

Create Deployment Script

Create a new file scripts/deploy.ts:
scripts/deploy.ts

Deploy to Testnet

Deploy your contract to Hedera testnet:
You should see output similar to:
Save the deployed contract address and block number! You’ll need these for your fork tests. The contract must exist at the block you’re forking from.

Update Hardhat Config with Deployment Block

After deployment, update your hardhat.config.ts with the block number:
We have already deployed this HTS contract on testnet at https://hashscan.io/testnet/contract/0xdC6F13e9Bb740593ffacdB7510548FD2E62bc035 so we will be using this for the remainder of this exercise.

Step 3: Write Tests for Supported HTS Methods

Create a new file test/HTSTokenManager.test.ts:
Make sure to update the DEPLOYED_CONTRACT and TOKEN_ADDRESS constants below with the values from your deployment.
test/HTSTokenManager.test.ts
Key points about these tests:
  • TypeScript types - Uses generated types from typechain-types for type safety
  • Uses deployed contract - Tests bind to the already deployed HTSTokenManager contract using getContractAt
  • HTS token creation - Demonstrates creating fungible tokens using HTS System Contracts precompiles on the forked network
  • Event parsing - Parses CreatedToken, ResponseCode, and FungibleTokenInfo events to verify HTS operations
  • Response code validation - Checks for HTS SUCCESS response code (22) to confirm operations completed successfully
  • Self-contained tests - Each test creates its own token and operates on it, ensuring test isolation
  • Local modifications - All token creations and queries happen only on the local fork
  • No testnet changes - The real testnet is never modified by these tests
  • Uses fixtures - loadFixture ensures each test starts with a clean state
  • Funded accounts - Uses hardhat_setBalance to fund test accounts for gas fees and token creation costs

Step 4: Run Tests on the Forked Network

Run your tests against the forked Hedera testnet:
You should see output similar to:

Pin to a Specific Block

For reproducible tests, make sure the blockNumber in your hardhat.config.ts is set to a block where your contract exists. If you try to fork at a block before your contract was deployed, you’ll see an error because the contract doesn’t exist yet at that block.

Best Practices for HTS Fork Testing

  1. Always verify on real network - Fork testing is for development; always test on testnet/mainnet before production
  2. Use supported methods - Stick to the currently supported HTS methods
  3. Handle associations - Remember that token associations work differently in emulation
  4. Check response codes - Always verify HTS response codes (SUCCESS = 22)
  5. Fund test accounts - Use hardhat_setBalance to fund accounts for gas

Understanding Fork Testing with Deployed Contracts

Why Test Against Deployed Contracts?

  1. Real-world state - Test against actual balances, allowances, and state
  2. No deployment costs - Don’t spend gas deploying for every test run
  3. Impersonation - Act as any account (even the contract owner) without their private key
  4. Safe experimentation - Try anything without affecting the real network

How Impersonation Works

Hardhat’s impersonation feature allows you to act as any address without having its private key:
Note: Impersonation is not needed in this tutorial because HTSTokenManager is designed with the contract itself as the treasury and supply key holder. All functions are public with no access control, so anyone can call them. See Part 1 for an example where impersonation is required for onlyOwner functions.

Funding Accounts with hardhat_setBalance

Local test accounts on a forked network start with no balance. Fund them for gas and operations:

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
    Learn fork testing with Foundry framework
  3. hedera-forking Repository
    Explore examples and documentation
  4. Hiero Contracts Repository
    Explore HTS System Contracts interfaces

Writer: Kiran Pachhai, Developer Advocate