Create a Token

Learn how to launch a simple fungible token on Hedera testnet. A fungible token is a divisible digital asset (think loyalty points, stablecoins, or stocks) created by the Hedera Token Service (HTS).


Prerequisites

  • A Hedera testnet operator account ID and DER-encoded private key (from the Quickstart)

  • A few testnet HBAR (ℏ) to cover the ≈ $1 token-creation fee

We will use the operator account as the token’s treasury (the account that initially holds the supply).

Note

You can always check the "Code Check" section at the bottom of each page to view the entire code if you run into issues. You can also post your issue to the respective SDK channel in our Discord community here.


Project Setup and SDK Installation

Open your terminal and create a directory hedera-examples directory. Then change into the newly created directory:

mkdir hedera-examples && cd hedera-examples

Initialize a node.js project in this new directory:

npm init -y

Ensure you have Node.js v18 or later installed on your machine. Then, install the JavaScript SDK.

npm install --save @hashgraph/sdk

Update your package.json file to enable ES6 modules and configure the project:

{
  "name": "hedera-examples",
  "version": "1.0.0",
  "type": "module",
  "main": "createTokenDemo.js",
  "scripts": {
    "start": "node createTokenDemo.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@hashgraph/sdk": "^2.69.0"
  }
}

Create a createTokenDemo.js file and add the following imports:

import {
  Client,
  PrivateKey,
  TokenCreateTransaction,
  TokenSupplyType
} from "@hashgraph/sdk";

Environment Variables

Set your operator credentials as environment variables. Your OPERATOR_ID is your testnet account ID. Your OPERATOR_KEY is your testnet account's corresponding ECDSA private key.

export OPERATOR_ID="0.0.1234"
export OPERATOR_KEY="3030020100300506032b657004220420..."

Step 1: Initialize Hedera client

Load your operator credentials from environment variables and initialize your Hedera testnet client. This client will connect to the Hedera test network and use your operator account to sign transactions and pay transaction fees.

// Load your operator credentials
const operatorId  = process.env.OPERATOR_ID;
const operatorKey = process.env.OPERATOR_KEY;

// Initialize your testnet client and set operator
const client = Client.forTestnet()
    .setOperator(operatorId, operatorKey);

Step 2: Generate Token Keys

Generate an ECDSA key and use it for both admin and supply operations.

Why keys?

adminKey lets you update or delete the token; supplyKey authorizes mint and burn operations. We use the same key for both roles to keep this tutorial simple.

// Generate keys that control your token
const supplyKey = PrivateKey.generateECDSA();   // can mint/burn
const adminKey  = supplyKey;    // can update and delete (reuse for simplicity)

‼️ Security reminder: Keep your private keys secure - anyone with access can control your token.


Step 3: Create Your First Token on Hedera

Build a TokenCreateTransaction with your token properties like name, symbol, and initial supply. Sign the transaction with your admin key, submit it to the network, and Hedera returns a unique token ID that identifies your token.

// Build the transaction
const transaction = new TokenCreateTransaction()
  .setTokenName("Demo Token")        // readable name
  .setTokenSymbol("DEMO")
  .setDecimals(2)                    // 100 = 1.00 token
  .setInitialSupply(100_000)         // 1 000.00 DEMO in treasury
  .setSupplyType(TokenSupplyType.Finite)
  .setMaxSupply(100_000)             // cap equals initial supply
  .setTreasuryAccountId(operatorId)
  .setAdminKey(adminKey.publicKey)   // optional
  .setSupplyKey(supplyKey.publicKey) // optional for fungible tokens
  .setTokenMemo("Created via tutorial")
  .freezeWith(client);

// Sign with the admin key, execute with the operator, and get receipt
const signedTx = await transaction.sign(adminKey);
const txResponse = await signedTx.execute(client);
const receipt = await txResponse.getReceipt(client);
const tokenId = receipt.tokenId;

console.log(`\nFungible token created: ${tokenId.toString()}`)

Step 4: Query the Treasury Balance Using Mirror Node API

Use the Mirror Node REST API to check your treasury account's token balance. Mirror nodes provide free access to network data without transaction fees.

API endpoint:

/api/v1/accounts/{accountId}/tokens?token.id={tokenId}

Replace the placeholders:

  • {accountId} - Your treasury account (operator account)

  • {tokenId} - Token ID from the creation transaction

Why this endpoint?

This endpoint queries the account's token balances, filtered by the specific token ID. It returns detailed information including the balance and token metadata, making it ideal for verifying the treasury account holds the expected initial supply.

Example URLs:

const mirrorNodeUrl =`https://testnet.mirrornode.hedera.com/api/v1/accounts/${operatorId}/tokens?token.id=${tokenId}`;

Complete Implementation:

// Wait for Mirror Node to populate data
console.log("\nWaiting for Mirror Node to update...");
await new Promise(resolve => setTimeout(resolve, 3000));

// Query balance using Mirror Node
const mirrorNodeUrl = `https://testnet.mirrornode.hedera.com/api/v1/accounts/${operatorId}/tokens?token.id=${tokenId}`;

const response = await fetch(mirrorNodeUrl);
const data = await response.json();

if (data.tokens && data.tokens.length > 0) {
  const balance = data.tokens[0].balance;
  console.log(`\nTreasury holds: ${balance} DEMO\n`);
} else {
  console.log("Token balance not yet available in Mirror Node");
}

client.close();

✅ Code check

Before running your project, verify your code matches the complete example:

JavaScript
import {
  Client,
  PrivateKey,
  TokenCreateTransaction,
  TokenSupplyType
} from "@hashgraph/sdk";

async function createTokenDemo() {
  // load your operator credentials
  const operatorId = process.env.OPERATOR_ID;
  const operatorKey = process.env.OPERATOR_KEY;

  // initialize the client for testnet
  const client = Client.forTestnet()
    .setOperator(operatorId, operatorKey);

  // generate token keys
  const supplyKey = PrivateKey.generateECDSA();
  const adminKey = supplyKey;

  // build & execute the token creation transaction
  const transaction = new TokenCreateTransaction()
    .setTokenName("Demo Token")
    .setTokenSymbol("DEMO")
    .setDecimals(2)
    .setInitialSupply(100_000)
    .setSupplyType(TokenSupplyType.Finite)
    .setMaxSupply(100_000)
    .setTreasuryAccountId(operatorId)
    .setAdminKey(adminKey.publicKey)
    .setSupplyKey(supplyKey.publicKey)
    .setTokenMemo("Created via tutorial")
    .freezeWith(client);

  const signedTx = await transaction.sign(adminKey);
  const txResponse = await signedTx.execute(client);
  const receipt = await txResponse.getReceipt(client);
  const tokenId = receipt.tokenId;

  console.log(`\nFungible token created: ${tokenId}`);

  // Wait for Mirror Node to populate data
  console.log("\nWaiting for Mirror Node to update...");
  await new Promise(resolve => setTimeout(resolve, 3000));

  // query balance using Mirror Node
  const mirrorNodeUrl = `https://testnet.mirrornode.hedera.com/api/v1/accounts/${operatorId}/tokens?token.id=${tokenId}`;

  const response = await fetch(mirrorNodeUrl);
  const data = await response.json();
  
  if (data.tokens && data.tokens.length > 0) {
    const balance = data.tokens[0].balance;
    console.log(`\nTreasury holds: ${balance} DEMO\n`);
  } else {
    console.log("Token balance not yet available in Mirror Node");
  }

  client.close();
}

createTokenDemo().catch(console.error);
Java
import com.hedera.hashgraph.sdk.*;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonArray; 

public class CreateTokenDemo {
    public static void main(String[] args ) throws Exception {
        // load your operator credentials
        AccountId operatorId = AccountId.fromString(System.getenv("OPERATOR_ID"));
        PrivateKey operatorKey = PrivateKey.fromString(System.getenv("OPERATOR_KEY"));

        // initialize the client for testnet
        Client client = Client.forTestnet().setOperator(operatorId, operatorKey);

        // generate token keys
        PrivateKey supplyKey = PrivateKey.generateECDSA();
        PrivateKey adminKey = supplyKey;

        // build & execute the token creation transaction
        TokenCreateTransaction transaction = new TokenCreateTransaction()
            .setTokenName("Demo Token")
            .setTokenSymbol("DEMO")
            .setDecimals(2)
            .setInitialSupply(100_000)
            .setSupplyType(TokenSupplyType.FINITE)
            .setMaxSupply(100_000)
            .setTreasuryAccountId(operatorId)
            .setAdminKey(adminKey.getPublicKey())
            .setSupplyKey(supplyKey.getPublicKey())
            .setTokenMemo("Created via tutorial")
            .freezeWith(client);

        TokenCreateTransaction signedTx = transaction.sign(adminKey);
        TransactionResponse txResponse = signedTx.execute(client);
        TransactionReceipt receipt = txResponse.getReceipt(client);
        TokenId tokenId = receipt.tokenId;

        System.out.println("\nFungible token created: " + tokenId );

        // Wait for Mirror Node to populate data
        System.out.println("\nWaiting for Mirror Node to update...");
        Thread.sleep(3000);

        // query balance using Mirror Node
        String mirrorNodeUrl = "https://testnet.mirrornode.hedera.com/api/v1/accounts/" + 
                               operatorId + "/tokens?token.id=" + tokenId;
        
        HttpClient httpClient = HttpClient.newHttpClient( );
        HttpRequest request = HttpRequest.newBuilder().uri(URI.create(mirrorNodeUrl)).build();
        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString( ));
        
        JsonObject data = new Gson().fromJson(response.body(), JsonObject.class);
        JsonArray tokens = data.getAsJsonArray("tokens");
        
        if (tokens.size() > 0) {
            long balance = tokens.get(0).getAsJsonObject().get("balance").getAsLong();
            System.out.println("\nTreasury holds: " + balance + " DEMO\n");
        } else {
            System.out.println("Token balance not yet available in Mirror Node");
        }

        client.close();
    }
}
Go
package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
    "time"

    hedera "github.com/hiero-ledger/hiero-sdk-go/v2/sdk"
)

func main() {
	// load your operator credentials
	operatorId, _ := hedera.AccountIDFromString(os.Getenv("OPERATOR_ID"))
	operatorKey, _ := hedera.PrivateKeyFromString(os.Getenv("OPERATOR_KEY"))

	// initialize the client for testnet
	client := hedera.ClientForTestnet()
	client.SetOperator(operatorId, operatorKey)

	// generate token keys
	supplyKey, _ := hedera.PrivateKeyGenerateEcdsa()
	adminKey := supplyKey

	// build & execute the token creation transaction
	transaction, _ := hedera.NewTokenCreateTransaction().
		SetTokenName("Demo Token").
		SetTokenSymbol("DEMO").
		SetDecimals(2).
		SetInitialSupply(100_000).
		SetSupplyType(hedera.TokenSupplyTypeFinite).
		SetMaxSupply(100_000).
		SetTreasuryAccountID(operatorId).
		SetAdminKey(adminKey.PublicKey()).
		SetSupplyKey(supplyKey.PublicKey()).
		SetTokenMemo("Created via tutorial").
		FreezeWith(client)

	signedTx := transaction.Sign(adminKey)
	txResponse, _ := signedTx.Execute(client)
	receipt, _ := txResponse.GetReceipt(client)
	tokenId := *receipt.TokenID

	fmt.Printf("\nToken created: %s\n", tokenId.String())

	// Wait for Mirror Node to populate data
	fmt.Println("\nWaiting for Mirror Node to update...")
	time.Sleep(3 * time.Second)

	// query balance using Mirror Node
	mirrorNodeUrl := "https://testnet.mirrornode.hedera.com/api/v1/accounts/" +
		operatorId.String() + "/tokens?token.id=" + tokenId.String()

	response, _ := http.Get(mirrorNodeUrl)
	defer response.Body.Close()

	body, _ := io.ReadAll(response.Body)

	var data struct {
		Tokens []struct {
			Balance int64 `json:"balance"`
		} `json:"tokens"`
	}

	json.Unmarshal(body, &data)

	if len(data.Tokens) > 0 {
		balance := data.Tokens[0].Balance
		fmt.Printf("\nTreasury holds: %d DEMO\n\n", balance)
	} else {
		fmt.Println("Token balance not yet available in Mirror Node")
	}

	client.Close()
}

Run Your Project

Ensure your environment variables are set:

export OPERATOR_ID="0.0.1234"
export OPERATOR_KEY="3030020100300506032b657004220420..."
node createTokenDemo.js

Expected sample output:

Fungible token created: 0.0.12345

Waiting for Mirror Node to update...

Treasury holds: 100000 DEMO

‼️ Troubleshooting

Common ERROR messages and solutions ⬇️
Symptom
Likely cause
Fix

INSUFFICIENT_PAYER_BALANCE

Not enough HBAR for transaction fees

Top up your operator account on the testnet faucet

INVALID_SIGNATURE

Not signing the transaction with the admin key or treasury account

Add .sign(privateKey)to the transaction before executing it

TOKEN_ALREADY_ASSOCIATED_TO_ACCOUNT

Account already associated with token

This is normal for treasury accounts - ignore this error

INVALID_ACCOUNT_ID

Malformed account ID in environment variables

Verify OPERATOR_ID format is 0.0.1234

INVALID_PRIVATE_KEY

Malformed private key in environment variables

Verify OPERATOR_KEY is a valid DER-encoded private key string

Environment Issues:

  • Environment variables not set or accessible

  • Wrong network (mainnet vs testnet) configuration

  • SDK version compatibility issues


What just happened?

  1. The SDK built a TokenCreateTransaction and signed it with every required key.

  2. A consensus node validated signatures and attached the fee.

  3. After network consensus, HTS registered your token and returned its token ID.

  4. The full initial supply now sits in your treasury account and is ready to transfer.

  5. The Mirror Node API confirmed your treasury account holds the token balance.


Next steps


🎉 Great work! You've successfully created your first fungible token on Hedera and verified its balance using the Mirror Node API. Guard your private keys and happy building!

Additional Resources

Last updated

Was this helpful?