Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

RedDuck

Hedera HTS Regulated Token Template

Contract-controlled Hedera security-token demo with native HTS compliance, wallet actions, and a Cloudflare Workers frontend.

Hedera Network Frontend License


This repository demonstrates a regulated fungible token on Hedera Token Service (HTS).

The token is not an ERC-20 clone. It is a native HTS token with network-level KYC, freeze, wipe, pause, and supply controls. A small Solidity contract owns the demo authority for those HTS controls through Hedera's precompile, so visitors can connect a wallet and exercise the flow on testnet without any backend signing service.

The frontend is a static React app. There is no Worker API, no server-side private key, and no browser-bundled secret.

Demo sandbox notice

The faucet and compliance methods are intentionally public on testnet. This is a showcase repo: any visitor should be able to connect HashPack, grant/revoke KYC, freeze/unfreeze, wipe demo balances, pause/unpause, and claim demo tokens without asking for an admin key. A production token would put the same HTS authority behind issuer governance, multisig, policy checks, and audit controls.

Built With

Area Technology
Runtime Node.js 22+
Monorepo npm workspaces
HTS package TypeScript, Solidity, @hiero-ledger/sdk, solc, ethers
Contract library @hiero-ledger/hiero-contracts
Frontend React 19, Vite, Tailwind CSS v4, HashPack via Hedera WalletConnect
UI package shadcn-style primitives in packages/ui
Tests Vitest
Deployment Hedera testnet, Cloudflare Workers

What It Shows

  • Contract-created HTS fungible token with regulated token keys.
  • HashPack-signed token association.
  • Public testnet KYC grant/revoke through a contract key.
  • Public testnet freeze/unfreeze through a contract key.
  • Public testnet regulatory wipe through a contract key.
  • Public testnet pause/unpause through a contract key.
  • Faucet claim flow that mints HTS supply and transfers demo tokens to the caller.
  • Mirror Node reads for token state, transfer gate state, and holder ledger.

Demo Design Choices

This template is built to be easy to evaluate live. The public methods are not an accident; they are the demo surface.

Choice Why it exists
Public faucet claim() Lets a visitor mint a fixed amount of testnet RST after association and KYC. No backend signer is required.
Public KYC actions Shows Hedera's native KYC gate directly from the UI. A visitor can grant and revoke eligibility to see transfer state change.
Public freeze actions Demonstrates per-account regulatory controls without writing custom token transfer hooks.
Public wipe action Demonstrates forced balance removal for testnet regulatory scenarios.
Public pause actions Demonstrates token-wide transfer halt and resume through HTS pause key behavior.
Contract-held HTS keys The Solidity controller owns the HTS role keys, so the frontend never stores a private admin key.
Static frontend Cloudflare Workers hosts the demo as static assets. There is no worker entrypoint and no secret-bearing API.
HashPack-only wallet UX The app uses native Hedera transaction signing instead of pretending the token is just another ERC-20.

For production, the public methods should be replaced with access control and real issuer operations. The important point is that the same HTS primitives remain the core mechanism.

Current Testnet Deployment

Field Value
Network Hedera testnet
Token ID 0.0.9960798
Faucet contract ID 0.0.9960797
Claim amount 100 RST
Manifest packages/hts/deployments/testnet/regulated-faucet.json

Architecture

packages/hts/contracts/RegulatedHtsFaucet.sol is the demo controller. It creates one HTS token through the Hedera Token Service precompile, sets itself as the HTS role key for admin, KYC, freeze, wipe, supply, and pause, then exposes small public demo methods.

apps/web is a static dApp. It uses HashPack through Hedera WalletConnect for native Hedera transaction signing and Mirror Node REST for read-only state. Cloudflare hosts it as Worker static assets.

Association is deliberately wallet-signed. Hedera token association must be authorized by the holder account, so the frontend builds a native TokenAssociateTransaction and sends it to HashPack for approval.

Compliance actions are also wallet-signed Hedera transactions. The frontend builds ContractExecuteTransaction calls to the faucet contract, and HashPack signs/submits them from the connected account. The Solidity contract owns the HTS role keys, which is why public testnet users can trigger KYC, freeze, wipe, pause, and faucet actions without any server-held admin key.

Mirror Node is used for read-only state: token metadata, holder token relations, KYC status, freeze status, balances, transaction results, and the holder ledger. The ledger auto-refreshes every 45 seconds and also refetches after UI actions to handle Mirror Node indexing delay.

HTS Concepts

Concept In this repo
Token ID The canonical Hedera token identifier, for example 0.0.9960798. This is what the UI shows.
Association A holder must opt into an HTS token before receiving it. The holder signs this with HashPack.
KYC A token-level eligibility flag. Transfers require KYC when the token has a KYC key.
Freeze A per-account transfer lock for this token.
Wipe A regulated removal of tokens from a holder balance.
Pause A token-wide halt of transfers.
Treasury HTS requires a treasury account. In this demo the contract is the treasury because it creates and controls the token.
Mirror Node Hedera's indexed REST data source used by the frontend for display state.

User Flow

  1. Connect HashPack.
  2. Click Associate token.
  3. Click Grant KYC.
  4. Click Claim 100 RST.
  5. Transfers are now allowed while the holder has KYC, is not frozen, and the token is not paused.
  6. Try Freeze, Unfreeze, Wipe, Pause, Unpause, Revoke KYC, and Grant KYC to see native HTS compliance controls reflected through Mirror Node.

UI Actions

Action What it does
Associate token Sends a native TokenAssociateTransaction signed by the connected HashPack account.
Grant KYC Calls the faucet controller to grant HTS KYC for a target holder.
Claim 100 RST Mints demo supply and transfers it to the connected holder.
Revoke KYC Removes the holder's transfer eligibility.
Freeze holder Freezes one holder for this token.
Unfreeze holder Clears the holder freeze flag.
Wipe tokens Removes a chosen testnet balance amount from a holder.
Pause token Pauses transfers for the whole HTS token.
Unpause token Resumes transfers.

Actions that target a holder accept a Hedera account ID such as 0.0.9959030. The frontend resolves the account's EVM alias internally when calling the Solidity precompile wrapper, but the user-facing UI stays Hedera-native.

Local Setup

npm install
npm run check
cp packages/hts/.env.example packages/hts/.env
cp apps/web/.env.example apps/web/.env

For contract deployment, fill packages/hts/.env with a funded Hedera testnet operator:

HEDERA_NETWORK=testnet
HEDERA_OPERATOR_ID=0.0.x
HEDERA_OPERATOR_KEY=...

Deploy a new faucet-controlled HTS token:

npm run deploy:faucet

The script deploys the Solidity controller, creates the HTS token, writes packages/hts/deployments/<network>/regulated-faucet.json, and prints the VITE_* values for the frontend.

Set apps/web/.env:

VITE_HEDERA_NETWORK=testnet
VITE_HEDERA_TOKEN_ID=0.0.x
VITE_HTS_FAUCET_CONTRACT_ID=0.0.x
VITE_HTS_FAUCET_CLAIM_AMOUNT=10000
VITE_MIRROR_NODE_REST_URL=https://testnet.mirrornode.hedera.com
VITE_DEMO_HOLDER_ACCOUNT_ID=0.0.x
VITE_WALLETCONNECT_PROJECT_ID=c31d31a72f20e6ff1c43934de17d8664

Run the frontend locally:

npm run dev:web

Open the printed Vite URL. The Connect HashPack button pairs with the HashPack browser extension and then sends native Hedera transactions to the wallet for user approval.

VITE_WALLETCONNECT_PROJECT_ID is a public WalletConnect/Reown project id used by the relay transport. It is not a private key and does not grant issuer authority.

Manual Demo Script

Use this flow when presenting the project:

  1. Open the frontend and connect HashPack on Hedera testnet.
  2. Go to Actions.
  3. Run Associate token for the connected account.
  4. Run Grant KYC for the same account.
  5. Run Claim 100 RST.
  6. Go to Overview and watch the transfer gate and holder ledger update.
  7. Run Freeze holder and show that the transfer gate blocks the account.
  8. Run Unfreeze holder, then Revoke KYC, then Grant KYC again.
  9. Run Pause token and Unpause token to show token-wide controls.
  10. Run Wipe tokens with a small amount to demonstrate regulated balance removal.

Mirror Node can take a few seconds to index new transactions. The frontend refreshes after actions and the holder ledger auto-refreshes every 45 seconds.

The faucet claim is one-time per wallet. After a wallet has claimed, the UI marks the faucet as claimed and blocks another claim for that wallet.

Cloudflare

Static assets on Cloudflare Workers — no worker entrypoint, no server code.

For Cloudflare Workers Builds / static assets, use:

Setting Value
Root directory repository root
Build command npm run build
Deploy command npm run deploy:web

The web app is deployed as Worker static assets, matching the RWA frontend pattern. Do not use wrangler pages deploy in this project; that command targets the Pages API and can fail in Cloudflare Builds with a hidden build token that is not scoped for Pages direct upload.

For local or external CI direct upload, use Wrangler:

npm run build:web
npm run deploy:web

Cloudflare config lives in apps/web/wrangler.jsonc, uses assets.directory: "./dist", and deploys the hedera-regulated-token static-assets Worker.

Wrangler direct upload needs either an interactive wrangler login session or a Cloudflare Builds token with Workers deploy permissions.

Because the app is fully static, configuration is baked in at build time from VITE_* variables. Set the same public VITE_* variables in the Cloudflare project before building, and rebuild after changing any of them. No Cloudflare Worker secrets are required for the frontend; .dev.vars has no effect, as it only reaches server-side worker code.

Useful Commands

Command Description
npm run check Builds all packages and runs HTS tests
npm run deploy:faucet Deploys the Solidity faucet controller and creates a new HTS token
npm run dev:web Starts the Vite frontend
npm run build:web Builds the static frontend
npm run deploy:web Builds and deploys the frontend as Cloudflare static assets
npm run token:info Reads token info through the SDK utilities

Repository Layout

.
├── apps/
│   └── web/                     # Static React dApp
├── packages/
│   ├── hts/
│   │   ├── contracts/           # Solidity HTS controller
│   │   ├── deployments/         # Testnet deployment manifests
│   │   ├── scripts/             # Hedera deploy and utility scripts
│   │   ├── src/                 # SDK helpers and compiler wrapper
│   │   └── tests/               # Vitest coverage
│   └── ui/                      # Shared UI primitives
└── README.md

Security Notes

This is a public testnet demo. The faucet contract intentionally exposes role methods so anyone can exercise Hedera compliance behavior.

For production, do not expose KYC, freeze, wipe, pause, or mint methods publicly. Keep the HTS authority behind real issuer governance and audit the contract before mainnet use.

Production Hardening Path

Before adapting this template for mainnet:

  • Replace public role methods with issuer-only authorization.
  • Put HTS role keys behind multisig, a governance contract, or a transfer-agent operations service.
  • Separate faucet behavior from regulated issuance.
  • Add policy checks for KYC, jurisdiction, transfer limits, and investor status.
  • Add event indexing, monitoring, and operational runbooks.
  • Audit the Solidity controller and deployment scripts.
  • Use dedicated environments for testnet, staging, and mainnet.

About

Hedera HTS regulated token demo with KYC, freeze, wipe, pause, faucet claims, HashPack signing, and a Cloudflare-ready frontend.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages